##// END OF EJS Templates
move connect_[channel] to ConnectionFileMixin
MinRK -
Show More
@@ -1,216 +1,203 b''
1 """Base class to manage the interaction with a running kernel
1 """Base class to manage the interaction with a running kernel
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import absolute_import
15 from __future__ import absolute_import
16
16
17 import zmq
17 import zmq
18
18
19 # Local imports
19 # Local imports
20 from IPython.config.configurable import LoggingConfigurable
20 from IPython.config.configurable import LoggingConfigurable
21 from IPython.utils.traitlets import (
21 from IPython.utils.traitlets import (
22 Any, Instance, Type,
22 Any, Instance, Type,
23 )
23 )
24
24
25 from .zmq.session import Session
25 from .zmq.session import Session
26 from .channels import (
26 from .channels import (
27 ShellChannel, IOPubChannel,
27 ShellChannel, IOPubChannel,
28 HBChannel, StdInChannel,
28 HBChannel, StdInChannel,
29 )
29 )
30 from .clientabc import KernelClientABC
30 from .clientabc import KernelClientABC
31 from .connect import ConnectionFileMixin
31 from .connect import ConnectionFileMixin
32
32
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Main kernel client class
35 # Main kernel client class
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
38 class KernelClient(LoggingConfigurable, ConnectionFileMixin):
39 """Communicates with a single kernel on any host via zmq channels.
39 """Communicates with a single kernel on any host via zmq channels.
40
40
41 There are four channels associated with each kernel:
41 There are four channels associated with each kernel:
42
42
43 * shell: for request/reply calls to the kernel.
43 * shell: for request/reply calls to the kernel.
44 * iopub: for the kernel to publish results to frontends.
44 * iopub: for the kernel to publish results to frontends.
45 * hb: for monitoring the kernel's heartbeat.
45 * hb: for monitoring the kernel's heartbeat.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
46 * stdin: for frontends to reply to raw_input calls in the kernel.
47
47
48 """
48 """
49
49
50 # The PyZMQ Context to use for communication with the kernel.
50 # The PyZMQ Context to use for communication with the kernel.
51 context = Instance(zmq.Context)
51 context = Instance(zmq.Context)
52 def _context_default(self):
52 def _context_default(self):
53 return zmq.Context.instance()
53 return zmq.Context.instance()
54
54
55 # The Session to use for communication with the kernel.
55 # The Session to use for communication with the kernel.
56 session = Instance(Session)
56 session = Instance(Session)
57 def _session_default(self):
57 def _session_default(self):
58 return Session(config=self.config)
58 return Session(config=self.config)
59
59
60 # The classes to use for the various channels
60 # The classes to use for the various channels
61 shell_channel_class = Type(ShellChannel)
61 shell_channel_class = Type(ShellChannel)
62 iopub_channel_class = Type(IOPubChannel)
62 iopub_channel_class = Type(IOPubChannel)
63 stdin_channel_class = Type(StdInChannel)
63 stdin_channel_class = Type(StdInChannel)
64 hb_channel_class = Type(HBChannel)
64 hb_channel_class = Type(HBChannel)
65
65
66 # Protected traits
66 # Protected traits
67 _shell_channel = Any
67 _shell_channel = Any
68 _iopub_channel = Any
68 _iopub_channel = Any
69 _stdin_channel = Any
69 _stdin_channel = Any
70 _hb_channel = Any
70 _hb_channel = Any
71
71
72 # def __init__(self, *args, **kwargs):
72 # def __init__(self, *args, **kwargs):
73 # super(KernelClient, self).__init__(*args, **kwargs)
73 # super(KernelClient, self).__init__(*args, **kwargs)
74 # # setup channel proxy methods, e.g.
74 # # setup channel proxy methods, e.g.
75 # # Client.execute => shell_channel.execute
75 # # Client.execute => shell_channel.execute
76 # for channel in ['shell', 'iopub', 'stdin', 'hb']:
76 # for channel in ['shell', 'iopub', 'stdin', 'hb']:
77 # cls = getattr(self, '%s_channel_class' % channel)
77 # cls = getattr(self, '%s_channel_class' % channel)
78 # for method in cls.proxy_methods:
78 # for method in cls.proxy_methods:
79 # setattr(self, method, self._proxy_method(channel, method))
79 # setattr(self, method, self._proxy_method(channel, method))
80 #
80 #
81 #--------------------------------------------------------------------------
81 #--------------------------------------------------------------------------
82 # Channel proxy methods
82 # Channel proxy methods
83 #--------------------------------------------------------------------------
83 #--------------------------------------------------------------------------
84
84
85 def _get_msg(channel, *args, **kwargs):
85 def _get_msg(channel, *args, **kwargs):
86 return channel.get_msg(*args, **kwargs)
86 return channel.get_msg(*args, **kwargs)
87
87
88 def get_shell_msg(self, *args, **kwargs):
88 def get_shell_msg(self, *args, **kwargs):
89 """Get a message from the shell channel"""
89 """Get a message from the shell channel"""
90 return self.shell_channel.get_msg(*args, **kwargs)
90 return self.shell_channel.get_msg(*args, **kwargs)
91
91
92 def get_iopub_msg(self, *args, **kwargs):
92 def get_iopub_msg(self, *args, **kwargs):
93 """Get a message from the iopub channel"""
93 """Get a message from the iopub channel"""
94 return self.iopub_channel.get_msg(*args, **kwargs)
94 return self.iopub_channel.get_msg(*args, **kwargs)
95
95
96 def get_stdin_msg(self, *args, **kwargs):
96 def get_stdin_msg(self, *args, **kwargs):
97 """Get a message from the stdin channel"""
97 """Get a message from the stdin channel"""
98 return self.stdin_channel.get_msg(*args, **kwargs)
98 return self.stdin_channel.get_msg(*args, **kwargs)
99
99
100 #--------------------------------------------------------------------------
100 #--------------------------------------------------------------------------
101 # Channel management methods
101 # Channel management methods
102 #--------------------------------------------------------------------------
102 #--------------------------------------------------------------------------
103
103
104 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
104 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
105 """Starts the channels for this kernel.
105 """Starts the channels for this kernel.
106
106
107 This will create the channels if they do not exist and then start
107 This will create the channels if they do not exist and then start
108 them (their activity runs in a thread). If port numbers of 0 are
108 them (their activity runs in a thread). If port numbers of 0 are
109 being used (random ports) then you must first call
109 being used (random ports) then you must first call
110 :method:`start_kernel`. If the channels have been stopped and you
110 :method:`start_kernel`. If the channels have been stopped and you
111 call this, :class:`RuntimeError` will be raised.
111 call this, :class:`RuntimeError` will be raised.
112 """
112 """
113 if shell:
113 if shell:
114 self.shell_channel.start()
114 self.shell_channel.start()
115 for method in self.shell_channel.proxy_methods:
115 for method in self.shell_channel.proxy_methods:
116 setattr(self, method, getattr(self.shell_channel, method))
116 setattr(self, method, getattr(self.shell_channel, method))
117 if iopub:
117 if iopub:
118 self.iopub_channel.start()
118 self.iopub_channel.start()
119 for method in self.iopub_channel.proxy_methods:
119 for method in self.iopub_channel.proxy_methods:
120 setattr(self, method, getattr(self.iopub_channel, method))
120 setattr(self, method, getattr(self.iopub_channel, method))
121 if stdin:
121 if stdin:
122 self.stdin_channel.start()
122 self.stdin_channel.start()
123 for method in self.stdin_channel.proxy_methods:
123 for method in self.stdin_channel.proxy_methods:
124 setattr(self, method, getattr(self.stdin_channel, method))
124 setattr(self, method, getattr(self.stdin_channel, method))
125 self.shell_channel.allow_stdin = True
125 self.shell_channel.allow_stdin = True
126 else:
126 else:
127 self.shell_channel.allow_stdin = False
127 self.shell_channel.allow_stdin = False
128 if hb:
128 if hb:
129 self.hb_channel.start()
129 self.hb_channel.start()
130
130
131 def stop_channels(self):
131 def stop_channels(self):
132 """Stops all the running channels for this kernel.
132 """Stops all the running channels for this kernel.
133
133
134 This stops their event loops and joins their threads.
134 This stops their event loops and joins their threads.
135 """
135 """
136 if self.shell_channel.is_alive():
136 if self.shell_channel.is_alive():
137 self.shell_channel.stop()
137 self.shell_channel.stop()
138 if self.iopub_channel.is_alive():
138 if self.iopub_channel.is_alive():
139 self.iopub_channel.stop()
139 self.iopub_channel.stop()
140 if self.stdin_channel.is_alive():
140 if self.stdin_channel.is_alive():
141 self.stdin_channel.stop()
141 self.stdin_channel.stop()
142 if self.hb_channel.is_alive():
142 if self.hb_channel.is_alive():
143 self.hb_channel.stop()
143 self.hb_channel.stop()
144
144
145 @property
145 @property
146 def channels_running(self):
146 def channels_running(self):
147 """Are any of the channels created and running?"""
147 """Are any of the channels created and running?"""
148 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
148 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
149 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
149 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
150
150
151 def _make_url(self, port):
152 """Make a zmq url with a port.
153
154 There are two cases that this handles:
155
156 * tcp: tcp://ip:port
157 * ipc: ipc://ip-port
158 """
159 if self.transport == 'tcp':
160 return "tcp://%s:%i" % (self.ip, port)
161 else:
162 return "%s://%s-%s" % (self.transport, self.ip, port)
163
164 @property
151 @property
165 def shell_channel(self):
152 def shell_channel(self):
166 """Get the shell channel object for this kernel."""
153 """Get the shell channel object for this kernel."""
167 if self._shell_channel is None:
154 if self._shell_channel is None:
168 self._shell_channel = self.shell_channel_class(
155 self._shell_channel = self.shell_channel_class(
169 self.context, self.session, self._make_url(self.shell_port)
156 self.context, self.session, self._make_url('shell')
170 )
157 )
171 return self._shell_channel
158 return self._shell_channel
172
159
173 @property
160 @property
174 def iopub_channel(self):
161 def iopub_channel(self):
175 """Get the iopub channel object for this kernel."""
162 """Get the iopub channel object for this kernel."""
176 if self._iopub_channel is None:
163 if self._iopub_channel is None:
177 self._iopub_channel = self.iopub_channel_class(
164 self._iopub_channel = self.iopub_channel_class(
178 self.context, self.session, self._make_url(self.iopub_port)
165 self.context, self.session, self._make_url('iopub')
179 )
166 )
180 return self._iopub_channel
167 return self._iopub_channel
181
168
182 @property
169 @property
183 def stdin_channel(self):
170 def stdin_channel(self):
184 """Get the stdin channel object for this kernel."""
171 """Get the stdin channel object for this kernel."""
185 if self._stdin_channel is None:
172 if self._stdin_channel is None:
186 self._stdin_channel = self.stdin_channel_class(
173 self._stdin_channel = self.stdin_channel_class(
187 self.context, self.session, self._make_url(self.stdin_port)
174 self.context, self.session, self._make_url('stdin')
188 )
175 )
189 return self._stdin_channel
176 return self._stdin_channel
190
177
191 @property
178 @property
192 def hb_channel(self):
179 def hb_channel(self):
193 """Get the hb channel object for this kernel."""
180 """Get the hb channel object for this kernel."""
194 if self._hb_channel is None:
181 if self._hb_channel is None:
195 self._hb_channel = self.hb_channel_class(
182 self._hb_channel = self.hb_channel_class(
196 self.context, self.session, self._make_url(self.hb_port)
183 self.context, self.session, self._make_url('hb')
197 )
184 )
198 return self._hb_channel
185 return self._hb_channel
199
186
200 def is_alive(self):
187 def is_alive(self):
201 """Is the kernel process still running?"""
188 """Is the kernel process still running?"""
202 if self._hb_channel is not None:
189 if self._hb_channel is not None:
203 # We didn't start the kernel with this KernelManager so we
190 # We didn't start the kernel with this KernelManager so we
204 # use the heartbeat.
191 # use the heartbeat.
205 return self._hb_channel.is_beating()
192 return self._hb_channel.is_beating()
206 else:
193 else:
207 # no heartbeat and not local, we can't tell if it's running,
194 # no heartbeat and not local, we can't tell if it's running,
208 # so naively return True
195 # so naively return True
209 return True
196 return True
210
197
211
198
212 #-----------------------------------------------------------------------------
199 #-----------------------------------------------------------------------------
213 # ABC Registration
200 # ABC Registration
214 #-----------------------------------------------------------------------------
201 #-----------------------------------------------------------------------------
215
202
216 KernelClientABC.register(KernelClient)
203 KernelClientABC.register(KernelClient)
@@ -1,478 +1,538 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Min Ragan-Kelley
5 * Min Ragan-Kelley
6
6
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from __future__ import absolute_import
21
20 import glob
22 import glob
21 import json
23 import json
22 import os
24 import os
23 import socket
25 import socket
24 import sys
26 import sys
25 from getpass import getpass
27 from getpass import getpass
26 from subprocess import Popen, PIPE
28 from subprocess import Popen, PIPE
27 import tempfile
29 import tempfile
28
30
31 import zmq
32
29 # external imports
33 # external imports
30 from IPython.external.ssh import tunnel
34 from IPython.external.ssh import tunnel
31
35
32 # IPython imports
36 # IPython imports
33 # from IPython.config import Configurable
37 # from IPython.config import Configurable
34 from IPython.core.profiledir import ProfileDir
38 from IPython.core.profiledir import ProfileDir
35 from IPython.utils.localinterfaces import LOCALHOST
39 from IPython.utils.localinterfaces import LOCALHOST
36 from IPython.utils.path import filefind, get_ipython_dir
40 from IPython.utils.path import filefind, get_ipython_dir
37 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
41 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
38 from IPython.utils.traitlets import (
42 from IPython.utils.traitlets import (
39 Bool, Integer, Unicode, CaselessStrEnum,
43 Bool, Integer, Unicode, CaselessStrEnum,
40 HasTraits,
44 HasTraits,
41 )
45 )
42
46
43
47
44 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
45 # Working with Connection Files
49 # Working with Connection Files
46 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
47
51
48 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
52 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
49 control_port=0, ip=LOCALHOST, key=b'', transport='tcp'):
53 control_port=0, ip=LOCALHOST, key=b'', transport='tcp'):
50 """Generates a JSON config file, including the selection of random ports.
54 """Generates a JSON config file, including the selection of random ports.
51
55
52 Parameters
56 Parameters
53 ----------
57 ----------
54
58
55 fname : unicode
59 fname : unicode
56 The path to the file to write
60 The path to the file to write
57
61
58 shell_port : int, optional
62 shell_port : int, optional
59 The port to use for ROUTER channel.
63 The port to use for ROUTER channel.
60
64
61 iopub_port : int, optional
65 iopub_port : int, optional
62 The port to use for the SUB channel.
66 The port to use for the SUB channel.
63
67
64 stdin_port : int, optional
68 stdin_port : int, optional
65 The port to use for the ROUTER (raw input) channel.
69 The port to use for the ROUTER (raw input) channel.
66
70
67 control_port : int, optional
71 control_port : int, optional
68 The port to use for the ROUTER (raw input) channel.
72 The port to use for the ROUTER (raw input) channel.
69
73
70 hb_port : int, optional
74 hb_port : int, optional
71 The port to use for the hearbeat REP channel.
75 The port to use for the hearbeat REP channel.
72
76
73 ip : str, optional
77 ip : str, optional
74 The ip address the kernel will bind to.
78 The ip address the kernel will bind to.
75
79
76 key : str, optional
80 key : str, optional
77 The Session key used for HMAC authentication.
81 The Session key used for HMAC authentication.
78
82
79 """
83 """
80 # default to temporary connector file
84 # default to temporary connector file
81 if not fname:
85 if not fname:
82 fname = tempfile.mktemp('.json')
86 fname = tempfile.mktemp('.json')
83
87
84 # Find open ports as necessary.
88 # Find open ports as necessary.
85
89
86 ports = []
90 ports = []
87 ports_needed = int(shell_port <= 0) + \
91 ports_needed = int(shell_port <= 0) + \
88 int(iopub_port <= 0) + \
92 int(iopub_port <= 0) + \
89 int(stdin_port <= 0) + \
93 int(stdin_port <= 0) + \
90 int(control_port <= 0) + \
94 int(control_port <= 0) + \
91 int(hb_port <= 0)
95 int(hb_port <= 0)
92 if transport == 'tcp':
96 if transport == 'tcp':
93 for i in range(ports_needed):
97 for i in range(ports_needed):
94 sock = socket.socket()
98 sock = socket.socket()
95 sock.bind(('', 0))
99 sock.bind(('', 0))
96 ports.append(sock)
100 ports.append(sock)
97 for i, sock in enumerate(ports):
101 for i, sock in enumerate(ports):
98 port = sock.getsockname()[1]
102 port = sock.getsockname()[1]
99 sock.close()
103 sock.close()
100 ports[i] = port
104 ports[i] = port
101 else:
105 else:
102 N = 1
106 N = 1
103 for i in range(ports_needed):
107 for i in range(ports_needed):
104 while os.path.exists("%s-%s" % (ip, str(N))):
108 while os.path.exists("%s-%s" % (ip, str(N))):
105 N += 1
109 N += 1
106 ports.append(N)
110 ports.append(N)
107 N += 1
111 N += 1
108 if shell_port <= 0:
112 if shell_port <= 0:
109 shell_port = ports.pop(0)
113 shell_port = ports.pop(0)
110 if iopub_port <= 0:
114 if iopub_port <= 0:
111 iopub_port = ports.pop(0)
115 iopub_port = ports.pop(0)
112 if stdin_port <= 0:
116 if stdin_port <= 0:
113 stdin_port = ports.pop(0)
117 stdin_port = ports.pop(0)
114 if control_port <= 0:
118 if control_port <= 0:
115 control_port = ports.pop(0)
119 control_port = ports.pop(0)
116 if hb_port <= 0:
120 if hb_port <= 0:
117 hb_port = ports.pop(0)
121 hb_port = ports.pop(0)
118
122
119 cfg = dict( shell_port=shell_port,
123 cfg = dict( shell_port=shell_port,
120 iopub_port=iopub_port,
124 iopub_port=iopub_port,
121 stdin_port=stdin_port,
125 stdin_port=stdin_port,
122 control_port=control_port,
126 control_port=control_port,
123 hb_port=hb_port,
127 hb_port=hb_port,
124 )
128 )
125 cfg['ip'] = ip
129 cfg['ip'] = ip
126 cfg['key'] = bytes_to_str(key)
130 cfg['key'] = bytes_to_str(key)
127 cfg['transport'] = transport
131 cfg['transport'] = transport
128
132
129 with open(fname, 'w') as f:
133 with open(fname, 'w') as f:
130 f.write(json.dumps(cfg, indent=2))
134 f.write(json.dumps(cfg, indent=2))
131
135
132 return fname, cfg
136 return fname, cfg
133
137
134
138
135 def get_connection_file(app=None):
139 def get_connection_file(app=None):
136 """Return the path to the connection file of an app
140 """Return the path to the connection file of an app
137
141
138 Parameters
142 Parameters
139 ----------
143 ----------
140 app : IPKernelApp instance [optional]
144 app : IPKernelApp instance [optional]
141 If unspecified, the currently running app will be used
145 If unspecified, the currently running app will be used
142 """
146 """
143 if app is None:
147 if app is None:
144 from IPython.kernel.zmq.kernelapp import IPKernelApp
148 from IPython.kernel.zmq.kernelapp import IPKernelApp
145 if not IPKernelApp.initialized():
149 if not IPKernelApp.initialized():
146 raise RuntimeError("app not specified, and not in a running Kernel")
150 raise RuntimeError("app not specified, and not in a running Kernel")
147
151
148 app = IPKernelApp.instance()
152 app = IPKernelApp.instance()
149 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
153 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
150
154
151
155
152 def find_connection_file(filename, profile=None):
156 def find_connection_file(filename, profile=None):
153 """find a connection file, and return its absolute path.
157 """find a connection file, and return its absolute path.
154
158
155 The current working directory and the profile's security
159 The current working directory and the profile's security
156 directory will be searched for the file if it is not given by
160 directory will be searched for the file if it is not given by
157 absolute path.
161 absolute path.
158
162
159 If profile is unspecified, then the current running application's
163 If profile is unspecified, then the current running application's
160 profile will be used, or 'default', if not run from IPython.
164 profile will be used, or 'default', if not run from IPython.
161
165
162 If the argument does not match an existing file, it will be interpreted as a
166 If the argument does not match an existing file, it will be interpreted as a
163 fileglob, and the matching file in the profile's security dir with
167 fileglob, and the matching file in the profile's security dir with
164 the latest access time will be used.
168 the latest access time will be used.
165
169
166 Parameters
170 Parameters
167 ----------
171 ----------
168 filename : str
172 filename : str
169 The connection file or fileglob to search for.
173 The connection file or fileglob to search for.
170 profile : str [optional]
174 profile : str [optional]
171 The name of the profile to use when searching for the connection file,
175 The name of the profile to use when searching for the connection file,
172 if different from the current IPython session or 'default'.
176 if different from the current IPython session or 'default'.
173
177
174 Returns
178 Returns
175 -------
179 -------
176 str : The absolute path of the connection file.
180 str : The absolute path of the connection file.
177 """
181 """
178 from IPython.core.application import BaseIPythonApplication as IPApp
182 from IPython.core.application import BaseIPythonApplication as IPApp
179 try:
183 try:
180 # quick check for absolute path, before going through logic
184 # quick check for absolute path, before going through logic
181 return filefind(filename)
185 return filefind(filename)
182 except IOError:
186 except IOError:
183 pass
187 pass
184
188
185 if profile is None:
189 if profile is None:
186 # profile unspecified, check if running from an IPython app
190 # profile unspecified, check if running from an IPython app
187 if IPApp.initialized():
191 if IPApp.initialized():
188 app = IPApp.instance()
192 app = IPApp.instance()
189 profile_dir = app.profile_dir
193 profile_dir = app.profile_dir
190 else:
194 else:
191 # not running in IPython, use default profile
195 # not running in IPython, use default profile
192 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
196 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
193 else:
197 else:
194 # find profiledir by profile name:
198 # find profiledir by profile name:
195 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
199 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
196 security_dir = profile_dir.security_dir
200 security_dir = profile_dir.security_dir
197
201
198 try:
202 try:
199 # first, try explicit name
203 # first, try explicit name
200 return filefind(filename, ['.', security_dir])
204 return filefind(filename, ['.', security_dir])
201 except IOError:
205 except IOError:
202 pass
206 pass
203
207
204 # not found by full name
208 # not found by full name
205
209
206 if '*' in filename:
210 if '*' in filename:
207 # given as a glob already
211 # given as a glob already
208 pat = filename
212 pat = filename
209 else:
213 else:
210 # accept any substring match
214 # accept any substring match
211 pat = '*%s*' % filename
215 pat = '*%s*' % filename
212 matches = glob.glob( os.path.join(security_dir, pat) )
216 matches = glob.glob( os.path.join(security_dir, pat) )
213 if not matches:
217 if not matches:
214 raise IOError("Could not find %r in %r" % (filename, security_dir))
218 raise IOError("Could not find %r in %r" % (filename, security_dir))
215 elif len(matches) == 1:
219 elif len(matches) == 1:
216 return matches[0]
220 return matches[0]
217 else:
221 else:
218 # get most recent match, by access time:
222 # get most recent match, by access time:
219 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
223 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
220
224
221
225
222 def get_connection_info(connection_file=None, unpack=False, profile=None):
226 def get_connection_info(connection_file=None, unpack=False, profile=None):
223 """Return the connection information for the current Kernel.
227 """Return the connection information for the current Kernel.
224
228
225 Parameters
229 Parameters
226 ----------
230 ----------
227 connection_file : str [optional]
231 connection_file : str [optional]
228 The connection file to be used. Can be given by absolute path, or
232 The connection file to be used. Can be given by absolute path, or
229 IPython will search in the security directory of a given profile.
233 IPython will search in the security directory of a given profile.
230 If run from IPython,
234 If run from IPython,
231
235
232 If unspecified, the connection file for the currently running
236 If unspecified, the connection file for the currently running
233 IPython Kernel will be used, which is only allowed from inside a kernel.
237 IPython Kernel will be used, which is only allowed from inside a kernel.
234 unpack : bool [default: False]
238 unpack : bool [default: False]
235 if True, return the unpacked dict, otherwise just the string contents
239 if True, return the unpacked dict, otherwise just the string contents
236 of the file.
240 of the file.
237 profile : str [optional]
241 profile : str [optional]
238 The name of the profile to use when searching for the connection file,
242 The name of the profile to use when searching for the connection file,
239 if different from the current IPython session or 'default'.
243 if different from the current IPython session or 'default'.
240
244
241
245
242 Returns
246 Returns
243 -------
247 -------
244 The connection dictionary of the current kernel, as string or dict,
248 The connection dictionary of the current kernel, as string or dict,
245 depending on `unpack`.
249 depending on `unpack`.
246 """
250 """
247 if connection_file is None:
251 if connection_file is None:
248 # get connection file from current kernel
252 # get connection file from current kernel
249 cf = get_connection_file()
253 cf = get_connection_file()
250 else:
254 else:
251 # connection file specified, allow shortnames:
255 # connection file specified, allow shortnames:
252 cf = find_connection_file(connection_file, profile=profile)
256 cf = find_connection_file(connection_file, profile=profile)
253
257
254 with open(cf) as f:
258 with open(cf) as f:
255 info = f.read()
259 info = f.read()
256
260
257 if unpack:
261 if unpack:
258 info = json.loads(info)
262 info = json.loads(info)
259 # ensure key is bytes:
263 # ensure key is bytes:
260 info['key'] = str_to_bytes(info.get('key', ''))
264 info['key'] = str_to_bytes(info.get('key', ''))
261 return info
265 return info
262
266
263
267
264 def connect_qtconsole(connection_file=None, argv=None, profile=None):
268 def connect_qtconsole(connection_file=None, argv=None, profile=None):
265 """Connect a qtconsole to the current kernel.
269 """Connect a qtconsole to the current kernel.
266
270
267 This is useful for connecting a second qtconsole to a kernel, or to a
271 This is useful for connecting a second qtconsole to a kernel, or to a
268 local notebook.
272 local notebook.
269
273
270 Parameters
274 Parameters
271 ----------
275 ----------
272 connection_file : str [optional]
276 connection_file : str [optional]
273 The connection file to be used. Can be given by absolute path, or
277 The connection file to be used. Can be given by absolute path, or
274 IPython will search in the security directory of a given profile.
278 IPython will search in the security directory of a given profile.
275 If run from IPython,
279 If run from IPython,
276
280
277 If unspecified, the connection file for the currently running
281 If unspecified, the connection file for the currently running
278 IPython Kernel will be used, which is only allowed from inside a kernel.
282 IPython Kernel will be used, which is only allowed from inside a kernel.
279 argv : list [optional]
283 argv : list [optional]
280 Any extra args to be passed to the console.
284 Any extra args to be passed to the console.
281 profile : str [optional]
285 profile : str [optional]
282 The name of the profile to use when searching for the connection file,
286 The name of the profile to use when searching for the connection file,
283 if different from the current IPython session or 'default'.
287 if different from the current IPython session or 'default'.
284
288
285
289
286 Returns
290 Returns
287 -------
291 -------
288 subprocess.Popen instance running the qtconsole frontend
292 subprocess.Popen instance running the qtconsole frontend
289 """
293 """
290 argv = [] if argv is None else argv
294 argv = [] if argv is None else argv
291
295
292 if connection_file is None:
296 if connection_file is None:
293 # get connection file from current kernel
297 # get connection file from current kernel
294 cf = get_connection_file()
298 cf = get_connection_file()
295 else:
299 else:
296 cf = find_connection_file(connection_file, profile=profile)
300 cf = find_connection_file(connection_file, profile=profile)
297
301
298 cmd = ';'.join([
302 cmd = ';'.join([
299 "from IPython.frontend.qt.console import qtconsoleapp",
303 "from IPython.frontend.qt.console import qtconsoleapp",
300 "qtconsoleapp.main()"
304 "qtconsoleapp.main()"
301 ])
305 ])
302
306
303 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
307 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
304
308
305
309
306 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
310 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
307 """tunnel connections to a kernel via ssh
311 """tunnel connections to a kernel via ssh
308
312
309 This will open four SSH tunnels from localhost on this machine to the
313 This will open four SSH tunnels from localhost on this machine to the
310 ports associated with the kernel. They can be either direct
314 ports associated with the kernel. They can be either direct
311 localhost-localhost tunnels, or if an intermediate server is necessary,
315 localhost-localhost tunnels, or if an intermediate server is necessary,
312 the kernel must be listening on a public IP.
316 the kernel must be listening on a public IP.
313
317
314 Parameters
318 Parameters
315 ----------
319 ----------
316 connection_info : dict or str (path)
320 connection_info : dict or str (path)
317 Either a connection dict, or the path to a JSON connection file
321 Either a connection dict, or the path to a JSON connection file
318 sshserver : str
322 sshserver : str
319 The ssh sever to use to tunnel to the kernel. Can be a full
323 The ssh sever to use to tunnel to the kernel. Can be a full
320 `user@server:port` string. ssh config aliases are respected.
324 `user@server:port` string. ssh config aliases are respected.
321 sshkey : str [optional]
325 sshkey : str [optional]
322 Path to file containing ssh key to use for authentication.
326 Path to file containing ssh key to use for authentication.
323 Only necessary if your ssh config does not already associate
327 Only necessary if your ssh config does not already associate
324 a keyfile with the host.
328 a keyfile with the host.
325
329
326 Returns
330 Returns
327 -------
331 -------
328
332
329 (shell, iopub, stdin, hb) : ints
333 (shell, iopub, stdin, hb) : ints
330 The four ports on localhost that have been forwarded to the kernel.
334 The four ports on localhost that have been forwarded to the kernel.
331 """
335 """
332 if isinstance(connection_info, basestring):
336 if isinstance(connection_info, basestring):
333 # it's a path, unpack it
337 # it's a path, unpack it
334 with open(connection_info) as f:
338 with open(connection_info) as f:
335 connection_info = json.loads(f.read())
339 connection_info = json.loads(f.read())
336
340
337 cf = connection_info
341 cf = connection_info
338
342
339 lports = tunnel.select_random_ports(4)
343 lports = tunnel.select_random_ports(4)
340 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
344 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
341
345
342 remote_ip = cf['ip']
346 remote_ip = cf['ip']
343
347
344 if tunnel.try_passwordless_ssh(sshserver, sshkey):
348 if tunnel.try_passwordless_ssh(sshserver, sshkey):
345 password=False
349 password=False
346 else:
350 else:
347 password = getpass("SSH Password for %s: "%sshserver)
351 password = getpass("SSH Password for %s: "%sshserver)
348
352
349 for lp,rp in zip(lports, rports):
353 for lp,rp in zip(lports, rports):
350 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
354 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
351
355
352 return tuple(lports)
356 return tuple(lports)
353
357
354
358
355 #-----------------------------------------------------------------------------
359 #-----------------------------------------------------------------------------
356 # Mixin for classes that workw ith connection files
360 # Mixin for classes that work with connection files
357 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
362
363 channel_socket_types = {
364 'hb' : zmq.REQ,
365 'shell' : zmq.DEALER,
366 'iopub' : zmq.SUB,
367 'stdin' : zmq.DEALER,
368 'control': zmq.DEALER,
369 }
370
358 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
371 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
359
372
360 class ConnectionFileMixin(HasTraits):
373 class ConnectionFileMixin(HasTraits):
361 """Mixin for configurable classes that work with connection files"""
374 """Mixin for configurable classes that work with connection files"""
362
375
363 # The addresses for the communication channels
376 # The addresses for the communication channels
364 connection_file = Unicode('')
377 connection_file = Unicode('')
365 _connection_file_written = Bool(False)
378 _connection_file_written = Bool(False)
366
379
367 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
380 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
368
381
369 ip = Unicode(LOCALHOST, config=True,
382 ip = Unicode(LOCALHOST, config=True,
370 help="""Set the kernel\'s IP address [default localhost].
383 help="""Set the kernel\'s IP address [default localhost].
371 If the IP address is something other than localhost, then
384 If the IP address is something other than localhost, then
372 Consoles on other machines will be able to connect
385 Consoles on other machines will be able to connect
373 to the Kernel, so be careful!"""
386 to the Kernel, so be careful!"""
374 )
387 )
375
388
376 def _ip_default(self):
389 def _ip_default(self):
377 if self.transport == 'ipc':
390 if self.transport == 'ipc':
378 if self.connection_file:
391 if self.connection_file:
379 return os.path.splitext(self.connection_file)[0] + '-ipc'
392 return os.path.splitext(self.connection_file)[0] + '-ipc'
380 else:
393 else:
381 return 'kernel-ipc'
394 return 'kernel-ipc'
382 else:
395 else:
383 return LOCALHOST
396 return LOCALHOST
384
397
385 def _ip_changed(self, name, old, new):
398 def _ip_changed(self, name, old, new):
386 if new == '*':
399 if new == '*':
387 self.ip = '0.0.0.0'
400 self.ip = '0.0.0.0'
388
401
389 # protected traits
402 # protected traits
390
403
391 shell_port = Integer(0)
404 shell_port = Integer(0)
392 iopub_port = Integer(0)
405 iopub_port = Integer(0)
393 stdin_port = Integer(0)
406 stdin_port = Integer(0)
394 control_port = Integer(0)
407 control_port = Integer(0)
395 hb_port = Integer(0)
408 hb_port = Integer(0)
396
409
397 @property
410 @property
398 def ports(self):
411 def ports(self):
399 return [ getattr(self, name) for name in port_names ]
412 return [ getattr(self, name) for name in port_names ]
400
413
401 #--------------------------------------------------------------------------
414 #--------------------------------------------------------------------------
402 # Connection and ipc file management
415 # Connection and ipc file management
403 #--------------------------------------------------------------------------
416 #--------------------------------------------------------------------------
404
417
405 def get_connection_info(self):
418 def get_connection_info(self):
406 """return the connection info as a dict"""
419 """return the connection info as a dict"""
407 return dict(
420 return dict(
408 transport=self.transport,
421 transport=self.transport,
409 ip=self.ip,
422 ip=self.ip,
410 shell_port=self.shell_port,
423 shell_port=self.shell_port,
411 iopub_port=self.iopub_port,
424 iopub_port=self.iopub_port,
412 stdin_port=self.stdin_port,
425 stdin_port=self.stdin_port,
413 hb_port=self.hb_port,
426 hb_port=self.hb_port,
414 control_port=self.control_port,
427 control_port=self.control_port,
415 )
428 )
416
429
417 def cleanup_connection_file(self):
430 def cleanup_connection_file(self):
418 """Cleanup connection file *if we wrote it*
431 """Cleanup connection file *if we wrote it*
419
432
420 Will not raise if the connection file was already removed somehow.
433 Will not raise if the connection file was already removed somehow.
421 """
434 """
422 if self._connection_file_written:
435 if self._connection_file_written:
423 # cleanup connection files on full shutdown of kernel we started
436 # cleanup connection files on full shutdown of kernel we started
424 self._connection_file_written = False
437 self._connection_file_written = False
425 try:
438 try:
426 os.remove(self.connection_file)
439 os.remove(self.connection_file)
427 except (IOError, OSError, AttributeError):
440 except (IOError, OSError, AttributeError):
428 pass
441 pass
429
442
430 def cleanup_ipc_files(self):
443 def cleanup_ipc_files(self):
431 """Cleanup ipc files if we wrote them."""
444 """Cleanup ipc files if we wrote them."""
432 if self.transport != 'ipc':
445 if self.transport != 'ipc':
433 return
446 return
434 for port in self.ports:
447 for port in self.ports:
435 ipcfile = "%s-%i" % (self.ip, port)
448 ipcfile = "%s-%i" % (self.ip, port)
436 try:
449 try:
437 os.remove(ipcfile)
450 os.remove(ipcfile)
438 except (IOError, OSError):
451 except (IOError, OSError):
439 pass
452 pass
440
453
441 def write_connection_file(self):
454 def write_connection_file(self):
442 """Write connection info to JSON dict in self.connection_file."""
455 """Write connection info to JSON dict in self.connection_file."""
443 if self._connection_file_written:
456 if self._connection_file_written:
444 return
457 return
445
458
446 self.connection_file, cfg = write_connection_file(self.connection_file,
459 self.connection_file, cfg = write_connection_file(self.connection_file,
447 transport=self.transport, ip=self.ip, key=self.session.key,
460 transport=self.transport, ip=self.ip, key=self.session.key,
448 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
461 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
449 shell_port=self.shell_port, hb_port=self.hb_port,
462 shell_port=self.shell_port, hb_port=self.hb_port,
450 control_port=self.control_port,
463 control_port=self.control_port,
451 )
464 )
452 # write_connection_file also sets default ports:
465 # write_connection_file also sets default ports:
453 for name in port_names:
466 for name in port_names:
454 setattr(self, name, cfg[name])
467 setattr(self, name, cfg[name])
455
468
456 self._connection_file_written = True
469 self._connection_file_written = True
457
470
458 def load_connection_file(self):
471 def load_connection_file(self):
459 """Load connection info from JSON dict in self.connection_file."""
472 """Load connection info from JSON dict in self.connection_file."""
460 with open(self.connection_file) as f:
473 with open(self.connection_file) as f:
461 cfg = json.loads(f.read())
474 cfg = json.loads(f.read())
462
475
463 self.transport = cfg.get('transport', 'tcp')
476 self.transport = cfg.get('transport', 'tcp')
464 self.ip = cfg['ip']
477 self.ip = cfg['ip']
465 for name in port_names:
478 for name in port_names:
466 setattr(self, name, cfg[name])
479 setattr(self, name, cfg[name])
467 self.session.key = str_to_bytes(cfg['key'])
480 self.session.key = str_to_bytes(cfg['key'])
468
481
482 #--------------------------------------------------------------------------
483 # Creating connected sockets
484 #--------------------------------------------------------------------------
485
486 def _make_url(self, channel):
487 """Make a ZeroMQ URL for a given channel."""
488 transport = self.transport
489 ip = self.ip
490 port = getattr(self, '%s_port' % channel)
491
492 if transport == 'tcp':
493 return "tcp://%s:%i" % (ip, port)
494 else:
495 return "%s://%s-%s" % (transport, ip, port)
496
497 def _create_connected_socket(self, channel, identity=None):
498 """Create a zmq Socket and connect it to the kernel."""
499 url = self._make_url(channel)
500 socket_type = channel_socket_types[channel]
501 self.log.info("Connecting to: %s" % url)
502 sock = self.context.socket(socket_type)
503 if identity:
504 sock.identity = identity
505 sock.connect(url)
506 return sock
507
508 def connect_iopub(self, identity=None):
509 """return zmq Socket connected to the IOPub channel"""
510 sock = self._create_connected_socket('iopub', identity=identity)
511 sock.setsockopt(zmq.SUBSCRIBE, b'')
512 return sock
513
514 def connect_shell(self, identity=None):
515 """return zmq Socket connected to the Shell channel"""
516 return self._create_connected_socket('shell', identity=identity)
517
518 def connect_stdin(self, identity=None):
519 """return zmq Socket connected to the StdIn channel"""
520 return self._create_connected_socket('stdin', identity=identity)
521
522 def connect_hb(self, identity=None):
523 """return zmq Socket connected to the Heartbeat channel"""
524 return self._create_connected_socket('hb', identity=identity)
525
526 def connect_control(self, identity=None):
527 """return zmq Socket connected to the Heartbeat channel"""
528 return self._create_connected_socket('control', identity=identity)
469
529
470
530
471 __all__ = [
531 __all__ = [
472 'write_connection_file',
532 'write_connection_file',
473 'get_connection_file',
533 'get_connection_file',
474 'find_connection_file',
534 'find_connection_file',
475 'get_connection_info',
535 'get_connection_info',
476 'connect_qtconsole',
536 'connect_qtconsole',
477 'tunnel_to_kernel',
537 'tunnel_to_kernel',
478 ]
538 ]
@@ -1,435 +1,379 b''
1 """Base class to manage a running kernel
1 """Base class to manage a running kernel
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2013 The IPython Development Team
5 # Copyright (C) 2013 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import absolute_import
15 from __future__ import absolute_import
16
16
17 # Standard library imports
17 # Standard library imports
18 import signal
18 import signal
19 import sys
19 import sys
20 import time
20 import time
21
21
22 import zmq
22 import zmq
23
23
24 # Local imports
24 # Local imports
25 from IPython.config.configurable import LoggingConfigurable
25 from IPython.config.configurable import LoggingConfigurable
26 from IPython.utils.importstring import import_item
26 from IPython.utils.importstring import import_item
27 from IPython.utils.localinterfaces import LOCAL_IPS
27 from IPython.utils.localinterfaces import LOCAL_IPS
28 from IPython.utils.traitlets import (
28 from IPython.utils.traitlets import (
29 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
29 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
30 )
30 )
31 from IPython.kernel import (
31 from IPython.kernel import (
32 make_ipkernel_cmd,
32 make_ipkernel_cmd,
33 launch_kernel,
33 launch_kernel,
34 )
34 )
35 from .connect import ConnectionFileMixin
35 from .connect import ConnectionFileMixin
36 from .zmq.session import Session
36 from .zmq.session import Session
37 from .managerabc import (
37 from .managerabc import (
38 KernelManagerABC
38 KernelManagerABC
39 )
39 )
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Main kernel manager class
42 # Main kernel manager class
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 _socket_types = {
46 'hb' : zmq.REQ,
47 'shell' : zmq.DEALER,
48 'iopub' : zmq.SUB,
49 'stdin' : zmq.DEALER,
50 'control': zmq.DEALER,
51 }
52
53 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
45 class KernelManager(LoggingConfigurable, ConnectionFileMixin):
54 """Manages a single kernel in a subprocess on this host.
46 """Manages a single kernel in a subprocess on this host.
55
47
56 This version starts kernels with Popen.
48 This version starts kernels with Popen.
57 """
49 """
58
50
59 # The PyZMQ Context to use for communication with the kernel.
51 # The PyZMQ Context to use for communication with the kernel.
60 context = Instance(zmq.Context)
52 context = Instance(zmq.Context)
61 def _context_default(self):
53 def _context_default(self):
62 return zmq.Context.instance()
54 return zmq.Context.instance()
63
55
64 # The Session to use for communication with the kernel.
56 # The Session to use for communication with the kernel.
65 session = Instance(Session)
57 session = Instance(Session)
66 def _session_default(self):
58 def _session_default(self):
67 return Session(config=self.config)
59 return Session(config=self.config)
68
60
69 # the class to create with our `client` method
61 # the class to create with our `client` method
70 client_class = DottedObjectName('IPython.kernel.client.KernelClient')
62 client_class = DottedObjectName('IPython.kernel.client.KernelClient')
71 client_factory = Type()
63 client_factory = Type()
72 def _client_class_changed(self, name, old, new):
64 def _client_class_changed(self, name, old, new):
73 self.client_factory = import_item(str(new))
65 self.client_factory = import_item(str(new))
74
66
75 # The kernel process with which the KernelManager is communicating.
67 # The kernel process with which the KernelManager is communicating.
76 # generally a Popen instance
68 # generally a Popen instance
77 kernel = Any()
69 kernel = Any()
78
70
79 kernel_cmd = List(Unicode, config=True,
71 kernel_cmd = List(Unicode, config=True,
80 help="""The Popen Command to launch the kernel.
72 help="""The Popen Command to launch the kernel.
81 Override this if you have a custom
73 Override this if you have a custom
82 """
74 """
83 )
75 )
84
76
85 def _kernel_cmd_changed(self, name, old, new):
77 def _kernel_cmd_changed(self, name, old, new):
86 self.ipython_kernel = False
78 self.ipython_kernel = False
87
79
88 ipython_kernel = Bool(True)
80 ipython_kernel = Bool(True)
89
81
90 # Protected traits
82 # Protected traits
91 _launch_args = Any()
83 _launch_args = Any()
92 _control_socket = Any()
84 _control_socket = Any()
93
85
94 _restarter = Any()
86 _restarter = Any()
95
87
96 autorestart = Bool(False, config=True,
88 autorestart = Bool(False, config=True,
97 help="""Should we autorestart the kernel if it dies."""
89 help="""Should we autorestart the kernel if it dies."""
98 )
90 )
99
91
100 def __del__(self):
92 def __del__(self):
101 self._close_control_socket()
93 self._close_control_socket()
102 self.cleanup_connection_file()
94 self.cleanup_connection_file()
103
95
104 #--------------------------------------------------------------------------
96 #--------------------------------------------------------------------------
105 # Kernel restarter
97 # Kernel restarter
106 #--------------------------------------------------------------------------
98 #--------------------------------------------------------------------------
107
99
108 def start_restarter(self):
100 def start_restarter(self):
109 pass
101 pass
110
102
111 def stop_restarter(self):
103 def stop_restarter(self):
112 pass
104 pass
113
105
114 def add_restart_callback(self, callback, event='restart'):
106 def add_restart_callback(self, callback, event='restart'):
115 """register a callback to be called when a kernel is restarted"""
107 """register a callback to be called when a kernel is restarted"""
116 if self._restarter is None:
108 if self._restarter is None:
117 return
109 return
118 self._restarter.add_callback(callback, event)
110 self._restarter.add_callback(callback, event)
119
111
120 def remove_restart_callback(self, callback, event='restart'):
112 def remove_restart_callback(self, callback, event='restart'):
121 """unregister a callback to be called when a kernel is restarted"""
113 """unregister a callback to be called when a kernel is restarted"""
122 if self._restarter is None:
114 if self._restarter is None:
123 return
115 return
124 self._restarter.remove_callback(callback, event)
116 self._restarter.remove_callback(callback, event)
125
117
126 #--------------------------------------------------------------------------
118 #--------------------------------------------------------------------------
127 # create a Client connected to our Kernel
119 # create a Client connected to our Kernel
128 #--------------------------------------------------------------------------
120 #--------------------------------------------------------------------------
129
121
130 def client(self, **kwargs):
122 def client(self, **kwargs):
131 """Create a client configured to connect to our kernel"""
123 """Create a client configured to connect to our kernel"""
132 if self.client_factory is None:
124 if self.client_factory is None:
133 self.client_factory = import_item(self.client_class)
125 self.client_factory = import_item(self.client_class)
134
126
135 kw = {}
127 kw = {}
136 kw.update(self.get_connection_info())
128 kw.update(self.get_connection_info())
137 kw.update(dict(
129 kw.update(dict(
138 connection_file=self.connection_file,
130 connection_file=self.connection_file,
139 session=self.session,
131 session=self.session,
140 config=self.config,
132 config=self.config,
141 ))
133 ))
142
134
143 # add kwargs last, for manual overrides
135 # add kwargs last, for manual overrides
144 kw.update(kwargs)
136 kw.update(kwargs)
145 return self.client_factory(**kw)
137 return self.client_factory(**kw)
146
138
147 #--------------------------------------------------------------------------
139 #--------------------------------------------------------------------------
148 # Connection info
149 #--------------------------------------------------------------------------
150
151 def _make_url(self, channel):
152 """Make a ZeroMQ URL for a given channel."""
153 transport = self.transport
154 ip = self.ip
155 port = getattr(self, '%s_port' % channel)
156
157 if transport == 'tcp':
158 return "tcp://%s:%i" % (ip, port)
159 else:
160 return "%s://%s-%s" % (transport, ip, port)
161
162 def _create_connected_socket(self, channel, identity=None):
163 """Create a zmq Socket and connect it to the kernel."""
164 url = self._make_url(channel)
165 socket_type = _socket_types[channel]
166 self.log.info("Connecting to: %s" % url)
167 sock = self.context.socket(socket_type)
168 if identity:
169 sock.identity = identity
170 sock.connect(url)
171 return sock
172
173 def connect_iopub(self, identity=None):
174 """return zmq Socket connected to the IOPub channel"""
175 sock = self._create_connected_socket('iopub', identity=identity)
176 sock.setsockopt(zmq.SUBSCRIBE, b'')
177 return sock
178
179 def connect_shell(self, identity=None):
180 """return zmq Socket connected to the Shell channel"""
181 return self._create_connected_socket('shell', identity=identity)
182
183 def connect_stdin(self, identity=None):
184 """return zmq Socket connected to the StdIn channel"""
185 return self._create_connected_socket('stdin', identity=identity)
186
187 def connect_hb(self, identity=None):
188 """return zmq Socket connected to the Heartbeat channel"""
189 return self._create_connected_socket('hb', identity=identity)
190
191 def connect_control(self, identity=None):
192 """return zmq Socket connected to the Heartbeat channel"""
193 return self._create_connected_socket('control', identity=identity)
194
195 #--------------------------------------------------------------------------
196 # Kernel management
140 # Kernel management
197 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
198
142
199 def format_kernel_cmd(self, **kw):
143 def format_kernel_cmd(self, **kw):
200 """format templated args (e.g. {connection_file})"""
144 """format templated args (e.g. {connection_file})"""
201 if self.kernel_cmd:
145 if self.kernel_cmd:
202 cmd = self.kernel_cmd
146 cmd = self.kernel_cmd
203 else:
147 else:
204 cmd = make_ipkernel_cmd(
148 cmd = make_ipkernel_cmd(
205 'from IPython.kernel.zmq.kernelapp import main; main()',
149 'from IPython.kernel.zmq.kernelapp import main; main()',
206 **kw
150 **kw
207 )
151 )
208 ns = dict(connection_file=self.connection_file)
152 ns = dict(connection_file=self.connection_file)
209 ns.update(self._launch_args)
153 ns.update(self._launch_args)
210 return [ c.format(**ns) for c in cmd ]
154 return [ c.format(**ns) for c in cmd ]
211
155
212 def _launch_kernel(self, kernel_cmd, **kw):
156 def _launch_kernel(self, kernel_cmd, **kw):
213 """actually launch the kernel
157 """actually launch the kernel
214
158
215 override in a subclass to launch kernel subprocesses differently
159 override in a subclass to launch kernel subprocesses differently
216 """
160 """
217 return launch_kernel(kernel_cmd, **kw)
161 return launch_kernel(kernel_cmd, **kw)
218
162
219 # Control socket used for polite kernel shutdown
163 # Control socket used for polite kernel shutdown
220
164
221 def _connect_control_socket(self):
165 def _connect_control_socket(self):
222 if self._control_socket is None:
166 if self._control_socket is None:
223 self._control_socket = self.connect_control()
167 self._control_socket = self.connect_control()
224 self._control_socket.linger = 100
168 self._control_socket.linger = 100
225
169
226 def _close_control_socket(self):
170 def _close_control_socket(self):
227 if self._control_socket is None:
171 if self._control_socket is None:
228 return
172 return
229 self._control_socket.close()
173 self._control_socket.close()
230 self._control_socket = None
174 self._control_socket = None
231
175
232 def start_kernel(self, **kw):
176 def start_kernel(self, **kw):
233 """Starts a kernel on this host in a separate process.
177 """Starts a kernel on this host in a separate process.
234
178
235 If random ports (port=0) are being used, this method must be called
179 If random ports (port=0) are being used, this method must be called
236 before the channels are created.
180 before the channels are created.
237
181
238 Parameters:
182 Parameters:
239 -----------
183 -----------
240 **kw : optional
184 **kw : optional
241 keyword arguments that are passed down to build the kernel_cmd
185 keyword arguments that are passed down to build the kernel_cmd
242 and launching the kernel (e.g. Popen kwargs).
186 and launching the kernel (e.g. Popen kwargs).
243 """
187 """
244 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
188 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
245 raise RuntimeError("Can only launch a kernel on a local interface. "
189 raise RuntimeError("Can only launch a kernel on a local interface. "
246 "Make sure that the '*_address' attributes are "
190 "Make sure that the '*_address' attributes are "
247 "configured properly. "
191 "configured properly. "
248 "Currently valid addresses are: %s"%LOCAL_IPS
192 "Currently valid addresses are: %s"%LOCAL_IPS
249 )
193 )
250
194
251 # write connection file / get default ports
195 # write connection file / get default ports
252 self.write_connection_file()
196 self.write_connection_file()
253
197
254 # save kwargs for use in restart
198 # save kwargs for use in restart
255 self._launch_args = kw.copy()
199 self._launch_args = kw.copy()
256 # build the Popen cmd
200 # build the Popen cmd
257 kernel_cmd = self.format_kernel_cmd(**kw)
201 kernel_cmd = self.format_kernel_cmd(**kw)
258 # launch the kernel subprocess
202 # launch the kernel subprocess
259 self.kernel = self._launch_kernel(kernel_cmd,
203 self.kernel = self._launch_kernel(kernel_cmd,
260 ipython_kernel=self.ipython_kernel,
204 ipython_kernel=self.ipython_kernel,
261 **kw)
205 **kw)
262 self.start_restarter()
206 self.start_restarter()
263 self._connect_control_socket()
207 self._connect_control_socket()
264
208
265 def _send_shutdown_request(self, restart=False):
209 def _send_shutdown_request(self, restart=False):
266 """TODO: send a shutdown request via control channel"""
210 """TODO: send a shutdown request via control channel"""
267 content = dict(restart=restart)
211 content = dict(restart=restart)
268 msg = self.session.msg("shutdown_request", content=content)
212 msg = self.session.msg("shutdown_request", content=content)
269 self.session.send(self._control_socket, msg)
213 self.session.send(self._control_socket, msg)
270
214
271 def shutdown_kernel(self, now=False, restart=False):
215 def shutdown_kernel(self, now=False, restart=False):
272 """Attempts to the stop the kernel process cleanly.
216 """Attempts to the stop the kernel process cleanly.
273
217
274 This attempts to shutdown the kernels cleanly by:
218 This attempts to shutdown the kernels cleanly by:
275
219
276 1. Sending it a shutdown message over the shell channel.
220 1. Sending it a shutdown message over the shell channel.
277 2. If that fails, the kernel is shutdown forcibly by sending it
221 2. If that fails, the kernel is shutdown forcibly by sending it
278 a signal.
222 a signal.
279
223
280 Parameters:
224 Parameters:
281 -----------
225 -----------
282 now : bool
226 now : bool
283 Should the kernel be forcible killed *now*. This skips the
227 Should the kernel be forcible killed *now*. This skips the
284 first, nice shutdown attempt.
228 first, nice shutdown attempt.
285 restart: bool
229 restart: bool
286 Will this kernel be restarted after it is shutdown. When this
230 Will this kernel be restarted after it is shutdown. When this
287 is True, connection files will not be cleaned up.
231 is True, connection files will not be cleaned up.
288 """
232 """
289 # Stop monitoring for restarting while we shutdown.
233 # Stop monitoring for restarting while we shutdown.
290 self.stop_restarter()
234 self.stop_restarter()
291
235
292 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
236 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
293 if sys.platform == 'win32':
237 if sys.platform == 'win32':
294 self._kill_kernel()
238 self._kill_kernel()
295 return
239 return
296
240
297 if now:
241 if now:
298 if self.has_kernel:
242 if self.has_kernel:
299 self._kill_kernel()
243 self._kill_kernel()
300 else:
244 else:
301 # Don't send any additional kernel kill messages immediately, to give
245 # Don't send any additional kernel kill messages immediately, to give
302 # the kernel a chance to properly execute shutdown actions. Wait for at
246 # the kernel a chance to properly execute shutdown actions. Wait for at
303 # most 1s, checking every 0.1s.
247 # most 1s, checking every 0.1s.
304 self._send_shutdown_request(restart=restart)
248 self._send_shutdown_request(restart=restart)
305 for i in range(10):
249 for i in range(10):
306 if self.is_alive():
250 if self.is_alive():
307 time.sleep(0.1)
251 time.sleep(0.1)
308 else:
252 else:
309 break
253 break
310 else:
254 else:
311 # OK, we've waited long enough.
255 # OK, we've waited long enough.
312 if self.has_kernel:
256 if self.has_kernel:
313 self._kill_kernel()
257 self._kill_kernel()
314
258
315 if not restart:
259 if not restart:
316 self.cleanup_connection_file()
260 self.cleanup_connection_file()
317 self.cleanup_ipc_files()
261 self.cleanup_ipc_files()
318 else:
262 else:
319 self.cleanup_ipc_files()
263 self.cleanup_ipc_files()
320
264
321 def restart_kernel(self, now=False, **kw):
265 def restart_kernel(self, now=False, **kw):
322 """Restarts a kernel with the arguments that were used to launch it.
266 """Restarts a kernel with the arguments that were used to launch it.
323
267
324 If the old kernel was launched with random ports, the same ports will be
268 If the old kernel was launched with random ports, the same ports will be
325 used for the new kernel. The same connection file is used again.
269 used for the new kernel. The same connection file is used again.
326
270
327 Parameters
271 Parameters
328 ----------
272 ----------
329 now : bool, optional
273 now : bool, optional
330 If True, the kernel is forcefully restarted *immediately*, without
274 If True, the kernel is forcefully restarted *immediately*, without
331 having a chance to do any cleanup action. Otherwise the kernel is
275 having a chance to do any cleanup action. Otherwise the kernel is
332 given 1s to clean up before a forceful restart is issued.
276 given 1s to clean up before a forceful restart is issued.
333
277
334 In all cases the kernel is restarted, the only difference is whether
278 In all cases the kernel is restarted, the only difference is whether
335 it is given a chance to perform a clean shutdown or not.
279 it is given a chance to perform a clean shutdown or not.
336
280
337 **kw : optional
281 **kw : optional
338 Any options specified here will overwrite those used to launch the
282 Any options specified here will overwrite those used to launch the
339 kernel.
283 kernel.
340 """
284 """
341 if self._launch_args is None:
285 if self._launch_args is None:
342 raise RuntimeError("Cannot restart the kernel. "
286 raise RuntimeError("Cannot restart the kernel. "
343 "No previous call to 'start_kernel'.")
287 "No previous call to 'start_kernel'.")
344 else:
288 else:
345 # Stop currently running kernel.
289 # Stop currently running kernel.
346 self.shutdown_kernel(now=now, restart=True)
290 self.shutdown_kernel(now=now, restart=True)
347
291
348 # Start new kernel.
292 # Start new kernel.
349 self._launch_args.update(kw)
293 self._launch_args.update(kw)
350 self.start_kernel(**self._launch_args)
294 self.start_kernel(**self._launch_args)
351
295
352 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
296 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
353 # unless there is some delay here.
297 # unless there is some delay here.
354 if sys.platform == 'win32':
298 if sys.platform == 'win32':
355 time.sleep(0.2)
299 time.sleep(0.2)
356
300
357 @property
301 @property
358 def has_kernel(self):
302 def has_kernel(self):
359 """Has a kernel been started that we are managing."""
303 """Has a kernel been started that we are managing."""
360 return self.kernel is not None
304 return self.kernel is not None
361
305
362 def _kill_kernel(self):
306 def _kill_kernel(self):
363 """Kill the running kernel.
307 """Kill the running kernel.
364
308
365 This is a private method, callers should use shutdown_kernel(now=True).
309 This is a private method, callers should use shutdown_kernel(now=True).
366 """
310 """
367 if self.has_kernel:
311 if self.has_kernel:
368
312
369 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
313 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
370 # TerminateProcess() on Win32).
314 # TerminateProcess() on Win32).
371 try:
315 try:
372 self.kernel.kill()
316 self.kernel.kill()
373 except OSError as e:
317 except OSError as e:
374 # In Windows, we will get an Access Denied error if the process
318 # In Windows, we will get an Access Denied error if the process
375 # has already terminated. Ignore it.
319 # has already terminated. Ignore it.
376 if sys.platform == 'win32':
320 if sys.platform == 'win32':
377 if e.winerror != 5:
321 if e.winerror != 5:
378 raise
322 raise
379 # On Unix, we may get an ESRCH error if the process has already
323 # On Unix, we may get an ESRCH error if the process has already
380 # terminated. Ignore it.
324 # terminated. Ignore it.
381 else:
325 else:
382 from errno import ESRCH
326 from errno import ESRCH
383 if e.errno != ESRCH:
327 if e.errno != ESRCH:
384 raise
328 raise
385
329
386 # Block until the kernel terminates.
330 # Block until the kernel terminates.
387 self.kernel.wait()
331 self.kernel.wait()
388 self.kernel = None
332 self.kernel = None
389 else:
333 else:
390 raise RuntimeError("Cannot kill kernel. No kernel is running!")
334 raise RuntimeError("Cannot kill kernel. No kernel is running!")
391
335
392 def interrupt_kernel(self):
336 def interrupt_kernel(self):
393 """Interrupts the kernel by sending it a signal.
337 """Interrupts the kernel by sending it a signal.
394
338
395 Unlike ``signal_kernel``, this operation is well supported on all
339 Unlike ``signal_kernel``, this operation is well supported on all
396 platforms.
340 platforms.
397 """
341 """
398 if self.has_kernel:
342 if self.has_kernel:
399 if sys.platform == 'win32':
343 if sys.platform == 'win32':
400 from .zmq.parentpoller import ParentPollerWindows as Poller
344 from .zmq.parentpoller import ParentPollerWindows as Poller
401 Poller.send_interrupt(self.kernel.win32_interrupt_event)
345 Poller.send_interrupt(self.kernel.win32_interrupt_event)
402 else:
346 else:
403 self.kernel.send_signal(signal.SIGINT)
347 self.kernel.send_signal(signal.SIGINT)
404 else:
348 else:
405 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
349 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
406
350
407 def signal_kernel(self, signum):
351 def signal_kernel(self, signum):
408 """Sends a signal to the kernel.
352 """Sends a signal to the kernel.
409
353
410 Note that since only SIGTERM is supported on Windows, this function is
354 Note that since only SIGTERM is supported on Windows, this function is
411 only useful on Unix systems.
355 only useful on Unix systems.
412 """
356 """
413 if self.has_kernel:
357 if self.has_kernel:
414 self.kernel.send_signal(signum)
358 self.kernel.send_signal(signum)
415 else:
359 else:
416 raise RuntimeError("Cannot signal kernel. No kernel is running!")
360 raise RuntimeError("Cannot signal kernel. No kernel is running!")
417
361
418 def is_alive(self):
362 def is_alive(self):
419 """Is the kernel process still running?"""
363 """Is the kernel process still running?"""
420 if self.has_kernel:
364 if self.has_kernel:
421 if self.kernel.poll() is None:
365 if self.kernel.poll() is None:
422 return True
366 return True
423 else:
367 else:
424 return False
368 return False
425 else:
369 else:
426 # we don't have a kernel
370 # we don't have a kernel
427 return False
371 return False
428
372
429
373
430 #-----------------------------------------------------------------------------
374 #-----------------------------------------------------------------------------
431 # ABC Registration
375 # ABC Registration
432 #-----------------------------------------------------------------------------
376 #-----------------------------------------------------------------------------
433
377
434 KernelManagerABC.register(KernelManager)
378 KernelManagerABC.register(KernelManager)
435
379
General Comments 0
You need to be logged in to leave comments. Login now