##// END OF EJS Templates
Merge pull request #5760 from ivanov/connection-mixin...
Min RK -
r16508:3403b188 merge
parent child Browse files
Show More
@@ -1,399 +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
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, 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,
159 help="set the heartbeat port [default: random]")
160 shell_port = Int(0, config=True,
161 help="set the shell (ROUTER) port [default: random]")
162 iopub_port = Int(0, config=True,
163 help="set the iopub (PUB) port [default: random]")
164 stdin_port = Int(0, config=True,
165 help="set the stdin (DEALER) port [default: random]")
166 connection_file = Unicode('', config=True,
167 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
168
169 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
171 of the current profile, but can be specified by absolute path.
172 """)
173 def _connection_file_default(self):
147 def _connection_file_default(self):
174 return 'kernel-%i.json' % os.getpid()
148 return 'kernel-%i.json' % os.getpid()
175
149
176 existing = CUnicode('', config=True,
150 existing = CUnicode('', config=True,
177 help="""Connect to an already running kernel""")
151 help="""Connect to an already running kernel""")
178
152
179 kernel_name = Unicode('python', config=True,
153 kernel_name = Unicode('python', config=True,
180 help="""The name of the default kernel to start.""")
154 help="""The name of the default kernel to start.""")
181
155
182 confirm_exit = CBool(True, config=True,
156 confirm_exit = CBool(True, config=True,
183 help="""
157 help="""
184 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',
185 to force a direct exit without any confirmation.""",
159 to force a direct exit without any confirmation.""",
186 )
160 )
187
161
188
162
189 def build_kernel_argv(self, argv=None):
163 def build_kernel_argv(self, argv=None):
190 """build argv to be passed to kernel subprocess"""
164 """build argv to be passed to kernel subprocess"""
191 if argv is None:
165 if argv is None:
192 argv = sys.argv[1:]
166 argv = sys.argv[1:]
193 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)
194 # kernel should inherit default config file from frontend
168 # kernel should inherit default config file from frontend
195 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
169 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
196
170
197 def init_connection_file(self):
171 def init_connection_file(self):
198 """find the connection file, and load the info if found.
172 """find the connection file, and load the info if found.
199
173
200 The current working directory and the current profile's security
174 The current working directory and the current profile's security
201 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
202 absolute path.
176 absolute path.
203
177
204 When attempting to connect to an existing kernel and the `--existing`
178 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
179 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
180 fileglob, and the matching file in the current profile's security dir
207 with the latest access time will be used.
181 with the latest access time will be used.
208
182
209 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*
210 to the connection file, never just its name.
184 to the connection file, never just its name.
211 """
185 """
212 if self.existing:
186 if self.existing:
213 try:
187 try:
214 cf = find_connection_file(self.existing)
188 cf = find_connection_file(self.existing)
215 except Exception:
189 except Exception:
216 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)
217 self.exit(1)
191 self.exit(1)
218 self.log.debug("Connecting to existing kernel: %s" % cf)
192 self.log.debug("Connecting to existing kernel: %s" % cf)
219 self.connection_file = cf
193 self.connection_file = cf
220 else:
194 else:
221 # not existing, check if we are going to write the file
195 # 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
196 # and ensure that self.connection_file is a full path, not just the shortname
223 try:
197 try:
224 cf = find_connection_file(self.connection_file)
198 cf = find_connection_file(self.connection_file)
225 except Exception:
199 except Exception:
226 # file might not exist
200 # file might not exist
227 if self.connection_file == os.path.basename(self.connection_file):
201 if self.connection_file == os.path.basename(self.connection_file):
228 # just shortname, put it in security dir
202 # just shortname, put it in security dir
229 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)
230 else:
204 else:
231 cf = self.connection_file
205 cf = self.connection_file
232 self.connection_file = cf
206 self.connection_file = cf
207 try:
208 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
209 except IOError:
210 self.log.debug("Connection File not found: %s", self.connection_file)
211 return
233
212
234 # should load_connection_file only be used for existing?
213 # should load_connection_file only be used for existing?
235 # as it is now, this allows reusing ports if an existing
214 # as it is now, this allows reusing ports if an existing
236 # file is requested
215 # file is requested
237 try:
216 try:
238 self.load_connection_file()
217 self.load_connection_file()
239 except Exception:
218 except Exception:
240 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)
241 self.exit(1)
220 self.exit(1)
242
221
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):
222 def init_ssh(self):
269 """set up ssh tunnels, if needed."""
223 """set up ssh tunnels, if needed."""
270 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):
271 return
225 return
272 self.load_connection_file()
226 self.load_connection_file()
273
227
274 transport = self.transport
228 transport = self.transport
275 ip = self.ip
229 ip = self.ip
276
230
277 if transport != 'tcp':
231 if transport != 'tcp':
278 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)
279 sys.exit(-1)
233 sys.exit(-1)
280
234
281 if self.sshkey and not self.sshserver:
235 if self.sshkey and not self.sshserver:
282 # specifying just the key implies that we are connecting directly
236 # specifying just the key implies that we are connecting directly
283 self.sshserver = ip
237 self.sshserver = ip
284 ip = localhost()
238 ip = localhost()
285
239
286 # build connection dict for tunnels:
240 # build connection dict for tunnels:
287 info = dict(ip=ip,
241 info = dict(ip=ip,
288 shell_port=self.shell_port,
242 shell_port=self.shell_port,
289 iopub_port=self.iopub_port,
243 iopub_port=self.iopub_port,
290 stdin_port=self.stdin_port,
244 stdin_port=self.stdin_port,
291 hb_port=self.hb_port
245 hb_port=self.hb_port
292 )
246 )
293
247
294 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))
295
249
296 # 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:
297 self.ip = localhost()
251 self.ip = localhost()
298 try:
252 try:
299 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
253 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
300 except:
254 except:
301 # even catch KeyboardInterrupt
255 # even catch KeyboardInterrupt
302 self.log.error("Could not setup tunnels", exc_info=True)
256 self.log.error("Could not setup tunnels", exc_info=True)
303 self.exit(1)
257 self.exit(1)
304
258
305 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
306
260
307 cf = self.connection_file
261 cf = self.connection_file
308 base,ext = os.path.splitext(cf)
262 base,ext = os.path.splitext(cf)
309 base = os.path.basename(base)
263 base = os.path.basename(base)
310 self.connection_file = os.path.basename(base)+'-ssh'+ext
264 self.connection_file = os.path.basename(base)+'-ssh'+ext
311 self.log.info("To connect another client via this tunnel, use:")
265 self.log.info("To connect another client via this tunnel, use:")
312 self.log.info("--existing %s" % self.connection_file)
266 self.log.info("--existing %s" % self.connection_file)
313
267
314 def _new_connection_file(self):
268 def _new_connection_file(self):
315 cf = ''
269 cf = ''
316 while not cf:
270 while not cf:
317 # 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
318 # 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
319 # kernels can subclass.
273 # kernels can subclass.
320 ident = str(uuid.uuid4()).split('-')[-1]
274 ident = str(uuid.uuid4()).split('-')[-1]
321 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)
322 # only keep if it's actually new. Protect against unlikely collision
276 # only keep if it's actually new. Protect against unlikely collision
323 # in 48b random search space
277 # in 48b random search space
324 cf = cf if not os.path.exists(cf) else ''
278 cf = cf if not os.path.exists(cf) else ''
325 return cf
279 return cf
326
280
327 def init_kernel_manager(self):
281 def init_kernel_manager(self):
328 # Don't let Qt or ZMQ swallow KeyboardInterupts.
282 # Don't let Qt or ZMQ swallow KeyboardInterupts.
329 if self.existing:
283 if self.existing:
330 self.kernel_manager = None
284 self.kernel_manager = None
331 return
285 return
332 signal.signal(signal.SIGINT, signal.SIG_DFL)
286 signal.signal(signal.SIGINT, signal.SIG_DFL)
333
287
334 # Create a KernelManager and start a kernel.
288 # Create a KernelManager and start a kernel.
335 try:
289 try:
336 self.kernel_manager = self.kernel_manager_class(
290 self.kernel_manager = self.kernel_manager_class(
337 ip=self.ip,
291 ip=self.ip,
338 transport=self.transport,
292 transport=self.transport,
339 shell_port=self.shell_port,
293 shell_port=self.shell_port,
340 iopub_port=self.iopub_port,
294 iopub_port=self.iopub_port,
341 stdin_port=self.stdin_port,
295 stdin_port=self.stdin_port,
342 hb_port=self.hb_port,
296 hb_port=self.hb_port,
343 connection_file=self.connection_file,
297 connection_file=self.connection_file,
344 kernel_name=self.kernel_name,
298 kernel_name=self.kernel_name,
345 parent=self,
299 parent=self,
346 ipython_dir=self.ipython_dir,
300 ipython_dir=self.ipython_dir,
347 )
301 )
348 except NoSuchKernel:
302 except NoSuchKernel:
349 self.log.critical("Could not find kernel %s", self.kernel_name)
303 self.log.critical("Could not find kernel %s", self.kernel_name)
350 self.exit(1)
304 self.exit(1)
351
305
352 self.kernel_manager.client_factory = self.kernel_client_class
306 self.kernel_manager.client_factory = self.kernel_client_class
353 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
307 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
354 atexit.register(self.kernel_manager.cleanup_ipc_files)
308 atexit.register(self.kernel_manager.cleanup_ipc_files)
355
309
356 if self.sshserver:
310 if self.sshserver:
357 # ssh, write new connection file
311 # ssh, write new connection file
358 self.kernel_manager.write_connection_file()
312 self.kernel_manager.write_connection_file()
359
313
360 # in case KM defaults / ssh writing changes things:
314 # in case KM defaults / ssh writing changes things:
361 km = self.kernel_manager
315 km = self.kernel_manager
362 self.shell_port=km.shell_port
316 self.shell_port=km.shell_port
363 self.iopub_port=km.iopub_port
317 self.iopub_port=km.iopub_port
364 self.stdin_port=km.stdin_port
318 self.stdin_port=km.stdin_port
365 self.hb_port=km.hb_port
319 self.hb_port=km.hb_port
366 self.connection_file = km.connection_file
320 self.connection_file = km.connection_file
367
321
368 atexit.register(self.kernel_manager.cleanup_connection_file)
322 atexit.register(self.kernel_manager.cleanup_connection_file)
369
323
370 def init_kernel_client(self):
324 def init_kernel_client(self):
371 if self.kernel_manager is not None:
325 if self.kernel_manager is not None:
372 self.kernel_client = self.kernel_manager.client()
326 self.kernel_client = self.kernel_manager.client()
373 else:
327 else:
374 self.kernel_client = self.kernel_client_class(
328 self.kernel_client = self.kernel_client_class(
375 ip=self.ip,
329 ip=self.ip,
376 transport=self.transport,
330 transport=self.transport,
377 shell_port=self.shell_port,
331 shell_port=self.shell_port,
378 iopub_port=self.iopub_port,
332 iopub_port=self.iopub_port,
379 stdin_port=self.stdin_port,
333 stdin_port=self.stdin_port,
380 hb_port=self.hb_port,
334 hb_port=self.hb_port,
381 connection_file=self.connection_file,
335 connection_file=self.connection_file,
382 parent=self,
336 parent=self,
383 )
337 )
384
338
385 self.kernel_client.start_channels()
339 self.kernel_client.start_channels()
386
340
387
341
388
342
389 def initialize(self, argv=None):
343 def initialize(self, argv=None):
390 """
344 """
391 Classes which mix this class in should call:
345 Classes which mix this class in should call:
392 IPythonConsoleApp.initialize(self,argv)
346 IPythonConsoleApp.initialize(self,argv)
393 """
347 """
394 self.init_connection_file()
348 self.init_connection_file()
395 default_secure(self.config)
349 default_secure(self.config)
396 self.init_ssh()
350 self.init_ssh()
397 self.init_kernel_manager()
351 self.init_kernel_manager()
398 self.init_kernel_client()
352 self.init_kernel_client()
399
353
@@ -1,562 +1,569 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 Authors:
3 Notable contents:
4
4 - ConnectionFileMixin class
5 * Min Ragan-Kelley
5 encapsulates the logic related to writing and reading connections files.
6
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('', config=True,
390 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
391
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
394 of the current profile, but can be specified by absolute path.
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 shell_port = Integer(0)
422 hb_port = Integer(0, config=True,
423 iopub_port = Integer(0)
423 help="set the heartbeat port [default: random]")
424 stdin_port = Integer(0)
424 shell_port = Integer(0, config=True,
425 control_port = Integer(0)
425 help="set the shell (ROUTER) port [default: random]")
426 hb_port = Integer(0)
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."""
499 self.log.debug(u"Loading connection file %s", self.connection_file)
494 with open(self.connection_file) as f:
500 with open(self.connection_file) as f:
495 cfg = json.loads(f.read())
501 cfg = json.load(f)
496
502 self.transport = cfg.get('transport', self.transport)
497 self.transport = cfg.get('transport', 'tcp')
503 self.ip = cfg.get('ip', self._ip_default())
498 self.ip = cfg['ip']
504
499 for name in port_names:
505 for name in port_names:
500 setattr(self, name, cfg[name])
506 if getattr(self, name) == 0 and name in cfg:
507 # not overridden by config or cl_args
508 setattr(self, name, cfg[name])
501 if 'key' in cfg:
509 if 'key' in cfg:
502 self.session.key = str_to_bytes(cfg['key'])
510 self.config.Session.key = str_to_bytes(cfg['key'])
503 if cfg.get('signature_scheme'):
511 if 'signature_scheme' in cfg:
504 self.session.signature_scheme = cfg['signature_scheme']
512 self.config.Session.signature_scheme = cfg['signature_scheme']
505
506 #--------------------------------------------------------------------------
513 #--------------------------------------------------------------------------
507 # Creating connected sockets
514 # Creating connected sockets
508 #--------------------------------------------------------------------------
515 #--------------------------------------------------------------------------
509
516
510 def _make_url(self, channel):
517 def _make_url(self, channel):
511 """Make a ZeroMQ URL for a given channel."""
518 """Make a ZeroMQ URL for a given channel."""
512 transport = self.transport
519 transport = self.transport
513 ip = self.ip
520 ip = self.ip
514 port = getattr(self, '%s_port' % channel)
521 port = getattr(self, '%s_port' % channel)
515
522
516 if transport == 'tcp':
523 if transport == 'tcp':
517 return "tcp://%s:%i" % (ip, port)
524 return "tcp://%s:%i" % (ip, port)
518 else:
525 else:
519 return "%s://%s-%s" % (transport, ip, port)
526 return "%s://%s-%s" % (transport, ip, port)
520
527
521 def _create_connected_socket(self, channel, identity=None):
528 def _create_connected_socket(self, channel, identity=None):
522 """Create a zmq Socket and connect it to the kernel."""
529 """Create a zmq Socket and connect it to the kernel."""
523 url = self._make_url(channel)
530 url = self._make_url(channel)
524 socket_type = channel_socket_types[channel]
531 socket_type = channel_socket_types[channel]
525 self.log.debug("Connecting to: %s" % url)
532 self.log.debug("Connecting to: %s" % url)
526 sock = self.context.socket(socket_type)
533 sock = self.context.socket(socket_type)
527 if identity:
534 if identity:
528 sock.identity = identity
535 sock.identity = identity
529 sock.connect(url)
536 sock.connect(url)
530 return sock
537 return sock
531
538
532 def connect_iopub(self, identity=None):
539 def connect_iopub(self, identity=None):
533 """return zmq Socket connected to the IOPub channel"""
540 """return zmq Socket connected to the IOPub channel"""
534 sock = self._create_connected_socket('iopub', identity=identity)
541 sock = self._create_connected_socket('iopub', identity=identity)
535 sock.setsockopt(zmq.SUBSCRIBE, b'')
542 sock.setsockopt(zmq.SUBSCRIBE, b'')
536 return sock
543 return sock
537
544
538 def connect_shell(self, identity=None):
545 def connect_shell(self, identity=None):
539 """return zmq Socket connected to the Shell channel"""
546 """return zmq Socket connected to the Shell channel"""
540 return self._create_connected_socket('shell', identity=identity)
547 return self._create_connected_socket('shell', identity=identity)
541
548
542 def connect_stdin(self, identity=None):
549 def connect_stdin(self, identity=None):
543 """return zmq Socket connected to the StdIn channel"""
550 """return zmq Socket connected to the StdIn channel"""
544 return self._create_connected_socket('stdin', identity=identity)
551 return self._create_connected_socket('stdin', identity=identity)
545
552
546 def connect_hb(self, identity=None):
553 def connect_hb(self, identity=None):
547 """return zmq Socket connected to the Heartbeat channel"""
554 """return zmq Socket connected to the Heartbeat channel"""
548 return self._create_connected_socket('hb', identity=identity)
555 return self._create_connected_socket('hb', identity=identity)
549
556
550 def connect_control(self, identity=None):
557 def connect_control(self, identity=None):
551 """return zmq Socket connected to the Heartbeat channel"""
558 """return zmq Socket connected to the Heartbeat channel"""
552 return self._create_connected_socket('control', identity=identity)
559 return self._create_connected_socket('control', identity=identity)
553
560
554
561
555 __all__ = [
562 __all__ = [
556 'write_connection_file',
563 'write_connection_file',
557 'get_connection_file',
564 'get_connection_file',
558 'find_connection_file',
565 'find_connection_file',
559 'get_connection_info',
566 'get_connection_info',
560 'connect_qtconsole',
567 'connect_qtconsole',
561 'tunnel_to_kernel',
568 'tunnel_to_kernel',
562 ]
569 ]
@@ -1,473 +1,411 b''
1 """An Application for launching a kernel
1 """An Application for launching a kernel
2
3 Authors
4 -------
5 * MinRK
6 """
2 """
7 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
8 # Copyright (C) 2011 The IPython Development Team
4 # Distributed under the terms of the Modified BSD License.
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING.txt, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
5
14 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
15 # Imports
7 # Imports
16 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
17
9
18 from __future__ import print_function
10 from __future__ import print_function
19
11
20 # Standard library imports
12 # Standard library imports
21 import atexit
13 import atexit
22 import json
23 import os
14 import os
24 import sys
15 import sys
25 import signal
16 import signal
26
17
27 # System library imports
18 # System library imports
28 import zmq
19 import zmq
29 from zmq.eventloop import ioloop
20 from zmq.eventloop import ioloop
30 from zmq.eventloop.zmqstream import ZMQStream
21 from zmq.eventloop.zmqstream import ZMQStream
31
22
32 # IPython imports
23 # IPython imports
33 from IPython.core.ultratb import FormattedTB
24 from IPython.core.ultratb import FormattedTB
34 from IPython.core.application import (
25 from IPython.core.application import (
35 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
26 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
36 )
27 )
37 from IPython.core.profiledir import ProfileDir
28 from IPython.core.profiledir import ProfileDir
38 from IPython.core.shellapp import (
29 from IPython.core.shellapp import (
39 InteractiveShellApp, shell_flags, shell_aliases
30 InteractiveShellApp, shell_flags, shell_aliases
40 )
31 )
41 from IPython.utils import io
32 from IPython.utils import io
42 from IPython.utils.localinterfaces import localhost
43 from IPython.utils.path import filefind
33 from IPython.utils.path import filefind
44 from IPython.utils.py3compat import str_to_bytes
45 from IPython.utils.traitlets import (
34 from IPython.utils.traitlets import (
46 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
35 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName,
47 DottedObjectName,
48 )
36 )
49 from IPython.utils.importstring import import_item
37 from IPython.utils.importstring import import_item
50 from IPython.kernel import write_connection_file
38 from IPython.kernel import write_connection_file
39 from IPython.kernel.connect import ConnectionFileMixin
51
40
52 # local imports
41 # local imports
53 from .heartbeat import Heartbeat
42 from .heartbeat import Heartbeat
54 from .ipkernel import Kernel
43 from .ipkernel import Kernel
55 from .parentpoller import ParentPollerUnix, ParentPollerWindows
44 from .parentpoller import ParentPollerUnix, ParentPollerWindows
56 from .session import (
45 from .session import (
57 Session, session_flags, session_aliases, default_secure,
46 Session, session_flags, session_aliases, default_secure,
58 )
47 )
59 from .zmqshell import ZMQInteractiveShell
48 from .zmqshell import ZMQInteractiveShell
60
49
61 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
62 # Flags and Aliases
51 # Flags and Aliases
63 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
64
53
65 kernel_aliases = dict(base_aliases)
54 kernel_aliases = dict(base_aliases)
66 kernel_aliases.update({
55 kernel_aliases.update({
67 'ip' : 'IPKernelApp.ip',
56 'ip' : 'IPKernelApp.ip',
68 'hb' : 'IPKernelApp.hb_port',
57 'hb' : 'IPKernelApp.hb_port',
69 'shell' : 'IPKernelApp.shell_port',
58 'shell' : 'IPKernelApp.shell_port',
70 'iopub' : 'IPKernelApp.iopub_port',
59 'iopub' : 'IPKernelApp.iopub_port',
71 'stdin' : 'IPKernelApp.stdin_port',
60 'stdin' : 'IPKernelApp.stdin_port',
72 'control' : 'IPKernelApp.control_port',
61 'control' : 'IPKernelApp.control_port',
73 'f' : 'IPKernelApp.connection_file',
62 'f' : 'IPKernelApp.connection_file',
74 'parent': 'IPKernelApp.parent_handle',
63 'parent': 'IPKernelApp.parent_handle',
75 'transport': 'IPKernelApp.transport',
64 'transport': 'IPKernelApp.transport',
76 })
65 })
77 if sys.platform.startswith('win'):
66 if sys.platform.startswith('win'):
78 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
67 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
79
68
80 kernel_flags = dict(base_flags)
69 kernel_flags = dict(base_flags)
81 kernel_flags.update({
70 kernel_flags.update({
82 'no-stdout' : (
71 'no-stdout' : (
83 {'IPKernelApp' : {'no_stdout' : True}},
72 {'IPKernelApp' : {'no_stdout' : True}},
84 "redirect stdout to the null device"),
73 "redirect stdout to the null device"),
85 'no-stderr' : (
74 'no-stderr' : (
86 {'IPKernelApp' : {'no_stderr' : True}},
75 {'IPKernelApp' : {'no_stderr' : True}},
87 "redirect stderr to the null device"),
76 "redirect stderr to the null device"),
88 'pylab' : (
77 'pylab' : (
89 {'IPKernelApp' : {'pylab' : 'auto'}},
78 {'IPKernelApp' : {'pylab' : 'auto'}},
90 """Pre-load matplotlib and numpy for interactive use with
79 """Pre-load matplotlib and numpy for interactive use with
91 the default matplotlib backend."""),
80 the default matplotlib backend."""),
92 })
81 })
93
82
94 # inherit flags&aliases for any IPython shell apps
83 # inherit flags&aliases for any IPython shell apps
95 kernel_aliases.update(shell_aliases)
84 kernel_aliases.update(shell_aliases)
96 kernel_flags.update(shell_flags)
85 kernel_flags.update(shell_flags)
97
86
98 # inherit flags&aliases for Sessions
87 # inherit flags&aliases for Sessions
99 kernel_aliases.update(session_aliases)
88 kernel_aliases.update(session_aliases)
100 kernel_flags.update(session_flags)
89 kernel_flags.update(session_flags)
101
90
102 _ctrl_c_message = """\
91 _ctrl_c_message = """\
103 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
92 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
104
93
105 To exit, you will have to explicitly quit this process, by either sending
94 To exit, you will have to explicitly quit this process, by either sending
106 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
95 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
107
96
108 To read more about this, see https://github.com/ipython/ipython/issues/2049
97 To read more about this, see https://github.com/ipython/ipython/issues/2049
109
98
110 """
99 """
111
100
112 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
113 # Application class for starting an IPython Kernel
102 # Application class for starting an IPython Kernel
114 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
115
104
116 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):
105 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
106 ConnectionFileMixin):
117 name='ipkernel'
107 name='ipkernel'
118 aliases = Dict(kernel_aliases)
108 aliases = Dict(kernel_aliases)
119 flags = Dict(kernel_flags)
109 flags = Dict(kernel_flags)
120 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
110 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
121 # the kernel class, as an importstring
111 # the kernel class, as an importstring
122 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
112 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
123 help="""The Kernel subclass to be used.
113 help="""The Kernel subclass to be used.
124
114
125 This should allow easy re-use of the IPKernelApp entry point
115 This should allow easy re-use of the IPKernelApp entry point
126 to configure and launch kernels other than IPython's own.
116 to configure and launch kernels other than IPython's own.
127 """)
117 """)
128 kernel = Any()
118 kernel = Any()
129 poller = Any() # don't restrict this even though current pollers are all Threads
119 poller = Any() # don't restrict this even though current pollers are all Threads
130 heartbeat = Instance(Heartbeat)
120 heartbeat = Instance(Heartbeat)
131 session = Instance('IPython.kernel.zmq.session.Session')
121 session = Instance('IPython.kernel.zmq.session.Session')
132 ports = Dict()
122 ports = Dict()
133
123
134 # ipkernel doesn't get its own config file
124 # ipkernel doesn't get its own config file
135 def _config_file_name_default(self):
125 def _config_file_name_default(self):
136 return 'ipython_config.py'
126 return 'ipython_config.py'
137
127
138 # inherit config file name from parent:
128 # inherit config file name from parent:
139 parent_appname = Unicode(config=True)
129 parent_appname = Unicode(config=True)
140 def _parent_appname_changed(self, name, old, new):
130 def _parent_appname_changed(self, name, old, new):
141 if self.config_file_specified:
131 if self.config_file_specified:
142 # it was manually specified, ignore
132 # it was manually specified, ignore
143 return
133 return
144 self.config_file_name = new.replace('-','_') + u'_config.py'
134 self.config_file_name = new.replace('-','_') + u'_config.py'
145 # don't let this count as specifying the config file
135 # don't let this count as specifying the config file
146 self.config_file_specified.remove(self.config_file_name)
136 self.config_file_specified.remove(self.config_file_name)
147
137
148 # connection info:
138 # connection info:
149 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
150 ip = Unicode(config=True,
151 help="Set the IP or interface on which the kernel will listen.")
152 def _ip_default(self):
153 if self.transport == 'ipc':
154 if self.connection_file:
155 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
156 else:
157 return 'kernel-ipc'
158 else:
159 return localhost()
160
161 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
162 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
163 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
164 stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
165 control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")
166 connection_file = Unicode('', config=True,
167 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
168
139
169 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
171 of the current profile, but can be specified by absolute path.
172 """)
173 @property
140 @property
174 def abs_connection_file(self):
141 def abs_connection_file(self):
175 if os.path.basename(self.connection_file) == self.connection_file:
142 if os.path.basename(self.connection_file) == self.connection_file:
176 return os.path.join(self.profile_dir.security_dir, self.connection_file)
143 return os.path.join(self.profile_dir.security_dir, self.connection_file)
177 else:
144 else:
178 return self.connection_file
145 return self.connection_file
179
146
180
147
181 # streams, etc.
148 # streams, etc.
182 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
149 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
183 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
150 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
184 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
151 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
185 config=True, help="The importstring for the OutStream factory")
152 config=True, help="The importstring for the OutStream factory")
186 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
153 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
187 config=True, help="The importstring for the DisplayHook factory")
154 config=True, help="The importstring for the DisplayHook factory")
188
155
189 # polling
156 # polling
190 parent_handle = Integer(0, config=True,
157 parent_handle = Integer(0, config=True,
191 help="""kill this process if its parent dies. On Windows, the argument
158 help="""kill this process if its parent dies. On Windows, the argument
192 specifies the HANDLE of the parent process, otherwise it is simply boolean.
159 specifies the HANDLE of the parent process, otherwise it is simply boolean.
193 """)
160 """)
194 interrupt = Integer(0, config=True,
161 interrupt = Integer(0, config=True,
195 help="""ONLY USED ON WINDOWS
162 help="""ONLY USED ON WINDOWS
196 Interrupt this process when the parent is signaled.
163 Interrupt this process when the parent is signaled.
197 """)
164 """)
198
165
199 def init_crash_handler(self):
166 def init_crash_handler(self):
200 # Install minimal exception handling
167 # Install minimal exception handling
201 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
168 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
202 ostream=sys.__stdout__)
169 ostream=sys.__stdout__)
203
170
204 def init_poller(self):
171 def init_poller(self):
205 if sys.platform == 'win32':
172 if sys.platform == 'win32':
206 if self.interrupt or self.parent_handle:
173 if self.interrupt or self.parent_handle:
207 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
174 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
208 elif self.parent_handle:
175 elif self.parent_handle:
209 self.poller = ParentPollerUnix()
176 self.poller = ParentPollerUnix()
210
177
211 def _bind_socket(self, s, port):
178 def _bind_socket(self, s, port):
212 iface = '%s://%s' % (self.transport, self.ip)
179 iface = '%s://%s' % (self.transport, self.ip)
213 if self.transport == 'tcp':
180 if self.transport == 'tcp':
214 if port <= 0:
181 if port <= 0:
215 port = s.bind_to_random_port(iface)
182 port = s.bind_to_random_port(iface)
216 else:
183 else:
217 s.bind("tcp://%s:%i" % (self.ip, port))
184 s.bind("tcp://%s:%i" % (self.ip, port))
218 elif self.transport == 'ipc':
185 elif self.transport == 'ipc':
219 if port <= 0:
186 if port <= 0:
220 port = 1
187 port = 1
221 path = "%s-%i" % (self.ip, port)
188 path = "%s-%i" % (self.ip, port)
222 while os.path.exists(path):
189 while os.path.exists(path):
223 port = port + 1
190 port = port + 1
224 path = "%s-%i" % (self.ip, port)
191 path = "%s-%i" % (self.ip, port)
225 else:
192 else:
226 path = "%s-%i" % (self.ip, port)
193 path = "%s-%i" % (self.ip, port)
227 s.bind("ipc://%s" % path)
194 s.bind("ipc://%s" % path)
228 return port
195 return port
229
196
230 def load_connection_file(self):
231 """load ip/port/hmac config from JSON connection file"""
232 try:
233 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
234 except IOError:
235 self.log.debug("Connection file not found: %s", self.connection_file)
236 # This means I own it, so I will clean it up:
237 atexit.register(self.cleanup_connection_file)
238 return
239 self.log.debug(u"Loading connection file %s", fname)
240 with open(fname) as f:
241 s = f.read()
242 cfg = json.loads(s)
243 self.transport = cfg.get('transport', self.transport)
244 if self.ip == self._ip_default() and 'ip' in cfg:
245 # not overridden by config or cl_args
246 self.ip = cfg['ip']
247 for channel in ('hb', 'shell', 'iopub', 'stdin', 'control'):
248 name = channel + '_port'
249 if getattr(self, name) == 0 and name in cfg:
250 # not overridden by config or cl_args
251 setattr(self, name, cfg[name])
252 if 'key' in cfg:
253 self.config.Session.key = str_to_bytes(cfg['key'])
254
255 def write_connection_file(self):
197 def write_connection_file(self):
256 """write connection info to JSON file"""
198 """write connection info to JSON file"""
257 cf = self.abs_connection_file
199 cf = self.abs_connection_file
258 self.log.debug("Writing connection file: %s", cf)
200 self.log.debug("Writing connection file: %s", cf)
259 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
201 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
260 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
202 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
261 iopub_port=self.iopub_port, control_port=self.control_port)
203 iopub_port=self.iopub_port, control_port=self.control_port)
262
204
263 def cleanup_connection_file(self):
205 def cleanup_connection_file(self):
264 cf = self.abs_connection_file
206 cf = self.abs_connection_file
265 self.log.debug("Cleaning up connection file: %s", cf)
207 self.log.debug("Cleaning up connection file: %s", cf)
266 try:
208 try:
267 os.remove(cf)
209 os.remove(cf)
268 except (IOError, OSError):
210 except (IOError, OSError):
269 pass
211 pass
270
212
271 self.cleanup_ipc_files()
213 self.cleanup_ipc_files()
272
214
273 def cleanup_ipc_files(self):
274 """cleanup ipc files if we wrote them"""
275 if self.transport != 'ipc':
276 return
277 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port, self.control_port):
278 ipcfile = "%s-%i" % (self.ip, port)
279 try:
280 os.remove(ipcfile)
281 except (IOError, OSError):
282 pass
283
284 def init_connection_file(self):
215 def init_connection_file(self):
285 if not self.connection_file:
216 if not self.connection_file:
286 self.connection_file = "kernel-%s.json"%os.getpid()
217 self.connection_file = "kernel-%s.json"%os.getpid()
287 try:
218 try:
219 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
220 except IOError:
221 self.log.debug("Connection file not found: %s", self.connection_file)
222 # This means I own it, so I will clean it up:
223 atexit.register(self.cleanup_connection_file)
224 return
225 try:
288 self.load_connection_file()
226 self.load_connection_file()
289 except Exception:
227 except Exception:
290 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
228 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
291 self.exit(1)
229 self.exit(1)
292
230
293 def init_sockets(self):
231 def init_sockets(self):
294 # Create a context, a session, and the kernel sockets.
232 # Create a context, a session, and the kernel sockets.
295 self.log.info("Starting the kernel at pid: %i", os.getpid())
233 self.log.info("Starting the kernel at pid: %i", os.getpid())
296 context = zmq.Context.instance()
234 context = zmq.Context.instance()
297 # Uncomment this to try closing the context.
235 # Uncomment this to try closing the context.
298 # atexit.register(context.term)
236 # atexit.register(context.term)
299
237
300 self.shell_socket = context.socket(zmq.ROUTER)
238 self.shell_socket = context.socket(zmq.ROUTER)
301 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
239 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
302 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
240 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
303
241
304 self.iopub_socket = context.socket(zmq.PUB)
242 self.iopub_socket = context.socket(zmq.PUB)
305 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
243 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
306 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
244 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
307
245
308 self.stdin_socket = context.socket(zmq.ROUTER)
246 self.stdin_socket = context.socket(zmq.ROUTER)
309 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
247 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
310 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
248 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
311
249
312 self.control_socket = context.socket(zmq.ROUTER)
250 self.control_socket = context.socket(zmq.ROUTER)
313 self.control_port = self._bind_socket(self.control_socket, self.control_port)
251 self.control_port = self._bind_socket(self.control_socket, self.control_port)
314 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
252 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
315
253
316 def init_heartbeat(self):
254 def init_heartbeat(self):
317 """start the heart beating"""
255 """start the heart beating"""
318 # heartbeat doesn't share context, because it mustn't be blocked
256 # heartbeat doesn't share context, because it mustn't be blocked
319 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
257 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
320 hb_ctx = zmq.Context()
258 hb_ctx = zmq.Context()
321 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
259 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
322 self.hb_port = self.heartbeat.port
260 self.hb_port = self.heartbeat.port
323 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
261 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
324 self.heartbeat.start()
262 self.heartbeat.start()
325
263
326 def log_connection_info(self):
264 def log_connection_info(self):
327 """display connection info, and store ports"""
265 """display connection info, and store ports"""
328 basename = os.path.basename(self.connection_file)
266 basename = os.path.basename(self.connection_file)
329 if basename == self.connection_file or \
267 if basename == self.connection_file or \
330 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
268 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
331 # use shortname
269 # use shortname
332 tail = basename
270 tail = basename
333 if self.profile != 'default':
271 if self.profile != 'default':
334 tail += " --profile %s" % self.profile
272 tail += " --profile %s" % self.profile
335 else:
273 else:
336 tail = self.connection_file
274 tail = self.connection_file
337 lines = [
275 lines = [
338 "To connect another client to this kernel, use:",
276 "To connect another client to this kernel, use:",
339 " --existing %s" % tail,
277 " --existing %s" % tail,
340 ]
278 ]
341 # log connection info
279 # log connection info
342 # info-level, so often not shown.
280 # info-level, so often not shown.
343 # frontends should use the %connect_info magic
281 # frontends should use the %connect_info magic
344 # to see the connection info
282 # to see the connection info
345 for line in lines:
283 for line in lines:
346 self.log.info(line)
284 self.log.info(line)
347 # also raw print to the terminal if no parent_handle (`ipython kernel`)
285 # also raw print to the terminal if no parent_handle (`ipython kernel`)
348 if not self.parent_handle:
286 if not self.parent_handle:
349 io.rprint(_ctrl_c_message)
287 io.rprint(_ctrl_c_message)
350 for line in lines:
288 for line in lines:
351 io.rprint(line)
289 io.rprint(line)
352
290
353 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
291 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
354 stdin=self.stdin_port, hb=self.hb_port,
292 stdin=self.stdin_port, hb=self.hb_port,
355 control=self.control_port)
293 control=self.control_port)
356
294
357 def init_session(self):
295 def init_session(self):
358 """create our session object"""
296 """create our session object"""
359 default_secure(self.config)
297 default_secure(self.config)
360 self.session = Session(parent=self, username=u'kernel')
298 self.session = Session(parent=self, username=u'kernel')
361
299
362 def init_blackhole(self):
300 def init_blackhole(self):
363 """redirects stdout/stderr to devnull if necessary"""
301 """redirects stdout/stderr to devnull if necessary"""
364 if self.no_stdout or self.no_stderr:
302 if self.no_stdout or self.no_stderr:
365 blackhole = open(os.devnull, 'w')
303 blackhole = open(os.devnull, 'w')
366 if self.no_stdout:
304 if self.no_stdout:
367 sys.stdout = sys.__stdout__ = blackhole
305 sys.stdout = sys.__stdout__ = blackhole
368 if self.no_stderr:
306 if self.no_stderr:
369 sys.stderr = sys.__stderr__ = blackhole
307 sys.stderr = sys.__stderr__ = blackhole
370
308
371 def init_io(self):
309 def init_io(self):
372 """Redirect input streams and set a display hook."""
310 """Redirect input streams and set a display hook."""
373 if self.outstream_class:
311 if self.outstream_class:
374 outstream_factory = import_item(str(self.outstream_class))
312 outstream_factory = import_item(str(self.outstream_class))
375 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
313 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
376 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
314 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
377 if self.displayhook_class:
315 if self.displayhook_class:
378 displayhook_factory = import_item(str(self.displayhook_class))
316 displayhook_factory = import_item(str(self.displayhook_class))
379 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
317 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
380
318
381 def init_signal(self):
319 def init_signal(self):
382 signal.signal(signal.SIGINT, signal.SIG_IGN)
320 signal.signal(signal.SIGINT, signal.SIG_IGN)
383
321
384 def init_kernel(self):
322 def init_kernel(self):
385 """Create the Kernel object itself"""
323 """Create the Kernel object itself"""
386 shell_stream = ZMQStream(self.shell_socket)
324 shell_stream = ZMQStream(self.shell_socket)
387 control_stream = ZMQStream(self.control_socket)
325 control_stream = ZMQStream(self.control_socket)
388
326
389 kernel_factory = import_item(str(self.kernel_class))
327 kernel_factory = import_item(str(self.kernel_class))
390
328
391 kernel = kernel_factory(parent=self, session=self.session,
329 kernel = kernel_factory(parent=self, session=self.session,
392 shell_streams=[shell_stream, control_stream],
330 shell_streams=[shell_stream, control_stream],
393 iopub_socket=self.iopub_socket,
331 iopub_socket=self.iopub_socket,
394 stdin_socket=self.stdin_socket,
332 stdin_socket=self.stdin_socket,
395 log=self.log,
333 log=self.log,
396 profile_dir=self.profile_dir,
334 profile_dir=self.profile_dir,
397 user_ns=self.user_ns,
335 user_ns=self.user_ns,
398 )
336 )
399 kernel.record_ports(self.ports)
337 kernel.record_ports(self.ports)
400 self.kernel = kernel
338 self.kernel = kernel
401
339
402 def init_gui_pylab(self):
340 def init_gui_pylab(self):
403 """Enable GUI event loop integration, taking pylab into account."""
341 """Enable GUI event loop integration, taking pylab into account."""
404
342
405 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
343 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
406 # to ensure that any exception is printed straight to stderr.
344 # to ensure that any exception is printed straight to stderr.
407 # Normally _showtraceback associates the reply with an execution,
345 # Normally _showtraceback associates the reply with an execution,
408 # which means frontends will never draw it, as this exception
346 # which means frontends will never draw it, as this exception
409 # is not associated with any execute request.
347 # is not associated with any execute request.
410
348
411 shell = self.shell
349 shell = self.shell
412 _showtraceback = shell._showtraceback
350 _showtraceback = shell._showtraceback
413 try:
351 try:
414 # replace pyerr-sending traceback with stderr
352 # replace pyerr-sending traceback with stderr
415 def print_tb(etype, evalue, stb):
353 def print_tb(etype, evalue, stb):
416 print ("GUI event loop or pylab initialization failed",
354 print ("GUI event loop or pylab initialization failed",
417 file=io.stderr)
355 file=io.stderr)
418 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
356 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
419 shell._showtraceback = print_tb
357 shell._showtraceback = print_tb
420 InteractiveShellApp.init_gui_pylab(self)
358 InteractiveShellApp.init_gui_pylab(self)
421 finally:
359 finally:
422 shell._showtraceback = _showtraceback
360 shell._showtraceback = _showtraceback
423
361
424 def init_shell(self):
362 def init_shell(self):
425 self.shell = self.kernel.shell
363 self.shell = self.kernel.shell
426 self.shell.configurables.append(self)
364 self.shell.configurables.append(self)
427
365
428 @catch_config_error
366 @catch_config_error
429 def initialize(self, argv=None):
367 def initialize(self, argv=None):
430 super(IPKernelApp, self).initialize(argv)
368 super(IPKernelApp, self).initialize(argv)
431 self.init_blackhole()
369 self.init_blackhole()
432 self.init_connection_file()
370 self.init_connection_file()
433 self.init_session()
371 self.init_session()
434 self.init_poller()
372 self.init_poller()
435 self.init_sockets()
373 self.init_sockets()
436 self.init_heartbeat()
374 self.init_heartbeat()
437 # writing/displaying connection info must be *after* init_sockets/heartbeat
375 # writing/displaying connection info must be *after* init_sockets/heartbeat
438 self.log_connection_info()
376 self.log_connection_info()
439 self.write_connection_file()
377 self.write_connection_file()
440 self.init_io()
378 self.init_io()
441 self.init_signal()
379 self.init_signal()
442 self.init_kernel()
380 self.init_kernel()
443 # shell init steps
381 # shell init steps
444 self.init_path()
382 self.init_path()
445 self.init_shell()
383 self.init_shell()
446 self.init_gui_pylab()
384 self.init_gui_pylab()
447 self.init_extensions()
385 self.init_extensions()
448 self.init_code()
386 self.init_code()
449 # flush stdout/stderr, so that anything written to these streams during
387 # flush stdout/stderr, so that anything written to these streams during
450 # initialization do not get associated with the first execution request
388 # initialization do not get associated with the first execution request
451 sys.stdout.flush()
389 sys.stdout.flush()
452 sys.stderr.flush()
390 sys.stderr.flush()
453
391
454 def start(self):
392 def start(self):
455 if self.poller is not None:
393 if self.poller is not None:
456 self.poller.start()
394 self.poller.start()
457 self.kernel.start()
395 self.kernel.start()
458 try:
396 try:
459 ioloop.IOLoop.instance().start()
397 ioloop.IOLoop.instance().start()
460 except KeyboardInterrupt:
398 except KeyboardInterrupt:
461 pass
399 pass
462
400
463 launch_new_instance = IPKernelApp.launch_instance
401 launch_new_instance = IPKernelApp.launch_instance
464
402
465 def main():
403 def main():
466 """Run an IPKernel as an application"""
404 """Run an IPKernel as an application"""
467 app = IPKernelApp.instance()
405 app = IPKernelApp.instance()
468 app.initialize()
406 app.initialize()
469 app.start()
407 app.start()
470
408
471
409
472 if __name__ == '__main__':
410 if __name__ == '__main__':
473 main()
411 main()
General Comments 0
You need to be logged in to leave comments. Login now