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