##// END OF EJS Templates
Merge pull request #8538 from sharelatex/fix-cleanup-ipc-files-for-zmq...
Min RK -
r21482:9ac7f9c7 merge
parent child Browse files
Show More
@@ -1,576 +1,581 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 The :class:`ConnectionFileMixin` class in this module encapsulates the logic
3 The :class:`ConnectionFileMixin` class in this module encapsulates the logic
4 related to writing and reading connections files.
4 related to writing and reading connections files.
5 """
5 """
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 from __future__ import absolute_import
13 from __future__ import absolute_import
14
14
15 import glob
15 import glob
16 import json
16 import json
17 import os
17 import os
18 import socket
18 import socket
19 import sys
19 import sys
20 from getpass import getpass
20 from getpass import getpass
21 from subprocess import Popen, PIPE
21 from subprocess import Popen, PIPE
22 import tempfile
22 import tempfile
23
23
24 import zmq
24 import zmq
25
25
26 # IPython imports
26 # IPython imports
27 from IPython.config import LoggingConfigurable
27 from IPython.config import LoggingConfigurable
28 from IPython.core.profiledir import ProfileDir
28 from IPython.core.profiledir import ProfileDir
29 from IPython.utils.localinterfaces import localhost
29 from IPython.utils.localinterfaces import localhost
30 from IPython.utils.path import filefind, get_ipython_dir
30 from IPython.utils.path import filefind, get_ipython_dir
31 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
31 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
32 string_types)
32 string_types)
33 from IPython.utils.traitlets import (
33 from IPython.utils.traitlets import (
34 Bool, Integer, Unicode, CaselessStrEnum, Instance,
34 Bool, Integer, Unicode, CaselessStrEnum, Instance,
35 )
35 )
36
36
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Working with Connection Files
39 # Working with Connection Files
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
42 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
43 control_port=0, ip='', key=b'', transport='tcp',
43 control_port=0, ip='', key=b'', transport='tcp',
44 signature_scheme='hmac-sha256',
44 signature_scheme='hmac-sha256',
45 ):
45 ):
46 """Generates a JSON config file, including the selection of random ports.
46 """Generates a JSON config file, including the selection of random ports.
47
47
48 Parameters
48 Parameters
49 ----------
49 ----------
50
50
51 fname : unicode
51 fname : unicode
52 The path to the file to write
52 The path to the file to write
53
53
54 shell_port : int, optional
54 shell_port : int, optional
55 The port to use for ROUTER (shell) channel.
55 The port to use for ROUTER (shell) channel.
56
56
57 iopub_port : int, optional
57 iopub_port : int, optional
58 The port to use for the SUB channel.
58 The port to use for the SUB channel.
59
59
60 stdin_port : int, optional
60 stdin_port : int, optional
61 The port to use for the ROUTER (raw input) channel.
61 The port to use for the ROUTER (raw input) channel.
62
62
63 control_port : int, optional
63 control_port : int, optional
64 The port to use for the ROUTER (control) channel.
64 The port to use for the ROUTER (control) channel.
65
65
66 hb_port : int, optional
66 hb_port : int, optional
67 The port to use for the heartbeat REP channel.
67 The port to use for the heartbeat REP channel.
68
68
69 ip : str, optional
69 ip : str, optional
70 The ip address the kernel will bind to.
70 The ip address the kernel will bind to.
71
71
72 key : str, optional
72 key : str, optional
73 The Session key used for message authentication.
73 The Session key used for message authentication.
74
74
75 signature_scheme : str, optional
75 signature_scheme : str, optional
76 The scheme used for message authentication.
76 The scheme used for message authentication.
77 This has the form 'digest-hash', where 'digest'
77 This has the form 'digest-hash', where 'digest'
78 is the scheme used for digests, and 'hash' is the name of the hash function
78 is the scheme used for digests, and 'hash' is the name of the hash function
79 used by the digest scheme.
79 used by the digest scheme.
80 Currently, 'hmac' is the only supported digest scheme,
80 Currently, 'hmac' is the only supported digest scheme,
81 and 'sha256' is the default hash function.
81 and 'sha256' is the default hash function.
82
82
83 """
83 """
84 if not ip:
84 if not ip:
85 ip = localhost()
85 ip = localhost()
86 # default to temporary connector file
86 # default to temporary connector file
87 if not fname:
87 if not fname:
88 fd, fname = tempfile.mkstemp('.json')
88 fd, fname = tempfile.mkstemp('.json')
89 os.close(fd)
89 os.close(fd)
90
90
91 # Find open ports as necessary.
91 # Find open ports as necessary.
92
92
93 ports = []
93 ports = []
94 ports_needed = int(shell_port <= 0) + \
94 ports_needed = int(shell_port <= 0) + \
95 int(iopub_port <= 0) + \
95 int(iopub_port <= 0) + \
96 int(stdin_port <= 0) + \
96 int(stdin_port <= 0) + \
97 int(control_port <= 0) + \
97 int(control_port <= 0) + \
98 int(hb_port <= 0)
98 int(hb_port <= 0)
99 if transport == 'tcp':
99 if transport == 'tcp':
100 for i in range(ports_needed):
100 for i in range(ports_needed):
101 sock = socket.socket()
101 sock = socket.socket()
102 # struct.pack('ii', (0,0)) is 8 null bytes
102 # struct.pack('ii', (0,0)) is 8 null bytes
103 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
103 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
104 sock.bind(('', 0))
104 sock.bind(('', 0))
105 ports.append(sock)
105 ports.append(sock)
106 for i, sock in enumerate(ports):
106 for i, sock in enumerate(ports):
107 port = sock.getsockname()[1]
107 port = sock.getsockname()[1]
108 sock.close()
108 sock.close()
109 ports[i] = port
109 ports[i] = port
110 else:
110 else:
111 N = 1
111 N = 1
112 for i in range(ports_needed):
112 for i in range(ports_needed):
113 while os.path.exists("%s-%s" % (ip, str(N))):
113 while os.path.exists("%s-%s" % (ip, str(N))):
114 N += 1
114 N += 1
115 ports.append(N)
115 ports.append(N)
116 N += 1
116 N += 1
117 if shell_port <= 0:
117 if shell_port <= 0:
118 shell_port = ports.pop(0)
118 shell_port = ports.pop(0)
119 if iopub_port <= 0:
119 if iopub_port <= 0:
120 iopub_port = ports.pop(0)
120 iopub_port = ports.pop(0)
121 if stdin_port <= 0:
121 if stdin_port <= 0:
122 stdin_port = ports.pop(0)
122 stdin_port = ports.pop(0)
123 if control_port <= 0:
123 if control_port <= 0:
124 control_port = ports.pop(0)
124 control_port = ports.pop(0)
125 if hb_port <= 0:
125 if hb_port <= 0:
126 hb_port = ports.pop(0)
126 hb_port = ports.pop(0)
127
127
128 cfg = dict( shell_port=shell_port,
128 cfg = dict( shell_port=shell_port,
129 iopub_port=iopub_port,
129 iopub_port=iopub_port,
130 stdin_port=stdin_port,
130 stdin_port=stdin_port,
131 control_port=control_port,
131 control_port=control_port,
132 hb_port=hb_port,
132 hb_port=hb_port,
133 )
133 )
134 cfg['ip'] = ip
134 cfg['ip'] = ip
135 cfg['key'] = bytes_to_str(key)
135 cfg['key'] = bytes_to_str(key)
136 cfg['transport'] = transport
136 cfg['transport'] = transport
137 cfg['signature_scheme'] = signature_scheme
137 cfg['signature_scheme'] = signature_scheme
138
138
139 with open(fname, 'w') as f:
139 with open(fname, 'w') as f:
140 f.write(json.dumps(cfg, indent=2))
140 f.write(json.dumps(cfg, indent=2))
141
141
142 return fname, cfg
142 return fname, cfg
143
143
144
144
145 def get_connection_file(app=None):
145 def get_connection_file(app=None):
146 """Return the path to the connection file of an app
146 """Return the path to the connection file of an app
147
147
148 Parameters
148 Parameters
149 ----------
149 ----------
150 app : IPKernelApp instance [optional]
150 app : IPKernelApp instance [optional]
151 If unspecified, the currently running app will be used
151 If unspecified, the currently running app will be used
152 """
152 """
153 if app is None:
153 if app is None:
154 from IPython.kernel.zmq.kernelapp import IPKernelApp
154 from IPython.kernel.zmq.kernelapp import IPKernelApp
155 if not IPKernelApp.initialized():
155 if not IPKernelApp.initialized():
156 raise RuntimeError("app not specified, and not in a running Kernel")
156 raise RuntimeError("app not specified, and not in a running Kernel")
157
157
158 app = IPKernelApp.instance()
158 app = IPKernelApp.instance()
159 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
159 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
160
160
161
161
162 def find_connection_file(filename='kernel-*.json', profile=None):
162 def find_connection_file(filename='kernel-*.json', profile=None):
163 """find a connection file, and return its absolute path.
163 """find a connection file, and return its absolute path.
164
164
165 The current working directory and the profile's security
165 The current working directory and the profile's security
166 directory will be searched for the file if it is not given by
166 directory will be searched for the file if it is not given by
167 absolute path.
167 absolute path.
168
168
169 If profile is unspecified, then the current running application's
169 If profile is unspecified, then the current running application's
170 profile will be used, or 'default', if not run from IPython.
170 profile will be used, or 'default', if not run from IPython.
171
171
172 If the argument does not match an existing file, it will be interpreted as a
172 If the argument does not match an existing file, it will be interpreted as a
173 fileglob, and the matching file in the profile's security dir with
173 fileglob, and the matching file in the profile's security dir with
174 the latest access time will be used.
174 the latest access time will be used.
175
175
176 Parameters
176 Parameters
177 ----------
177 ----------
178 filename : str
178 filename : str
179 The connection file or fileglob to search for.
179 The connection file or fileglob to search for.
180 profile : str [optional]
180 profile : str [optional]
181 The name of the profile to use when searching for the connection file,
181 The name of the profile to use when searching for the connection file,
182 if different from the current IPython session or 'default'.
182 if different from the current IPython session or 'default'.
183
183
184 Returns
184 Returns
185 -------
185 -------
186 str : The absolute path of the connection file.
186 str : The absolute path of the connection file.
187 """
187 """
188 from IPython.core.application import BaseIPythonApplication as IPApp
188 from IPython.core.application import BaseIPythonApplication as IPApp
189 try:
189 try:
190 # quick check for absolute path, before going through logic
190 # quick check for absolute path, before going through logic
191 return filefind(filename)
191 return filefind(filename)
192 except IOError:
192 except IOError:
193 pass
193 pass
194
194
195 if profile is None:
195 if profile is None:
196 # profile unspecified, check if running from an IPython app
196 # profile unspecified, check if running from an IPython app
197 if IPApp.initialized():
197 if IPApp.initialized():
198 app = IPApp.instance()
198 app = IPApp.instance()
199 profile_dir = app.profile_dir
199 profile_dir = app.profile_dir
200 else:
200 else:
201 # not running in IPython, use default profile
201 # not running in IPython, use default profile
202 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
202 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
203 else:
203 else:
204 # find profiledir by profile name:
204 # find profiledir by profile name:
205 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
205 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
206 security_dir = profile_dir.security_dir
206 security_dir = profile_dir.security_dir
207
207
208 try:
208 try:
209 # first, try explicit name
209 # first, try explicit name
210 return filefind(filename, ['.', security_dir])
210 return filefind(filename, ['.', security_dir])
211 except IOError:
211 except IOError:
212 pass
212 pass
213
213
214 # not found by full name
214 # not found by full name
215
215
216 if '*' in filename:
216 if '*' in filename:
217 # given as a glob already
217 # given as a glob already
218 pat = filename
218 pat = filename
219 else:
219 else:
220 # accept any substring match
220 # accept any substring match
221 pat = '*%s*' % filename
221 pat = '*%s*' % filename
222 matches = glob.glob( os.path.join(security_dir, pat) )
222 matches = glob.glob( os.path.join(security_dir, pat) )
223 if not matches:
223 if not matches:
224 raise IOError("Could not find %r in %r" % (filename, security_dir))
224 raise IOError("Could not find %r in %r" % (filename, security_dir))
225 elif len(matches) == 1:
225 elif len(matches) == 1:
226 return matches[0]
226 return matches[0]
227 else:
227 else:
228 # get most recent match, by access time:
228 # get most recent match, by access time:
229 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
229 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
230
230
231
231
232 def get_connection_info(connection_file=None, unpack=False, profile=None):
232 def get_connection_info(connection_file=None, unpack=False, profile=None):
233 """Return the connection information for the current Kernel.
233 """Return the connection information for the current Kernel.
234
234
235 Parameters
235 Parameters
236 ----------
236 ----------
237 connection_file : str [optional]
237 connection_file : str [optional]
238 The connection file to be used. Can be given by absolute path, or
238 The connection file to be used. Can be given by absolute path, or
239 IPython will search in the security directory of a given profile.
239 IPython will search in the security directory of a given profile.
240 If run from IPython,
240 If run from IPython,
241
241
242 If unspecified, the connection file for the currently running
242 If unspecified, the connection file for the currently running
243 IPython Kernel will be used, which is only allowed from inside a kernel.
243 IPython Kernel will be used, which is only allowed from inside a kernel.
244 unpack : bool [default: False]
244 unpack : bool [default: False]
245 if True, return the unpacked dict, otherwise just the string contents
245 if True, return the unpacked dict, otherwise just the string contents
246 of the file.
246 of the file.
247 profile : str [optional]
247 profile : str [optional]
248 The name of the profile to use when searching for the connection file,
248 The name of the profile to use when searching for the connection file,
249 if different from the current IPython session or 'default'.
249 if different from the current IPython session or 'default'.
250
250
251
251
252 Returns
252 Returns
253 -------
253 -------
254 The connection dictionary of the current kernel, as string or dict,
254 The connection dictionary of the current kernel, as string or dict,
255 depending on `unpack`.
255 depending on `unpack`.
256 """
256 """
257 if connection_file is None:
257 if connection_file is None:
258 # get connection file from current kernel
258 # get connection file from current kernel
259 cf = get_connection_file()
259 cf = get_connection_file()
260 else:
260 else:
261 # connection file specified, allow shortnames:
261 # connection file specified, allow shortnames:
262 cf = find_connection_file(connection_file, profile=profile)
262 cf = find_connection_file(connection_file, profile=profile)
263
263
264 with open(cf) as f:
264 with open(cf) as f:
265 info = f.read()
265 info = f.read()
266
266
267 if unpack:
267 if unpack:
268 info = json.loads(info)
268 info = json.loads(info)
269 # ensure key is bytes:
269 # ensure key is bytes:
270 info['key'] = str_to_bytes(info.get('key', ''))
270 info['key'] = str_to_bytes(info.get('key', ''))
271 return info
271 return info
272
272
273
273
274 def connect_qtconsole(connection_file=None, argv=None, profile=None):
274 def connect_qtconsole(connection_file=None, argv=None, profile=None):
275 """Connect a qtconsole to the current kernel.
275 """Connect a qtconsole to the current kernel.
276
276
277 This is useful for connecting a second qtconsole to a kernel, or to a
277 This is useful for connecting a second qtconsole to a kernel, or to a
278 local notebook.
278 local notebook.
279
279
280 Parameters
280 Parameters
281 ----------
281 ----------
282 connection_file : str [optional]
282 connection_file : str [optional]
283 The connection file to be used. Can be given by absolute path, or
283 The connection file to be used. Can be given by absolute path, or
284 IPython will search in the security directory of a given profile.
284 IPython will search in the security directory of a given profile.
285 If run from IPython,
285 If run from IPython,
286
286
287 If unspecified, the connection file for the currently running
287 If unspecified, the connection file for the currently running
288 IPython Kernel will be used, which is only allowed from inside a kernel.
288 IPython Kernel will be used, which is only allowed from inside a kernel.
289 argv : list [optional]
289 argv : list [optional]
290 Any extra args to be passed to the console.
290 Any extra args to be passed to the console.
291 profile : str [optional]
291 profile : str [optional]
292 The name of the profile to use when searching for the connection file,
292 The name of the profile to use when searching for the connection file,
293 if different from the current IPython session or 'default'.
293 if different from the current IPython session or 'default'.
294
294
295
295
296 Returns
296 Returns
297 -------
297 -------
298 :class:`subprocess.Popen` instance running the qtconsole frontend
298 :class:`subprocess.Popen` instance running the qtconsole frontend
299 """
299 """
300 argv = [] if argv is None else argv
300 argv = [] if argv is None else argv
301
301
302 if connection_file is None:
302 if connection_file is None:
303 # get connection file from current kernel
303 # get connection file from current kernel
304 cf = get_connection_file()
304 cf = get_connection_file()
305 else:
305 else:
306 cf = find_connection_file(connection_file, profile=profile)
306 cf = find_connection_file(connection_file, profile=profile)
307
307
308 cmd = ';'.join([
308 cmd = ';'.join([
309 "from IPython.qt.console import qtconsoleapp",
309 "from IPython.qt.console import qtconsoleapp",
310 "qtconsoleapp.main()"
310 "qtconsoleapp.main()"
311 ])
311 ])
312
312
313 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
313 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
314 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
314 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
315 )
315 )
316
316
317
317
318 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
318 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
319 """tunnel connections to a kernel via ssh
319 """tunnel connections to a kernel via ssh
320
320
321 This will open four SSH tunnels from localhost on this machine to the
321 This will open four SSH tunnels from localhost on this machine to the
322 ports associated with the kernel. They can be either direct
322 ports associated with the kernel. They can be either direct
323 localhost-localhost tunnels, or if an intermediate server is necessary,
323 localhost-localhost tunnels, or if an intermediate server is necessary,
324 the kernel must be listening on a public IP.
324 the kernel must be listening on a public IP.
325
325
326 Parameters
326 Parameters
327 ----------
327 ----------
328 connection_info : dict or str (path)
328 connection_info : dict or str (path)
329 Either a connection dict, or the path to a JSON connection file
329 Either a connection dict, or the path to a JSON connection file
330 sshserver : str
330 sshserver : str
331 The ssh sever to use to tunnel to the kernel. Can be a full
331 The ssh sever to use to tunnel to the kernel. Can be a full
332 `user@server:port` string. ssh config aliases are respected.
332 `user@server:port` string. ssh config aliases are respected.
333 sshkey : str [optional]
333 sshkey : str [optional]
334 Path to file containing ssh key to use for authentication.
334 Path to file containing ssh key to use for authentication.
335 Only necessary if your ssh config does not already associate
335 Only necessary if your ssh config does not already associate
336 a keyfile with the host.
336 a keyfile with the host.
337
337
338 Returns
338 Returns
339 -------
339 -------
340
340
341 (shell, iopub, stdin, hb) : ints
341 (shell, iopub, stdin, hb) : ints
342 The four ports on localhost that have been forwarded to the kernel.
342 The four ports on localhost that have been forwarded to the kernel.
343 """
343 """
344 from zmq.ssh import tunnel
344 from zmq.ssh import tunnel
345 if isinstance(connection_info, string_types):
345 if isinstance(connection_info, string_types):
346 # it's a path, unpack it
346 # it's a path, unpack it
347 with open(connection_info) as f:
347 with open(connection_info) as f:
348 connection_info = json.loads(f.read())
348 connection_info = json.loads(f.read())
349
349
350 cf = connection_info
350 cf = connection_info
351
351
352 lports = tunnel.select_random_ports(4)
352 lports = tunnel.select_random_ports(4)
353 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
353 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
354
354
355 remote_ip = cf['ip']
355 remote_ip = cf['ip']
356
356
357 if tunnel.try_passwordless_ssh(sshserver, sshkey):
357 if tunnel.try_passwordless_ssh(sshserver, sshkey):
358 password=False
358 password=False
359 else:
359 else:
360 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
360 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
361
361
362 for lp,rp in zip(lports, rports):
362 for lp,rp in zip(lports, rports):
363 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
363 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
364
364
365 return tuple(lports)
365 return tuple(lports)
366
366
367
367
368 #-----------------------------------------------------------------------------
368 #-----------------------------------------------------------------------------
369 # Mixin for classes that work with connection files
369 # Mixin for classes that work with connection files
370 #-----------------------------------------------------------------------------
370 #-----------------------------------------------------------------------------
371
371
372 channel_socket_types = {
372 channel_socket_types = {
373 'hb' : zmq.REQ,
373 'hb' : zmq.REQ,
374 'shell' : zmq.DEALER,
374 'shell' : zmq.DEALER,
375 'iopub' : zmq.SUB,
375 'iopub' : zmq.SUB,
376 'stdin' : zmq.DEALER,
376 'stdin' : zmq.DEALER,
377 'control': zmq.DEALER,
377 'control': zmq.DEALER,
378 }
378 }
379
379
380 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
380 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
381
381
382 class ConnectionFileMixin(LoggingConfigurable):
382 class ConnectionFileMixin(LoggingConfigurable):
383 """Mixin for configurable classes that work with connection files"""
383 """Mixin for configurable classes that work with connection files"""
384
384
385 # The addresses for the communication channels
385 # The addresses for the communication channels
386 connection_file = Unicode('', config=True,
386 connection_file = Unicode('', config=True,
387 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
387 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
388
388
389 This file will contain the IP, ports, and authentication key needed to connect
389 This file will contain the IP, ports, and authentication key needed to connect
390 clients to this kernel. By default, this file will be created in the security dir
390 clients to this kernel. By default, this file will be created in the security dir
391 of the current profile, but can be specified by absolute path.
391 of the current profile, but can be specified by absolute path.
392 """)
392 """)
393 _connection_file_written = Bool(False)
393 _connection_file_written = Bool(False)
394
394
395 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
395 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
396
396
397 ip = Unicode(config=True,
397 ip = Unicode(config=True,
398 help="""Set the kernel\'s IP address [default localhost].
398 help="""Set the kernel\'s IP address [default localhost].
399 If the IP address is something other than localhost, then
399 If the IP address is something other than localhost, then
400 Consoles on other machines will be able to connect
400 Consoles on other machines will be able to connect
401 to the Kernel, so be careful!"""
401 to the Kernel, so be careful!"""
402 )
402 )
403
403
404 def _ip_default(self):
404 def _ip_default(self):
405 if self.transport == 'ipc':
405 if self.transport == 'ipc':
406 if self.connection_file:
406 if self.connection_file:
407 return os.path.splitext(self.connection_file)[0] + '-ipc'
407 return os.path.splitext(self.connection_file)[0] + '-ipc'
408 else:
408 else:
409 return 'kernel-ipc'
409 return 'kernel-ipc'
410 else:
410 else:
411 return localhost()
411 return localhost()
412
412
413 def _ip_changed(self, name, old, new):
413 def _ip_changed(self, name, old, new):
414 if new == '*':
414 if new == '*':
415 self.ip = '0.0.0.0'
415 self.ip = '0.0.0.0'
416
416
417 # protected traits
417 # protected traits
418
418
419 hb_port = Integer(0, config=True,
419 hb_port = Integer(0, config=True,
420 help="set the heartbeat port [default: random]")
420 help="set the heartbeat port [default: random]")
421 shell_port = Integer(0, config=True,
421 shell_port = Integer(0, config=True,
422 help="set the shell (ROUTER) port [default: random]")
422 help="set the shell (ROUTER) port [default: random]")
423 iopub_port = Integer(0, config=True,
423 iopub_port = Integer(0, config=True,
424 help="set the iopub (PUB) port [default: random]")
424 help="set the iopub (PUB) port [default: random]")
425 stdin_port = Integer(0, config=True,
425 stdin_port = Integer(0, config=True,
426 help="set the stdin (ROUTER) port [default: random]")
426 help="set the stdin (ROUTER) port [default: random]")
427 control_port = Integer(0, config=True,
427 control_port = Integer(0, config=True,
428 help="set the control (ROUTER) port [default: random]")
428 help="set the control (ROUTER) port [default: random]")
429
429
430 @property
430 @property
431 def ports(self):
431 def ports(self):
432 return [ getattr(self, name) for name in port_names ]
432 return [ getattr(self, name) for name in port_names ]
433
433
434 # The Session to use for communication with the kernel.
434 # The Session to use for communication with the kernel.
435 session = Instance('IPython.kernel.zmq.session.Session')
435 session = Instance('IPython.kernel.zmq.session.Session')
436 def _session_default(self):
436 def _session_default(self):
437 from IPython.kernel.zmq.session import Session
437 from IPython.kernel.zmq.session import Session
438 return Session(parent=self)
438 return Session(parent=self)
439
439
440 #--------------------------------------------------------------------------
440 #--------------------------------------------------------------------------
441 # Connection and ipc file management
441 # Connection and ipc file management
442 #--------------------------------------------------------------------------
442 #--------------------------------------------------------------------------
443
443
444 def get_connection_info(self):
444 def get_connection_info(self):
445 """return the connection info as a dict"""
445 """return the connection info as a dict"""
446 return dict(
446 return dict(
447 transport=self.transport,
447 transport=self.transport,
448 ip=self.ip,
448 ip=self.ip,
449 shell_port=self.shell_port,
449 shell_port=self.shell_port,
450 iopub_port=self.iopub_port,
450 iopub_port=self.iopub_port,
451 stdin_port=self.stdin_port,
451 stdin_port=self.stdin_port,
452 hb_port=self.hb_port,
452 hb_port=self.hb_port,
453 control_port=self.control_port,
453 control_port=self.control_port,
454 signature_scheme=self.session.signature_scheme,
454 signature_scheme=self.session.signature_scheme,
455 key=self.session.key,
455 key=self.session.key,
456 )
456 )
457
457
458 def cleanup_connection_file(self):
458 def cleanup_connection_file(self):
459 """Cleanup connection file *if we wrote it*
459 """Cleanup connection file *if we wrote it*
460
460
461 Will not raise if the connection file was already removed somehow.
461 Will not raise if the connection file was already removed somehow.
462 """
462 """
463 if self._connection_file_written:
463 if self._connection_file_written:
464 # cleanup connection files on full shutdown of kernel we started
464 # cleanup connection files on full shutdown of kernel we started
465 self._connection_file_written = False
465 self._connection_file_written = False
466 try:
466 try:
467 os.remove(self.connection_file)
467 os.remove(self.connection_file)
468 except (IOError, OSError, AttributeError):
468 except (IOError, OSError, AttributeError):
469 pass
469 pass
470
470
471 def cleanup_ipc_files(self):
471 def cleanup_ipc_files(self):
472 """Cleanup ipc files if we wrote them."""
472 """Cleanup ipc files if we wrote them."""
473 if self.transport != 'ipc':
473 if self.transport != 'ipc':
474 return
474 return
475 for port in self.ports:
475 # handle self.ports as a list [port1, ...] or dictionary
476 # {label1: port1, ...}
477 ports = self.ports
478 if isinstance(ports, dict):
479 ports = ports.values()
480 for port in ports:
476 ipcfile = "%s-%i" % (self.ip, port)
481 ipcfile = "%s-%i" % (self.ip, port)
477 try:
482 try:
478 os.remove(ipcfile)
483 os.remove(ipcfile)
479 except (IOError, OSError):
484 except (IOError, OSError):
480 pass
485 pass
481
486
482 def write_connection_file(self):
487 def write_connection_file(self):
483 """Write connection info to JSON dict in self.connection_file."""
488 """Write connection info to JSON dict in self.connection_file."""
484 if self._connection_file_written and os.path.exists(self.connection_file):
489 if self._connection_file_written and os.path.exists(self.connection_file):
485 return
490 return
486
491
487 self.connection_file, cfg = write_connection_file(self.connection_file,
492 self.connection_file, cfg = write_connection_file(self.connection_file,
488 transport=self.transport, ip=self.ip, key=self.session.key,
493 transport=self.transport, ip=self.ip, key=self.session.key,
489 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
494 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
490 shell_port=self.shell_port, hb_port=self.hb_port,
495 shell_port=self.shell_port, hb_port=self.hb_port,
491 control_port=self.control_port,
496 control_port=self.control_port,
492 signature_scheme=self.session.signature_scheme,
497 signature_scheme=self.session.signature_scheme,
493 )
498 )
494 # write_connection_file also sets default ports:
499 # write_connection_file also sets default ports:
495 for name in port_names:
500 for name in port_names:
496 setattr(self, name, cfg[name])
501 setattr(self, name, cfg[name])
497
502
498 self._connection_file_written = True
503 self._connection_file_written = True
499
504
500 def load_connection_file(self):
505 def load_connection_file(self):
501 """Load connection info from JSON dict in self.connection_file."""
506 """Load connection info from JSON dict in self.connection_file."""
502 self.log.debug(u"Loading connection file %s", self.connection_file)
507 self.log.debug(u"Loading connection file %s", self.connection_file)
503 with open(self.connection_file) as f:
508 with open(self.connection_file) as f:
504 cfg = json.load(f)
509 cfg = json.load(f)
505 self.transport = cfg.get('transport', self.transport)
510 self.transport = cfg.get('transport', self.transport)
506 self.ip = cfg.get('ip', self._ip_default())
511 self.ip = cfg.get('ip', self._ip_default())
507
512
508 for name in port_names:
513 for name in port_names:
509 if getattr(self, name) == 0 and name in cfg:
514 if getattr(self, name) == 0 and name in cfg:
510 # not overridden by config or cl_args
515 # not overridden by config or cl_args
511 setattr(self, name, cfg[name])
516 setattr(self, name, cfg[name])
512
517
513 if 'key' in cfg:
518 if 'key' in cfg:
514 self.session.key = str_to_bytes(cfg['key'])
519 self.session.key = str_to_bytes(cfg['key'])
515 if 'signature_scheme' in cfg:
520 if 'signature_scheme' in cfg:
516 self.session.signature_scheme = cfg['signature_scheme']
521 self.session.signature_scheme = cfg['signature_scheme']
517
522
518 #--------------------------------------------------------------------------
523 #--------------------------------------------------------------------------
519 # Creating connected sockets
524 # Creating connected sockets
520 #--------------------------------------------------------------------------
525 #--------------------------------------------------------------------------
521
526
522 def _make_url(self, channel):
527 def _make_url(self, channel):
523 """Make a ZeroMQ URL for a given channel."""
528 """Make a ZeroMQ URL for a given channel."""
524 transport = self.transport
529 transport = self.transport
525 ip = self.ip
530 ip = self.ip
526 port = getattr(self, '%s_port' % channel)
531 port = getattr(self, '%s_port' % channel)
527
532
528 if transport == 'tcp':
533 if transport == 'tcp':
529 return "tcp://%s:%i" % (ip, port)
534 return "tcp://%s:%i" % (ip, port)
530 else:
535 else:
531 return "%s://%s-%s" % (transport, ip, port)
536 return "%s://%s-%s" % (transport, ip, port)
532
537
533 def _create_connected_socket(self, channel, identity=None):
538 def _create_connected_socket(self, channel, identity=None):
534 """Create a zmq Socket and connect it to the kernel."""
539 """Create a zmq Socket and connect it to the kernel."""
535 url = self._make_url(channel)
540 url = self._make_url(channel)
536 socket_type = channel_socket_types[channel]
541 socket_type = channel_socket_types[channel]
537 self.log.debug("Connecting to: %s" % url)
542 self.log.debug("Connecting to: %s" % url)
538 sock = self.context.socket(socket_type)
543 sock = self.context.socket(socket_type)
539 # set linger to 1s to prevent hangs at exit
544 # set linger to 1s to prevent hangs at exit
540 sock.linger = 1000
545 sock.linger = 1000
541 if identity:
546 if identity:
542 sock.identity = identity
547 sock.identity = identity
543 sock.connect(url)
548 sock.connect(url)
544 return sock
549 return sock
545
550
546 def connect_iopub(self, identity=None):
551 def connect_iopub(self, identity=None):
547 """return zmq Socket connected to the IOPub channel"""
552 """return zmq Socket connected to the IOPub channel"""
548 sock = self._create_connected_socket('iopub', identity=identity)
553 sock = self._create_connected_socket('iopub', identity=identity)
549 sock.setsockopt(zmq.SUBSCRIBE, b'')
554 sock.setsockopt(zmq.SUBSCRIBE, b'')
550 return sock
555 return sock
551
556
552 def connect_shell(self, identity=None):
557 def connect_shell(self, identity=None):
553 """return zmq Socket connected to the Shell channel"""
558 """return zmq Socket connected to the Shell channel"""
554 return self._create_connected_socket('shell', identity=identity)
559 return self._create_connected_socket('shell', identity=identity)
555
560
556 def connect_stdin(self, identity=None):
561 def connect_stdin(self, identity=None):
557 """return zmq Socket connected to the StdIn channel"""
562 """return zmq Socket connected to the StdIn channel"""
558 return self._create_connected_socket('stdin', identity=identity)
563 return self._create_connected_socket('stdin', identity=identity)
559
564
560 def connect_hb(self, identity=None):
565 def connect_hb(self, identity=None):
561 """return zmq Socket connected to the Heartbeat channel"""
566 """return zmq Socket connected to the Heartbeat channel"""
562 return self._create_connected_socket('hb', identity=identity)
567 return self._create_connected_socket('hb', identity=identity)
563
568
564 def connect_control(self, identity=None):
569 def connect_control(self, identity=None):
565 """return zmq Socket connected to the Control channel"""
570 """return zmq Socket connected to the Control channel"""
566 return self._create_connected_socket('control', identity=identity)
571 return self._create_connected_socket('control', identity=identity)
567
572
568
573
569 __all__ = [
574 __all__ = [
570 'write_connection_file',
575 'write_connection_file',
571 'get_connection_file',
576 'get_connection_file',
572 'find_connection_file',
577 'find_connection_file',
573 'get_connection_info',
578 'get_connection_info',
574 'connect_qtconsole',
579 'connect_qtconsole',
575 'tunnel_to_kernel',
580 'tunnel_to_kernel',
576 ]
581 ]
General Comments 0
You need to be logged in to leave comments. Login now