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