##// END OF EJS Templates
remove load_connection_file from consoleapp...
Paul Ivanov -
Show More
@@ -1,399 +1,368 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6
7 Authors:
8
9 * Evan Patterson
10 * Min RK
11 * Erik Tollerud
12 * Fernando Perez
13 * Bussonnier Matthias
14 * Thomas Kluyver
15 * Paul Ivanov
16
17 """
6 """
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
18
9
19 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
20 # Imports
11 # Imports
21 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
22
13
23 # stdlib imports
14 # stdlib imports
24 import atexit
15 import atexit
25 import json
26 import os
16 import os
27 import signal
17 import signal
28 import sys
18 import sys
29 import uuid
19 import uuid
30
20
31
21
32 # Local imports
22 # Local imports
33 from IPython.config.application import boolean_flag
23 from IPython.config.application import boolean_flag
34 from IPython.core.profiledir import ProfileDir
24 from IPython.core.profiledir import ProfileDir
35 from IPython.kernel.blocking import BlockingKernelClient
25 from IPython.kernel.blocking import BlockingKernelClient
36 from IPython.kernel import KernelManager
26 from IPython.kernel import KernelManager
37 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
27 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
38 from IPython.kernel.kernelspec import NoSuchKernel
28 from IPython.kernel.kernelspec import NoSuchKernel
39 from IPython.utils.path import filefind
29 from IPython.utils.path import filefind
40 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
42 Dict, List, Unicode, CUnicode, Int, CBool, Any
31 Dict, List, Unicode, CUnicode, Int, CBool, Any
43 )
32 )
44 from IPython.kernel.zmq.kernelapp import (
33 from IPython.kernel.zmq.kernelapp import (
45 kernel_flags,
34 kernel_flags,
46 kernel_aliases,
35 kernel_aliases,
47 IPKernelApp
36 IPKernelApp
48 )
37 )
49 from IPython.kernel.zmq.pylab.config import InlineBackend
38 from IPython.kernel.zmq.pylab.config import InlineBackend
50 from IPython.kernel.zmq.session import Session, default_secure
39 from IPython.kernel.zmq.session import Session, default_secure
51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
40 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
52 from IPython.kernel.connect import ConnectionFileMixin
41 from IPython.kernel.connect import ConnectionFileMixin
53
42
54 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
55 # Network Constants
44 # Network Constants
56 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
57
46
58 from IPython.utils.localinterfaces import localhost
47 from IPython.utils.localinterfaces import localhost
59
48
60 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
61 # Globals
50 # Globals
62 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
63
52
64
53
65 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
66 # Aliases and Flags
55 # Aliases and Flags
67 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
68
57
69 flags = dict(kernel_flags)
58 flags = dict(kernel_flags)
70
59
71 # the flags that are specific to the frontend
60 # the flags that are specific to the frontend
72 # these must be scrubbed before being passed to the kernel,
61 # these must be scrubbed before being passed to the kernel,
73 # or it will raise an error on unrecognized flags
62 # or it will raise an error on unrecognized flags
74 app_flags = {
63 app_flags = {
75 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
64 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
76 "Connect to an existing kernel. If no argument specified, guess most recent"),
65 "Connect to an existing kernel. If no argument specified, guess most recent"),
77 }
66 }
78 app_flags.update(boolean_flag(
67 app_flags.update(boolean_flag(
79 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
68 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
80 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
69 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
81 to force a direct exit without any confirmation.
70 to force a direct exit without any confirmation.
82 """,
71 """,
83 """Don't prompt the user when exiting. This will terminate the kernel
72 """Don't prompt the user when exiting. This will terminate the kernel
84 if it is owned by the frontend, and leave it alive if it is external.
73 if it is owned by the frontend, and leave it alive if it is external.
85 """
74 """
86 ))
75 ))
87 flags.update(app_flags)
76 flags.update(app_flags)
88
77
89 aliases = dict(kernel_aliases)
78 aliases = dict(kernel_aliases)
90
79
91 # also scrub aliases from the frontend
80 # also scrub aliases from the frontend
92 app_aliases = dict(
81 app_aliases = dict(
93 ip = 'IPythonConsoleApp.ip',
82 ip = 'IPythonConsoleApp.ip',
94 transport = 'IPythonConsoleApp.transport',
83 transport = 'IPythonConsoleApp.transport',
95 hb = 'IPythonConsoleApp.hb_port',
84 hb = 'IPythonConsoleApp.hb_port',
96 shell = 'IPythonConsoleApp.shell_port',
85 shell = 'IPythonConsoleApp.shell_port',
97 iopub = 'IPythonConsoleApp.iopub_port',
86 iopub = 'IPythonConsoleApp.iopub_port',
98 stdin = 'IPythonConsoleApp.stdin_port',
87 stdin = 'IPythonConsoleApp.stdin_port',
99 existing = 'IPythonConsoleApp.existing',
88 existing = 'IPythonConsoleApp.existing',
100 f = 'IPythonConsoleApp.connection_file',
89 f = 'IPythonConsoleApp.connection_file',
101
90
102 kernel = 'IPythonConsoleApp.kernel_name',
91 kernel = 'IPythonConsoleApp.kernel_name',
103
92
104 ssh = 'IPythonConsoleApp.sshserver',
93 ssh = 'IPythonConsoleApp.sshserver',
105 )
94 )
106 aliases.update(app_aliases)
95 aliases.update(app_aliases)
107
96
108 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
109 # Classes
98 # Classes
110 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
111
100
112 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
113 # IPythonConsole
102 # IPythonConsole
114 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
115
104
116 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
105 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
117
106
118 class IPythonConsoleApp(ConnectionFileMixin):
107 class IPythonConsoleApp(ConnectionFileMixin):
119 name = 'ipython-console-mixin'
108 name = 'ipython-console-mixin'
120
109
121 description = """
110 description = """
122 The IPython Mixin Console.
111 The IPython Mixin Console.
123
112
124 This class contains the common portions of console client (QtConsole,
113 This class contains the common portions of console client (QtConsole,
125 ZMQ-based terminal console, etc). It is not a full console, in that
114 ZMQ-based terminal console, etc). It is not a full console, in that
126 launched terminal subprocesses will not be able to accept input.
115 launched terminal subprocesses will not be able to accept input.
127
116
128 The Console using this mixing supports various extra features beyond
117 The Console using this mixing supports various extra features beyond
129 the single-process Terminal IPython shell, such as connecting to
118 the single-process Terminal IPython shell, such as connecting to
130 existing kernel, via:
119 existing kernel, via:
131
120
132 ipython <appname> --existing
121 ipython <appname> --existing
133
122
134 as well as tunnel via SSH
123 as well as tunnel via SSH
135
124
136 """
125 """
137
126
138 classes = classes
127 classes = classes
139 flags = Dict(flags)
128 flags = Dict(flags)
140 aliases = Dict(aliases)
129 aliases = Dict(aliases)
141 kernel_manager_class = KernelManager
130 kernel_manager_class = KernelManager
142 kernel_client_class = BlockingKernelClient
131 kernel_client_class = BlockingKernelClient
143
132
144 kernel_argv = List(Unicode)
133 kernel_argv = List(Unicode)
145 # frontend flags&aliases to be stripped when building kernel_argv
134 # frontend flags&aliases to be stripped when building kernel_argv
146 frontend_flags = Any(app_flags)
135 frontend_flags = Any(app_flags)
147 frontend_aliases = Any(app_aliases)
136 frontend_aliases = Any(app_aliases)
148
137
149 # create requested profiles by default, if they don't exist:
138 # create requested profiles by default, if they don't exist:
150 auto_create = CBool(True)
139 auto_create = CBool(True)
151 # connection info:
140 # connection info:
152
141
153 sshserver = Unicode('', config=True,
142 sshserver = Unicode('', config=True,
154 help="""The SSH server to use to connect to the kernel.""")
143 help="""The SSH server to use to connect to the kernel.""")
155 sshkey = Unicode('', config=True,
144 sshkey = Unicode('', config=True,
156 help="""Path to the ssh key to use for logging in to the ssh server.""")
145 help="""Path to the ssh key to use for logging in to the ssh server.""")
157
146
158 hb_port = Int(0, config=True,
147 hb_port = Int(0, config=True,
159 help="set the heartbeat port [default: random]")
148 help="set the heartbeat port [default: random]")
160 shell_port = Int(0, config=True,
149 shell_port = Int(0, config=True,
161 help="set the shell (ROUTER) port [default: random]")
150 help="set the shell (ROUTER) port [default: random]")
162 iopub_port = Int(0, config=True,
151 iopub_port = Int(0, config=True,
163 help="set the iopub (PUB) port [default: random]")
152 help="set the iopub (PUB) port [default: random]")
164 stdin_port = Int(0, config=True,
153 stdin_port = Int(0, config=True,
165 help="set the stdin (DEALER) port [default: random]")
154 help="set the stdin (DEALER) port [default: random]")
166 connection_file = Unicode('', config=True,
155 connection_file = Unicode('', config=True,
167 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
156 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
168
157
169 This file will contain the IP, ports, and authentication key needed to connect
158 This file will contain the IP, ports, and authentication key needed to connect
170 clients to this kernel. By default, this file will be created in the security-dir
159 clients to this kernel. By default, this file will be created in the security-dir
171 of the current profile, but can be specified by absolute path.
160 of the current profile, but can be specified by absolute path.
172 """)
161 """)
173 def _connection_file_default(self):
162 def _connection_file_default(self):
174 return 'kernel-%i.json' % os.getpid()
163 return 'kernel-%i.json' % os.getpid()
175
164
176 existing = CUnicode('', config=True,
165 existing = CUnicode('', config=True,
177 help="""Connect to an already running kernel""")
166 help="""Connect to an already running kernel""")
178
167
179 kernel_name = Unicode('python', config=True,
168 kernel_name = Unicode('python', config=True,
180 help="""The name of the default kernel to start.""")
169 help="""The name of the default kernel to start.""")
181
170
182 confirm_exit = CBool(True, config=True,
171 confirm_exit = CBool(True, config=True,
183 help="""
172 help="""
184 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
173 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
185 to force a direct exit without any confirmation.""",
174 to force a direct exit without any confirmation.""",
186 )
175 )
187
176
188
177
189 def build_kernel_argv(self, argv=None):
178 def build_kernel_argv(self, argv=None):
190 """build argv to be passed to kernel subprocess"""
179 """build argv to be passed to kernel subprocess"""
191 if argv is None:
180 if argv is None:
192 argv = sys.argv[1:]
181 argv = sys.argv[1:]
193 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
182 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
194 # kernel should inherit default config file from frontend
183 # kernel should inherit default config file from frontend
195 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
184 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
196
185
197 def init_connection_file(self):
186 def init_connection_file(self):
198 """find the connection file, and load the info if found.
187 """find the connection file, and load the info if found.
199
188
200 The current working directory and the current profile's security
189 The current working directory and the current profile's security
201 directory will be searched for the file if it is not given by
190 directory will be searched for the file if it is not given by
202 absolute path.
191 absolute path.
203
192
204 When attempting to connect to an existing kernel and the `--existing`
193 When attempting to connect to an existing kernel and the `--existing`
205 argument does not match an existing file, it will be interpreted as a
194 argument does not match an existing file, it will be interpreted as a
206 fileglob, and the matching file in the current profile's security dir
195 fileglob, and the matching file in the current profile's security dir
207 with the latest access time will be used.
196 with the latest access time will be used.
208
197
209 After this method is called, self.connection_file contains the *full path*
198 After this method is called, self.connection_file contains the *full path*
210 to the connection file, never just its name.
199 to the connection file, never just its name.
211 """
200 """
212 if self.existing:
201 if self.existing:
213 try:
202 try:
214 cf = find_connection_file(self.existing)
203 cf = find_connection_file(self.existing)
215 except Exception:
204 except Exception:
216 self.log.critical("Could not find existing kernel connection file %s", self.existing)
205 self.log.critical("Could not find existing kernel connection file %s", self.existing)
217 self.exit(1)
206 self.exit(1)
218 self.log.debug("Connecting to existing kernel: %s" % cf)
207 self.log.debug("Connecting to existing kernel: %s" % cf)
219 self.connection_file = cf
208 self.connection_file = cf
220 else:
209 else:
221 # not existing, check if we are going to write the file
210 # not existing, check if we are going to write the file
222 # and ensure that self.connection_file is a full path, not just the shortname
211 # and ensure that self.connection_file is a full path, not just the shortname
223 try:
212 try:
224 cf = find_connection_file(self.connection_file)
213 cf = find_connection_file(self.connection_file)
225 except Exception:
214 except Exception:
226 # file might not exist
215 # file might not exist
227 if self.connection_file == os.path.basename(self.connection_file):
216 if self.connection_file == os.path.basename(self.connection_file):
228 # just shortname, put it in security dir
217 # just shortname, put it in security dir
229 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
218 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
230 else:
219 else:
231 cf = self.connection_file
220 cf = self.connection_file
232 self.connection_file = cf
221 self.connection_file = cf
222 try:
223 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
224 except IOError:
225 self.log.debug("Connection File not found: %s", self.connection_file)
226 return
233
227
234 # should load_connection_file only be used for existing?
228 # should load_connection_file only be used for existing?
235 # as it is now, this allows reusing ports if an existing
229 # as it is now, this allows reusing ports if an existing
236 # file is requested
230 # file is requested
237 try:
231 try:
238 self.load_connection_file()
232 self.load_connection_file()
239 except Exception:
233 except Exception:
240 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
234 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
241 self.exit(1)
235 self.exit(1)
242
236
243 def load_connection_file(self):
244 """load ip/port/hmac config from JSON connection file"""
245 # this is identical to IPKernelApp.load_connection_file
246 # perhaps it can be centralized somewhere?
247 try:
248 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
249 except IOError:
250 self.log.debug("Connection File not found: %s", self.connection_file)
251 return
252 self.log.debug(u"Loading connection file %s", fname)
253 with open(fname) as f:
254 cfg = json.load(f)
255 self.transport = cfg.get('transport', 'tcp')
256 self.ip = cfg.get('ip', localhost())
257
258 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
259 name = channel + '_port'
260 if getattr(self, name) == 0 and name in cfg:
261 # not overridden by config or cl_args
262 setattr(self, name, cfg[name])
263 if 'key' in cfg:
264 self.config.Session.key = str_to_bytes(cfg['key'])
265 if 'signature_scheme' in cfg:
266 self.config.Session.signature_scheme = cfg['signature_scheme']
267
268 def init_ssh(self):
237 def init_ssh(self):
269 """set up ssh tunnels, if needed."""
238 """set up ssh tunnels, if needed."""
270 if not self.existing or (not self.sshserver and not self.sshkey):
239 if not self.existing or (not self.sshserver and not self.sshkey):
271 return
240 return
272 self.load_connection_file()
241 self.load_connection_file()
273
242
274 transport = self.transport
243 transport = self.transport
275 ip = self.ip
244 ip = self.ip
276
245
277 if transport != 'tcp':
246 if transport != 'tcp':
278 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
247 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
279 sys.exit(-1)
248 sys.exit(-1)
280
249
281 if self.sshkey and not self.sshserver:
250 if self.sshkey and not self.sshserver:
282 # specifying just the key implies that we are connecting directly
251 # specifying just the key implies that we are connecting directly
283 self.sshserver = ip
252 self.sshserver = ip
284 ip = localhost()
253 ip = localhost()
285
254
286 # build connection dict for tunnels:
255 # build connection dict for tunnels:
287 info = dict(ip=ip,
256 info = dict(ip=ip,
288 shell_port=self.shell_port,
257 shell_port=self.shell_port,
289 iopub_port=self.iopub_port,
258 iopub_port=self.iopub_port,
290 stdin_port=self.stdin_port,
259 stdin_port=self.stdin_port,
291 hb_port=self.hb_port
260 hb_port=self.hb_port
292 )
261 )
293
262
294 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
263 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
295
264
296 # tunnels return a new set of ports, which will be on localhost:
265 # tunnels return a new set of ports, which will be on localhost:
297 self.ip = localhost()
266 self.ip = localhost()
298 try:
267 try:
299 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
268 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
300 except:
269 except:
301 # even catch KeyboardInterrupt
270 # even catch KeyboardInterrupt
302 self.log.error("Could not setup tunnels", exc_info=True)
271 self.log.error("Could not setup tunnels", exc_info=True)
303 self.exit(1)
272 self.exit(1)
304
273
305 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
274 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
306
275
307 cf = self.connection_file
276 cf = self.connection_file
308 base,ext = os.path.splitext(cf)
277 base,ext = os.path.splitext(cf)
309 base = os.path.basename(base)
278 base = os.path.basename(base)
310 self.connection_file = os.path.basename(base)+'-ssh'+ext
279 self.connection_file = os.path.basename(base)+'-ssh'+ext
311 self.log.info("To connect another client via this tunnel, use:")
280 self.log.info("To connect another client via this tunnel, use:")
312 self.log.info("--existing %s" % self.connection_file)
281 self.log.info("--existing %s" % self.connection_file)
313
282
314 def _new_connection_file(self):
283 def _new_connection_file(self):
315 cf = ''
284 cf = ''
316 while not cf:
285 while not cf:
317 # we don't need a 128b id to distinguish kernels, use more readable
286 # we don't need a 128b id to distinguish kernels, use more readable
318 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
287 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
319 # kernels can subclass.
288 # kernels can subclass.
320 ident = str(uuid.uuid4()).split('-')[-1]
289 ident = str(uuid.uuid4()).split('-')[-1]
321 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
290 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
322 # only keep if it's actually new. Protect against unlikely collision
291 # only keep if it's actually new. Protect against unlikely collision
323 # in 48b random search space
292 # in 48b random search space
324 cf = cf if not os.path.exists(cf) else ''
293 cf = cf if not os.path.exists(cf) else ''
325 return cf
294 return cf
326
295
327 def init_kernel_manager(self):
296 def init_kernel_manager(self):
328 # Don't let Qt or ZMQ swallow KeyboardInterupts.
297 # Don't let Qt or ZMQ swallow KeyboardInterupts.
329 if self.existing:
298 if self.existing:
330 self.kernel_manager = None
299 self.kernel_manager = None
331 return
300 return
332 signal.signal(signal.SIGINT, signal.SIG_DFL)
301 signal.signal(signal.SIGINT, signal.SIG_DFL)
333
302
334 # Create a KernelManager and start a kernel.
303 # Create a KernelManager and start a kernel.
335 try:
304 try:
336 self.kernel_manager = self.kernel_manager_class(
305 self.kernel_manager = self.kernel_manager_class(
337 ip=self.ip,
306 ip=self.ip,
338 transport=self.transport,
307 transport=self.transport,
339 shell_port=self.shell_port,
308 shell_port=self.shell_port,
340 iopub_port=self.iopub_port,
309 iopub_port=self.iopub_port,
341 stdin_port=self.stdin_port,
310 stdin_port=self.stdin_port,
342 hb_port=self.hb_port,
311 hb_port=self.hb_port,
343 connection_file=self.connection_file,
312 connection_file=self.connection_file,
344 kernel_name=self.kernel_name,
313 kernel_name=self.kernel_name,
345 parent=self,
314 parent=self,
346 ipython_dir=self.ipython_dir,
315 ipython_dir=self.ipython_dir,
347 )
316 )
348 except NoSuchKernel:
317 except NoSuchKernel:
349 self.log.critical("Could not find kernel %s", self.kernel_name)
318 self.log.critical("Could not find kernel %s", self.kernel_name)
350 self.exit(1)
319 self.exit(1)
351
320
352 self.kernel_manager.client_factory = self.kernel_client_class
321 self.kernel_manager.client_factory = self.kernel_client_class
353 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
322 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
354 atexit.register(self.kernel_manager.cleanup_ipc_files)
323 atexit.register(self.kernel_manager.cleanup_ipc_files)
355
324
356 if self.sshserver:
325 if self.sshserver:
357 # ssh, write new connection file
326 # ssh, write new connection file
358 self.kernel_manager.write_connection_file()
327 self.kernel_manager.write_connection_file()
359
328
360 # in case KM defaults / ssh writing changes things:
329 # in case KM defaults / ssh writing changes things:
361 km = self.kernel_manager
330 km = self.kernel_manager
362 self.shell_port=km.shell_port
331 self.shell_port=km.shell_port
363 self.iopub_port=km.iopub_port
332 self.iopub_port=km.iopub_port
364 self.stdin_port=km.stdin_port
333 self.stdin_port=km.stdin_port
365 self.hb_port=km.hb_port
334 self.hb_port=km.hb_port
366 self.connection_file = km.connection_file
335 self.connection_file = km.connection_file
367
336
368 atexit.register(self.kernel_manager.cleanup_connection_file)
337 atexit.register(self.kernel_manager.cleanup_connection_file)
369
338
370 def init_kernel_client(self):
339 def init_kernel_client(self):
371 if self.kernel_manager is not None:
340 if self.kernel_manager is not None:
372 self.kernel_client = self.kernel_manager.client()
341 self.kernel_client = self.kernel_manager.client()
373 else:
342 else:
374 self.kernel_client = self.kernel_client_class(
343 self.kernel_client = self.kernel_client_class(
375 ip=self.ip,
344 ip=self.ip,
376 transport=self.transport,
345 transport=self.transport,
377 shell_port=self.shell_port,
346 shell_port=self.shell_port,
378 iopub_port=self.iopub_port,
347 iopub_port=self.iopub_port,
379 stdin_port=self.stdin_port,
348 stdin_port=self.stdin_port,
380 hb_port=self.hb_port,
349 hb_port=self.hb_port,
381 connection_file=self.connection_file,
350 connection_file=self.connection_file,
382 parent=self,
351 parent=self,
383 )
352 )
384
353
385 self.kernel_client.start_channels()
354 self.kernel_client.start_channels()
386
355
387
356
388
357
389 def initialize(self, argv=None):
358 def initialize(self, argv=None):
390 """
359 """
391 Classes which mix this class in should call:
360 Classes which mix this class in should call:
392 IPythonConsoleApp.initialize(self,argv)
361 IPythonConsoleApp.initialize(self,argv)
393 """
362 """
394 self.init_connection_file()
363 self.init_connection_file()
395 default_secure(self.config)
364 default_secure(self.config)
396 self.init_ssh()
365 self.init_ssh()
397 self.init_kernel_manager()
366 self.init_kernel_manager()
398 self.init_kernel_client()
367 self.init_kernel_client()
399
368
@@ -1,562 +1,558 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 Authors:
3 There is a ConnectionFileMixin class which encapsulates the logic related to
4
4 writing and reading connections files
5 * Min Ragan-Kelley
6
5
7 """
6 """
8
7 # Copyright (c) IPython Development Team.
9 #-----------------------------------------------------------------------------
8 # Distributed under the terms of the Modified BSD License.
10 # Copyright (C) 2013 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
9
16 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
17 # Imports
11 # Imports
18 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
19
13
20 from __future__ import absolute_import
14 from __future__ import absolute_import
21
15
22 import glob
16 import glob
23 import json
17 import json
24 import os
18 import os
25 import socket
19 import socket
26 import sys
20 import sys
27 from getpass import getpass
21 from getpass import getpass
28 from subprocess import Popen, PIPE
22 from subprocess import Popen, PIPE
29 import tempfile
23 import tempfile
30
24
31 import zmq
25 import zmq
32
26
33 # external imports
27 # external imports
34 from IPython.external.ssh import tunnel
28 from IPython.external.ssh import tunnel
35
29
36 # IPython imports
30 # IPython imports
37 from IPython.config import Configurable
31 from IPython.config import Configurable
38 from IPython.core.profiledir import ProfileDir
32 from IPython.core.profiledir import ProfileDir
39 from IPython.utils.localinterfaces import localhost
33 from IPython.utils.localinterfaces import localhost
40 from IPython.utils.path import filefind, get_ipython_dir
34 from IPython.utils.path import filefind, get_ipython_dir
41 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
35 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
42 string_types)
36 string_types)
43 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
44 Bool, Integer, Unicode, CaselessStrEnum,
38 Bool, Integer, Unicode, CaselessStrEnum,
45 )
39 )
46
40
47
41
48 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
49 # Working with Connection Files
43 # Working with Connection Files
50 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
51
45
52 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
46 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',
47 control_port=0, ip='', key=b'', transport='tcp',
54 signature_scheme='hmac-sha256',
48 signature_scheme='hmac-sha256',
55 ):
49 ):
56 """Generates a JSON config file, including the selection of random ports.
50 """Generates a JSON config file, including the selection of random ports.
57
51
58 Parameters
52 Parameters
59 ----------
53 ----------
60
54
61 fname : unicode
55 fname : unicode
62 The path to the file to write
56 The path to the file to write
63
57
64 shell_port : int, optional
58 shell_port : int, optional
65 The port to use for ROUTER (shell) channel.
59 The port to use for ROUTER (shell) channel.
66
60
67 iopub_port : int, optional
61 iopub_port : int, optional
68 The port to use for the SUB channel.
62 The port to use for the SUB channel.
69
63
70 stdin_port : int, optional
64 stdin_port : int, optional
71 The port to use for the ROUTER (raw input) channel.
65 The port to use for the ROUTER (raw input) channel.
72
66
73 control_port : int, optional
67 control_port : int, optional
74 The port to use for the ROUTER (control) channel.
68 The port to use for the ROUTER (control) channel.
75
69
76 hb_port : int, optional
70 hb_port : int, optional
77 The port to use for the heartbeat REP channel.
71 The port to use for the heartbeat REP channel.
78
72
79 ip : str, optional
73 ip : str, optional
80 The ip address the kernel will bind to.
74 The ip address the kernel will bind to.
81
75
82 key : str, optional
76 key : str, optional
83 The Session key used for message authentication.
77 The Session key used for message authentication.
84
78
85 signature_scheme : str, optional
79 signature_scheme : str, optional
86 The scheme used for message authentication.
80 The scheme used for message authentication.
87 This has the form 'digest-hash', where 'digest'
81 This has the form 'digest-hash', where 'digest'
88 is the scheme used for digests, and 'hash' is the name of the hash function
82 is the scheme used for digests, and 'hash' is the name of the hash function
89 used by the digest scheme.
83 used by the digest scheme.
90 Currently, 'hmac' is the only supported digest scheme,
84 Currently, 'hmac' is the only supported digest scheme,
91 and 'sha256' is the default hash function.
85 and 'sha256' is the default hash function.
92
86
93 """
87 """
94 if not ip:
88 if not ip:
95 ip = localhost()
89 ip = localhost()
96 # default to temporary connector file
90 # default to temporary connector file
97 if not fname:
91 if not fname:
98 fd, fname = tempfile.mkstemp('.json')
92 fd, fname = tempfile.mkstemp('.json')
99 os.close(fd)
93 os.close(fd)
100
94
101 # Find open ports as necessary.
95 # Find open ports as necessary.
102
96
103 ports = []
97 ports = []
104 ports_needed = int(shell_port <= 0) + \
98 ports_needed = int(shell_port <= 0) + \
105 int(iopub_port <= 0) + \
99 int(iopub_port <= 0) + \
106 int(stdin_port <= 0) + \
100 int(stdin_port <= 0) + \
107 int(control_port <= 0) + \
101 int(control_port <= 0) + \
108 int(hb_port <= 0)
102 int(hb_port <= 0)
109 if transport == 'tcp':
103 if transport == 'tcp':
110 for i in range(ports_needed):
104 for i in range(ports_needed):
111 sock = socket.socket()
105 sock = socket.socket()
112 # struct.pack('ii', (0,0)) is 8 null bytes
106 # struct.pack('ii', (0,0)) is 8 null bytes
113 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
107 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
114 sock.bind(('', 0))
108 sock.bind(('', 0))
115 ports.append(sock)
109 ports.append(sock)
116 for i, sock in enumerate(ports):
110 for i, sock in enumerate(ports):
117 port = sock.getsockname()[1]
111 port = sock.getsockname()[1]
118 sock.close()
112 sock.close()
119 ports[i] = port
113 ports[i] = port
120 else:
114 else:
121 N = 1
115 N = 1
122 for i in range(ports_needed):
116 for i in range(ports_needed):
123 while os.path.exists("%s-%s" % (ip, str(N))):
117 while os.path.exists("%s-%s" % (ip, str(N))):
124 N += 1
118 N += 1
125 ports.append(N)
119 ports.append(N)
126 N += 1
120 N += 1
127 if shell_port <= 0:
121 if shell_port <= 0:
128 shell_port = ports.pop(0)
122 shell_port = ports.pop(0)
129 if iopub_port <= 0:
123 if iopub_port <= 0:
130 iopub_port = ports.pop(0)
124 iopub_port = ports.pop(0)
131 if stdin_port <= 0:
125 if stdin_port <= 0:
132 stdin_port = ports.pop(0)
126 stdin_port = ports.pop(0)
133 if control_port <= 0:
127 if control_port <= 0:
134 control_port = ports.pop(0)
128 control_port = ports.pop(0)
135 if hb_port <= 0:
129 if hb_port <= 0:
136 hb_port = ports.pop(0)
130 hb_port = ports.pop(0)
137
131
138 cfg = dict( shell_port=shell_port,
132 cfg = dict( shell_port=shell_port,
139 iopub_port=iopub_port,
133 iopub_port=iopub_port,
140 stdin_port=stdin_port,
134 stdin_port=stdin_port,
141 control_port=control_port,
135 control_port=control_port,
142 hb_port=hb_port,
136 hb_port=hb_port,
143 )
137 )
144 cfg['ip'] = ip
138 cfg['ip'] = ip
145 cfg['key'] = bytes_to_str(key)
139 cfg['key'] = bytes_to_str(key)
146 cfg['transport'] = transport
140 cfg['transport'] = transport
147 cfg['signature_scheme'] = signature_scheme
141 cfg['signature_scheme'] = signature_scheme
148
142
149 with open(fname, 'w') as f:
143 with open(fname, 'w') as f:
150 f.write(json.dumps(cfg, indent=2))
144 f.write(json.dumps(cfg, indent=2))
151
145
152 return fname, cfg
146 return fname, cfg
153
147
154
148
155 def get_connection_file(app=None):
149 def get_connection_file(app=None):
156 """Return the path to the connection file of an app
150 """Return the path to the connection file of an app
157
151
158 Parameters
152 Parameters
159 ----------
153 ----------
160 app : IPKernelApp instance [optional]
154 app : IPKernelApp instance [optional]
161 If unspecified, the currently running app will be used
155 If unspecified, the currently running app will be used
162 """
156 """
163 if app is None:
157 if app is None:
164 from IPython.kernel.zmq.kernelapp import IPKernelApp
158 from IPython.kernel.zmq.kernelapp import IPKernelApp
165 if not IPKernelApp.initialized():
159 if not IPKernelApp.initialized():
166 raise RuntimeError("app not specified, and not in a running Kernel")
160 raise RuntimeError("app not specified, and not in a running Kernel")
167
161
168 app = IPKernelApp.instance()
162 app = IPKernelApp.instance()
169 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
163 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
170
164
171
165
172 def find_connection_file(filename, profile=None):
166 def find_connection_file(filename, profile=None):
173 """find a connection file, and return its absolute path.
167 """find a connection file, and return its absolute path.
174
168
175 The current working directory and the profile's security
169 The current working directory and the profile's security
176 directory will be searched for the file if it is not given by
170 directory will be searched for the file if it is not given by
177 absolute path.
171 absolute path.
178
172
179 If profile is unspecified, then the current running application's
173 If profile is unspecified, then the current running application's
180 profile will be used, or 'default', if not run from IPython.
174 profile will be used, or 'default', if not run from IPython.
181
175
182 If the argument does not match an existing file, it will be interpreted as a
176 If the argument does not match an existing file, it will be interpreted as a
183 fileglob, and the matching file in the profile's security dir with
177 fileglob, and the matching file in the profile's security dir with
184 the latest access time will be used.
178 the latest access time will be used.
185
179
186 Parameters
180 Parameters
187 ----------
181 ----------
188 filename : str
182 filename : str
189 The connection file or fileglob to search for.
183 The connection file or fileglob to search for.
190 profile : str [optional]
184 profile : str [optional]
191 The name of the profile to use when searching for the connection file,
185 The name of the profile to use when searching for the connection file,
192 if different from the current IPython session or 'default'.
186 if different from the current IPython session or 'default'.
193
187
194 Returns
188 Returns
195 -------
189 -------
196 str : The absolute path of the connection file.
190 str : The absolute path of the connection file.
197 """
191 """
198 from IPython.core.application import BaseIPythonApplication as IPApp
192 from IPython.core.application import BaseIPythonApplication as IPApp
199 try:
193 try:
200 # quick check for absolute path, before going through logic
194 # quick check for absolute path, before going through logic
201 return filefind(filename)
195 return filefind(filename)
202 except IOError:
196 except IOError:
203 pass
197 pass
204
198
205 if profile is None:
199 if profile is None:
206 # profile unspecified, check if running from an IPython app
200 # profile unspecified, check if running from an IPython app
207 if IPApp.initialized():
201 if IPApp.initialized():
208 app = IPApp.instance()
202 app = IPApp.instance()
209 profile_dir = app.profile_dir
203 profile_dir = app.profile_dir
210 else:
204 else:
211 # not running in IPython, use default profile
205 # not running in IPython, use default profile
212 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
206 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
213 else:
207 else:
214 # find profiledir by profile name:
208 # find profiledir by profile name:
215 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
209 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
216 security_dir = profile_dir.security_dir
210 security_dir = profile_dir.security_dir
217
211
218 try:
212 try:
219 # first, try explicit name
213 # first, try explicit name
220 return filefind(filename, ['.', security_dir])
214 return filefind(filename, ['.', security_dir])
221 except IOError:
215 except IOError:
222 pass
216 pass
223
217
224 # not found by full name
218 # not found by full name
225
219
226 if '*' in filename:
220 if '*' in filename:
227 # given as a glob already
221 # given as a glob already
228 pat = filename
222 pat = filename
229 else:
223 else:
230 # accept any substring match
224 # accept any substring match
231 pat = '*%s*' % filename
225 pat = '*%s*' % filename
232 matches = glob.glob( os.path.join(security_dir, pat) )
226 matches = glob.glob( os.path.join(security_dir, pat) )
233 if not matches:
227 if not matches:
234 raise IOError("Could not find %r in %r" % (filename, security_dir))
228 raise IOError("Could not find %r in %r" % (filename, security_dir))
235 elif len(matches) == 1:
229 elif len(matches) == 1:
236 return matches[0]
230 return matches[0]
237 else:
231 else:
238 # get most recent match, by access time:
232 # get most recent match, by access time:
239 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
233 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
240
234
241
235
242 def get_connection_info(connection_file=None, unpack=False, profile=None):
236 def get_connection_info(connection_file=None, unpack=False, profile=None):
243 """Return the connection information for the current Kernel.
237 """Return the connection information for the current Kernel.
244
238
245 Parameters
239 Parameters
246 ----------
240 ----------
247 connection_file : str [optional]
241 connection_file : str [optional]
248 The connection file to be used. Can be given by absolute path, or
242 The connection file to be used. Can be given by absolute path, or
249 IPython will search in the security directory of a given profile.
243 IPython will search in the security directory of a given profile.
250 If run from IPython,
244 If run from IPython,
251
245
252 If unspecified, the connection file for the currently running
246 If unspecified, the connection file for the currently running
253 IPython Kernel will be used, which is only allowed from inside a kernel.
247 IPython Kernel will be used, which is only allowed from inside a kernel.
254 unpack : bool [default: False]
248 unpack : bool [default: False]
255 if True, return the unpacked dict, otherwise just the string contents
249 if True, return the unpacked dict, otherwise just the string contents
256 of the file.
250 of the file.
257 profile : str [optional]
251 profile : str [optional]
258 The name of the profile to use when searching for the connection file,
252 The name of the profile to use when searching for the connection file,
259 if different from the current IPython session or 'default'.
253 if different from the current IPython session or 'default'.
260
254
261
255
262 Returns
256 Returns
263 -------
257 -------
264 The connection dictionary of the current kernel, as string or dict,
258 The connection dictionary of the current kernel, as string or dict,
265 depending on `unpack`.
259 depending on `unpack`.
266 """
260 """
267 if connection_file is None:
261 if connection_file is None:
268 # get connection file from current kernel
262 # get connection file from current kernel
269 cf = get_connection_file()
263 cf = get_connection_file()
270 else:
264 else:
271 # connection file specified, allow shortnames:
265 # connection file specified, allow shortnames:
272 cf = find_connection_file(connection_file, profile=profile)
266 cf = find_connection_file(connection_file, profile=profile)
273
267
274 with open(cf) as f:
268 with open(cf) as f:
275 info = f.read()
269 info = f.read()
276
270
277 if unpack:
271 if unpack:
278 info = json.loads(info)
272 info = json.loads(info)
279 # ensure key is bytes:
273 # ensure key is bytes:
280 info['key'] = str_to_bytes(info.get('key', ''))
274 info['key'] = str_to_bytes(info.get('key', ''))
281 return info
275 return info
282
276
283
277
284 def connect_qtconsole(connection_file=None, argv=None, profile=None):
278 def connect_qtconsole(connection_file=None, argv=None, profile=None):
285 """Connect a qtconsole to the current kernel.
279 """Connect a qtconsole to the current kernel.
286
280
287 This is useful for connecting a second qtconsole to a kernel, or to a
281 This is useful for connecting a second qtconsole to a kernel, or to a
288 local notebook.
282 local notebook.
289
283
290 Parameters
284 Parameters
291 ----------
285 ----------
292 connection_file : str [optional]
286 connection_file : str [optional]
293 The connection file to be used. Can be given by absolute path, or
287 The connection file to be used. Can be given by absolute path, or
294 IPython will search in the security directory of a given profile.
288 IPython will search in the security directory of a given profile.
295 If run from IPython,
289 If run from IPython,
296
290
297 If unspecified, the connection file for the currently running
291 If unspecified, the connection file for the currently running
298 IPython Kernel will be used, which is only allowed from inside a kernel.
292 IPython Kernel will be used, which is only allowed from inside a kernel.
299 argv : list [optional]
293 argv : list [optional]
300 Any extra args to be passed to the console.
294 Any extra args to be passed to the console.
301 profile : str [optional]
295 profile : str [optional]
302 The name of the profile to use when searching for the connection file,
296 The name of the profile to use when searching for the connection file,
303 if different from the current IPython session or 'default'.
297 if different from the current IPython session or 'default'.
304
298
305
299
306 Returns
300 Returns
307 -------
301 -------
308 subprocess.Popen instance running the qtconsole frontend
302 subprocess.Popen instance running the qtconsole frontend
309 """
303 """
310 argv = [] if argv is None else argv
304 argv = [] if argv is None else argv
311
305
312 if connection_file is None:
306 if connection_file is None:
313 # get connection file from current kernel
307 # get connection file from current kernel
314 cf = get_connection_file()
308 cf = get_connection_file()
315 else:
309 else:
316 cf = find_connection_file(connection_file, profile=profile)
310 cf = find_connection_file(connection_file, profile=profile)
317
311
318 cmd = ';'.join([
312 cmd = ';'.join([
319 "from IPython.qt.console import qtconsoleapp",
313 "from IPython.qt.console import qtconsoleapp",
320 "qtconsoleapp.main()"
314 "qtconsoleapp.main()"
321 ])
315 ])
322
316
323 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
317 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
324 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
318 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
325 )
319 )
326
320
327
321
328 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
322 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
329 """tunnel connections to a kernel via ssh
323 """tunnel connections to a kernel via ssh
330
324
331 This will open four SSH tunnels from localhost on this machine to the
325 This will open four SSH tunnels from localhost on this machine to the
332 ports associated with the kernel. They can be either direct
326 ports associated with the kernel. They can be either direct
333 localhost-localhost tunnels, or if an intermediate server is necessary,
327 localhost-localhost tunnels, or if an intermediate server is necessary,
334 the kernel must be listening on a public IP.
328 the kernel must be listening on a public IP.
335
329
336 Parameters
330 Parameters
337 ----------
331 ----------
338 connection_info : dict or str (path)
332 connection_info : dict or str (path)
339 Either a connection dict, or the path to a JSON connection file
333 Either a connection dict, or the path to a JSON connection file
340 sshserver : str
334 sshserver : str
341 The ssh sever to use to tunnel to the kernel. Can be a full
335 The ssh sever to use to tunnel to the kernel. Can be a full
342 `user@server:port` string. ssh config aliases are respected.
336 `user@server:port` string. ssh config aliases are respected.
343 sshkey : str [optional]
337 sshkey : str [optional]
344 Path to file containing ssh key to use for authentication.
338 Path to file containing ssh key to use for authentication.
345 Only necessary if your ssh config does not already associate
339 Only necessary if your ssh config does not already associate
346 a keyfile with the host.
340 a keyfile with the host.
347
341
348 Returns
342 Returns
349 -------
343 -------
350
344
351 (shell, iopub, stdin, hb) : ints
345 (shell, iopub, stdin, hb) : ints
352 The four ports on localhost that have been forwarded to the kernel.
346 The four ports on localhost that have been forwarded to the kernel.
353 """
347 """
354 if isinstance(connection_info, string_types):
348 if isinstance(connection_info, string_types):
355 # it's a path, unpack it
349 # it's a path, unpack it
356 with open(connection_info) as f:
350 with open(connection_info) as f:
357 connection_info = json.loads(f.read())
351 connection_info = json.loads(f.read())
358
352
359 cf = connection_info
353 cf = connection_info
360
354
361 lports = tunnel.select_random_ports(4)
355 lports = tunnel.select_random_ports(4)
362 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
356 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
363
357
364 remote_ip = cf['ip']
358 remote_ip = cf['ip']
365
359
366 if tunnel.try_passwordless_ssh(sshserver, sshkey):
360 if tunnel.try_passwordless_ssh(sshserver, sshkey):
367 password=False
361 password=False
368 else:
362 else:
369 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
363 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
370
364
371 for lp,rp in zip(lports, rports):
365 for lp,rp in zip(lports, rports):
372 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
366 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
373
367
374 return tuple(lports)
368 return tuple(lports)
375
369
376
370
377 #-----------------------------------------------------------------------------
371 #-----------------------------------------------------------------------------
378 # Mixin for classes that work with connection files
372 # Mixin for classes that work with connection files
379 #-----------------------------------------------------------------------------
373 #-----------------------------------------------------------------------------
380
374
381 channel_socket_types = {
375 channel_socket_types = {
382 'hb' : zmq.REQ,
376 'hb' : zmq.REQ,
383 'shell' : zmq.DEALER,
377 'shell' : zmq.DEALER,
384 'iopub' : zmq.SUB,
378 'iopub' : zmq.SUB,
385 'stdin' : zmq.DEALER,
379 'stdin' : zmq.DEALER,
386 'control': zmq.DEALER,
380 'control': zmq.DEALER,
387 }
381 }
388
382
389 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
383 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
390
384
391 class ConnectionFileMixin(Configurable):
385 class ConnectionFileMixin(Configurable):
392 """Mixin for configurable classes that work with connection files"""
386 """Mixin for configurable classes that work with connection files"""
393
387
394 # The addresses for the communication channels
388 # The addresses for the communication channels
395 connection_file = Unicode('')
389 connection_file = Unicode('')
396 _connection_file_written = Bool(False)
390 _connection_file_written = Bool(False)
397
391
398 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
392 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
399
393
400 ip = Unicode(config=True,
394 ip = Unicode(config=True,
401 help="""Set the kernel\'s IP address [default localhost].
395 help="""Set the kernel\'s IP address [default localhost].
402 If the IP address is something other than localhost, then
396 If the IP address is something other than localhost, then
403 Consoles on other machines will be able to connect
397 Consoles on other machines will be able to connect
404 to the Kernel, so be careful!"""
398 to the Kernel, so be careful!"""
405 )
399 )
406
400
407 def _ip_default(self):
401 def _ip_default(self):
408 if self.transport == 'ipc':
402 if self.transport == 'ipc':
409 if self.connection_file:
403 if self.connection_file:
410 return os.path.splitext(self.connection_file)[0] + '-ipc'
404 return os.path.splitext(self.connection_file)[0] + '-ipc'
411 else:
405 else:
412 return 'kernel-ipc'
406 return 'kernel-ipc'
413 else:
407 else:
414 return localhost()
408 return localhost()
415
409
416 def _ip_changed(self, name, old, new):
410 def _ip_changed(self, name, old, new):
417 if new == '*':
411 if new == '*':
418 self.ip = '0.0.0.0'
412 self.ip = '0.0.0.0'
419
413
420 # protected traits
414 # protected traits
421
415
422 shell_port = Integer(0)
416 shell_port = Integer(0)
423 iopub_port = Integer(0)
417 iopub_port = Integer(0)
424 stdin_port = Integer(0)
418 stdin_port = Integer(0)
425 control_port = Integer(0)
419 control_port = Integer(0)
426 hb_port = Integer(0)
420 hb_port = Integer(0)
427
421
428 @property
422 @property
429 def ports(self):
423 def ports(self):
430 return [ getattr(self, name) for name in port_names ]
424 return [ getattr(self, name) for name in port_names ]
431
425
432 #--------------------------------------------------------------------------
426 #--------------------------------------------------------------------------
433 # Connection and ipc file management
427 # Connection and ipc file management
434 #--------------------------------------------------------------------------
428 #--------------------------------------------------------------------------
435
429
436 def get_connection_info(self):
430 def get_connection_info(self):
437 """return the connection info as a dict"""
431 """return the connection info as a dict"""
438 return dict(
432 return dict(
439 transport=self.transport,
433 transport=self.transport,
440 ip=self.ip,
434 ip=self.ip,
441 shell_port=self.shell_port,
435 shell_port=self.shell_port,
442 iopub_port=self.iopub_port,
436 iopub_port=self.iopub_port,
443 stdin_port=self.stdin_port,
437 stdin_port=self.stdin_port,
444 hb_port=self.hb_port,
438 hb_port=self.hb_port,
445 control_port=self.control_port,
439 control_port=self.control_port,
446 signature_scheme=self.session.signature_scheme,
440 signature_scheme=self.session.signature_scheme,
447 key=self.session.key,
441 key=self.session.key,
448 )
442 )
449
443
450 def cleanup_connection_file(self):
444 def cleanup_connection_file(self):
451 """Cleanup connection file *if we wrote it*
445 """Cleanup connection file *if we wrote it*
452
446
453 Will not raise if the connection file was already removed somehow.
447 Will not raise if the connection file was already removed somehow.
454 """
448 """
455 if self._connection_file_written:
449 if self._connection_file_written:
456 # cleanup connection files on full shutdown of kernel we started
450 # cleanup connection files on full shutdown of kernel we started
457 self._connection_file_written = False
451 self._connection_file_written = False
458 try:
452 try:
459 os.remove(self.connection_file)
453 os.remove(self.connection_file)
460 except (IOError, OSError, AttributeError):
454 except (IOError, OSError, AttributeError):
461 pass
455 pass
462
456
463 def cleanup_ipc_files(self):
457 def cleanup_ipc_files(self):
464 """Cleanup ipc files if we wrote them."""
458 """Cleanup ipc files if we wrote them."""
465 if self.transport != 'ipc':
459 if self.transport != 'ipc':
466 return
460 return
467 for port in self.ports:
461 for port in self.ports:
468 ipcfile = "%s-%i" % (self.ip, port)
462 ipcfile = "%s-%i" % (self.ip, port)
469 try:
463 try:
470 os.remove(ipcfile)
464 os.remove(ipcfile)
471 except (IOError, OSError):
465 except (IOError, OSError):
472 pass
466 pass
473
467
474 def write_connection_file(self):
468 def write_connection_file(self):
475 """Write connection info to JSON dict in self.connection_file."""
469 """Write connection info to JSON dict in self.connection_file."""
476 if self._connection_file_written and os.path.exists(self.connection_file):
470 if self._connection_file_written and os.path.exists(self.connection_file):
477 return
471 return
478
472
479 self.connection_file, cfg = write_connection_file(self.connection_file,
473 self.connection_file, cfg = write_connection_file(self.connection_file,
480 transport=self.transport, ip=self.ip, key=self.session.key,
474 transport=self.transport, ip=self.ip, key=self.session.key,
481 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
475 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
482 shell_port=self.shell_port, hb_port=self.hb_port,
476 shell_port=self.shell_port, hb_port=self.hb_port,
483 control_port=self.control_port,
477 control_port=self.control_port,
484 signature_scheme=self.session.signature_scheme,
478 signature_scheme=self.session.signature_scheme,
485 )
479 )
486 # write_connection_file also sets default ports:
480 # write_connection_file also sets default ports:
487 for name in port_names:
481 for name in port_names:
488 setattr(self, name, cfg[name])
482 setattr(self, name, cfg[name])
489
483
490 self._connection_file_written = True
484 self._connection_file_written = True
491
485
492 def load_connection_file(self):
486 def load_connection_file(self):
493 """Load connection info from JSON dict in self.connection_file."""
487 """Load connection info from JSON dict in self.connection_file."""
488 self.log.debug(u"Loading connection file %s", self.connection_file)
494 with open(self.connection_file) as f:
489 with open(self.connection_file) as f:
495 cfg = json.loads(f.read())
490 cfg = json.load(f)
496
497 self.transport = cfg.get('transport', 'tcp')
491 self.transport = cfg.get('transport', 'tcp')
498 self.ip = cfg['ip']
492 self.ip = cfg.get('ip', localhost())
493
499 for name in port_names:
494 for name in port_names:
500 setattr(self, name, cfg[name])
495 if getattr(self, name) == 0 and name in cfg:
496 # not overridden by config or cl_args
497 setattr(self, name, cfg[name])
501 if 'key' in cfg:
498 if 'key' in cfg:
502 self.session.key = str_to_bytes(cfg['key'])
499 self.config.Session.key = str_to_bytes(cfg['key'])
503 if cfg.get('signature_scheme'):
500 if 'signature_scheme' in cfg:
504 self.session.signature_scheme = cfg['signature_scheme']
501 self.config.Session.signature_scheme = cfg['signature_scheme']
505
506 #--------------------------------------------------------------------------
502 #--------------------------------------------------------------------------
507 # Creating connected sockets
503 # Creating connected sockets
508 #--------------------------------------------------------------------------
504 #--------------------------------------------------------------------------
509
505
510 def _make_url(self, channel):
506 def _make_url(self, channel):
511 """Make a ZeroMQ URL for a given channel."""
507 """Make a ZeroMQ URL for a given channel."""
512 transport = self.transport
508 transport = self.transport
513 ip = self.ip
509 ip = self.ip
514 port = getattr(self, '%s_port' % channel)
510 port = getattr(self, '%s_port' % channel)
515
511
516 if transport == 'tcp':
512 if transport == 'tcp':
517 return "tcp://%s:%i" % (ip, port)
513 return "tcp://%s:%i" % (ip, port)
518 else:
514 else:
519 return "%s://%s-%s" % (transport, ip, port)
515 return "%s://%s-%s" % (transport, ip, port)
520
516
521 def _create_connected_socket(self, channel, identity=None):
517 def _create_connected_socket(self, channel, identity=None):
522 """Create a zmq Socket and connect it to the kernel."""
518 """Create a zmq Socket and connect it to the kernel."""
523 url = self._make_url(channel)
519 url = self._make_url(channel)
524 socket_type = channel_socket_types[channel]
520 socket_type = channel_socket_types[channel]
525 self.log.debug("Connecting to: %s" % url)
521 self.log.debug("Connecting to: %s" % url)
526 sock = self.context.socket(socket_type)
522 sock = self.context.socket(socket_type)
527 if identity:
523 if identity:
528 sock.identity = identity
524 sock.identity = identity
529 sock.connect(url)
525 sock.connect(url)
530 return sock
526 return sock
531
527
532 def connect_iopub(self, identity=None):
528 def connect_iopub(self, identity=None):
533 """return zmq Socket connected to the IOPub channel"""
529 """return zmq Socket connected to the IOPub channel"""
534 sock = self._create_connected_socket('iopub', identity=identity)
530 sock = self._create_connected_socket('iopub', identity=identity)
535 sock.setsockopt(zmq.SUBSCRIBE, b'')
531 sock.setsockopt(zmq.SUBSCRIBE, b'')
536 return sock
532 return sock
537
533
538 def connect_shell(self, identity=None):
534 def connect_shell(self, identity=None):
539 """return zmq Socket connected to the Shell channel"""
535 """return zmq Socket connected to the Shell channel"""
540 return self._create_connected_socket('shell', identity=identity)
536 return self._create_connected_socket('shell', identity=identity)
541
537
542 def connect_stdin(self, identity=None):
538 def connect_stdin(self, identity=None):
543 """return zmq Socket connected to the StdIn channel"""
539 """return zmq Socket connected to the StdIn channel"""
544 return self._create_connected_socket('stdin', identity=identity)
540 return self._create_connected_socket('stdin', identity=identity)
545
541
546 def connect_hb(self, identity=None):
542 def connect_hb(self, identity=None):
547 """return zmq Socket connected to the Heartbeat channel"""
543 """return zmq Socket connected to the Heartbeat channel"""
548 return self._create_connected_socket('hb', identity=identity)
544 return self._create_connected_socket('hb', identity=identity)
549
545
550 def connect_control(self, identity=None):
546 def connect_control(self, identity=None):
551 """return zmq Socket connected to the Heartbeat channel"""
547 """return zmq Socket connected to the Heartbeat channel"""
552 return self._create_connected_socket('control', identity=identity)
548 return self._create_connected_socket('control', identity=identity)
553
549
554
550
555 __all__ = [
551 __all__ = [
556 'write_connection_file',
552 'write_connection_file',
557 'get_connection_file',
553 'get_connection_file',
558 'find_connection_file',
554 'find_connection_file',
559 'get_connection_info',
555 'get_connection_info',
560 'connect_qtconsole',
556 'connect_qtconsole',
561 'tunnel_to_kernel',
557 'tunnel_to_kernel',
562 ]
558 ]
General Comments 0
You need to be logged in to leave comments. Login now