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