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