##// END OF EJS Templates
add missing KernelManager to ConsoleApp class list
MinRK -
Show More
@@ -1,361 +1,362
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/frontend/qt/console/qtconsoleapp.py
6 6
7 7 Authors:
8 8
9 9 * Evan Patterson
10 10 * Min RK
11 11 * Erik Tollerud
12 12 * Fernando Perez
13 13 * Bussonnier Matthias
14 14 * Thomas Kluyver
15 15 * Paul Ivanov
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # stdlib imports
24 24 import atexit
25 25 import json
26 26 import os
27 27 import shutil
28 28 import signal
29 29 import sys
30 30 import uuid
31 31
32 32
33 33 # Local imports
34 34 from IPython.config.application import boolean_flag
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.core.profiledir import ProfileDir
37 37 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
38 38 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
39 from IPython.zmq.kernelmanager import KernelManager
39 40 from IPython.utils.path import filefind
40 41 from IPython.utils.py3compat import str_to_bytes
41 42 from IPython.utils.traitlets import (
42 43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
43 44 )
44 45 from IPython.zmq.ipkernel import (
45 46 flags as ipkernel_flags,
46 47 aliases as ipkernel_aliases,
47 48 IPKernelApp
48 49 )
49 50 from IPython.zmq.session import Session, default_secure
50 51 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 52
52 53 #-----------------------------------------------------------------------------
53 54 # Network Constants
54 55 #-----------------------------------------------------------------------------
55 56
56 57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
57 58
58 59 #-----------------------------------------------------------------------------
59 60 # Globals
60 61 #-----------------------------------------------------------------------------
61 62
62 63
63 64 #-----------------------------------------------------------------------------
64 65 # Aliases and Flags
65 66 #-----------------------------------------------------------------------------
66 67
67 68 flags = dict(ipkernel_flags)
68 69
69 70 # the flags that are specific to the frontend
70 71 # these must be scrubbed before being passed to the kernel,
71 72 # or it will raise an error on unrecognized flags
72 73 app_flags = {
73 74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
74 75 "Connect to an existing kernel. If no argument specified, guess most recent"),
75 76 }
76 77 app_flags.update(boolean_flag(
77 78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
78 79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
79 80 to force a direct exit without any confirmation.
80 81 """,
81 82 """Don't prompt the user when exiting. This will terminate the kernel
82 83 if it is owned by the frontend, and leave it alive if it is external.
83 84 """
84 85 ))
85 86 flags.update(app_flags)
86 87
87 88 aliases = dict(ipkernel_aliases)
88 89
89 90 # also scrub aliases from the frontend
90 91 app_aliases = dict(
91 92 ip = 'KernelManager.ip',
92 93 transport = 'KernelManager.transport',
93 94 hb = 'IPythonConsoleApp.hb_port',
94 95 shell = 'IPythonConsoleApp.shell_port',
95 96 iopub = 'IPythonConsoleApp.iopub_port',
96 97 stdin = 'IPythonConsoleApp.stdin_port',
97 98 existing = 'IPythonConsoleApp.existing',
98 99 f = 'IPythonConsoleApp.connection_file',
99 100
100 101
101 102 ssh = 'IPythonConsoleApp.sshserver',
102 103 )
103 104 aliases.update(app_aliases)
104 105
105 106 #-----------------------------------------------------------------------------
106 107 # Classes
107 108 #-----------------------------------------------------------------------------
108 109
109 110 #-----------------------------------------------------------------------------
110 111 # IPythonConsole
111 112 #-----------------------------------------------------------------------------
112 113
113 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session]
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
114 115
115 116 try:
116 117 from IPython.zmq.pylab.backend_inline import InlineBackend
117 118 except ImportError:
118 119 pass
119 120 else:
120 121 classes.append(InlineBackend)
121 122
122 123 class IPythonConsoleApp(Configurable):
123 124 name = 'ipython-console-mixin'
124 125 default_config_file_name='ipython_config.py'
125 126
126 127 description = """
127 128 The IPython Mixin Console.
128 129
129 130 This class contains the common portions of console client (QtConsole,
130 131 ZMQ-based terminal console, etc). It is not a full console, in that
131 132 launched terminal subprocesses will not be able to accept input.
132 133
133 134 The Console using this mixing supports various extra features beyond
134 135 the single-process Terminal IPython shell, such as connecting to
135 136 existing kernel, via:
136 137
137 138 ipython <appname> --existing
138 139
139 140 as well as tunnel via SSH
140 141
141 142 """
142 143
143 144 classes = classes
144 145 flags = Dict(flags)
145 146 aliases = Dict(aliases)
146 147 kernel_manager_class = BlockingKernelManager
147 148
148 149 kernel_argv = List(Unicode)
149 150 # frontend flags&aliases to be stripped when building kernel_argv
150 151 frontend_flags = Any(app_flags)
151 152 frontend_aliases = Any(app_aliases)
152 153
153 154 # create requested profiles by default, if they don't exist:
154 155 auto_create = CBool(True)
155 156 # connection info:
156 157
157 158 sshserver = Unicode('', config=True,
158 159 help="""The SSH server to use to connect to the kernel.""")
159 160 sshkey = Unicode('', config=True,
160 161 help="""Path to the ssh key to use for logging in to the ssh server.""")
161 162
162 163 hb_port = Int(0, config=True,
163 164 help="set the heartbeat port [default: random]")
164 165 shell_port = Int(0, config=True,
165 166 help="set the shell (ROUTER) port [default: random]")
166 167 iopub_port = Int(0, config=True,
167 168 help="set the iopub (PUB) port [default: random]")
168 169 stdin_port = Int(0, config=True,
169 170 help="set the stdin (DEALER) port [default: random]")
170 171 connection_file = Unicode('', config=True,
171 172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
172 173
173 174 This file will contain the IP, ports, and authentication key needed to connect
174 175 clients to this kernel. By default, this file will be created in the security-dir
175 176 of the current profile, but can be specified by absolute path.
176 177 """)
177 178 def _connection_file_default(self):
178 179 return 'kernel-%i.json' % os.getpid()
179 180
180 181 existing = CUnicode('', config=True,
181 182 help="""Connect to an already running kernel""")
182 183
183 184 confirm_exit = CBool(True, config=True,
184 185 help="""
185 186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
186 187 to force a direct exit without any confirmation.""",
187 188 )
188 189
189 190
190 191 def build_kernel_argv(self, argv=None):
191 192 """build argv to be passed to kernel subprocess"""
192 193 if argv is None:
193 194 argv = sys.argv[1:]
194 195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
195 196 # kernel should inherit default config file from frontend
196 197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
197 198
198 199 def init_connection_file(self):
199 200 """find the connection file, and load the info if found.
200 201
201 202 The current working directory and the current profile's security
202 203 directory will be searched for the file if it is not given by
203 204 absolute path.
204 205
205 206 When attempting to connect to an existing kernel and the `--existing`
206 207 argument does not match an existing file, it will be interpreted as a
207 208 fileglob, and the matching file in the current profile's security dir
208 209 with the latest access time will be used.
209 210
210 211 After this method is called, self.connection_file contains the *full path*
211 212 to the connection file, never just its name.
212 213 """
213 214 if self.existing:
214 215 try:
215 216 cf = find_connection_file(self.existing)
216 217 except Exception:
217 218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
218 219 self.exit(1)
219 220 self.log.info("Connecting to existing kernel: %s" % cf)
220 221 self.connection_file = cf
221 222 else:
222 223 # not existing, check if we are going to write the file
223 224 # and ensure that self.connection_file is a full path, not just the shortname
224 225 try:
225 226 cf = find_connection_file(self.connection_file)
226 227 except Exception:
227 228 # file might not exist
228 229 if self.connection_file == os.path.basename(self.connection_file):
229 230 # just shortname, put it in security dir
230 231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
231 232 else:
232 233 cf = self.connection_file
233 234 self.connection_file = cf
234 235
235 236 # should load_connection_file only be used for existing?
236 237 # as it is now, this allows reusing ports if an existing
237 238 # file is requested
238 239 try:
239 240 self.load_connection_file()
240 241 except Exception:
241 242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
242 243 self.exit(1)
243 244
244 245 def load_connection_file(self):
245 246 """load ip/port/hmac config from JSON connection file"""
246 247 # this is identical to KernelApp.load_connection_file
247 248 # perhaps it can be centralized somewhere?
248 249 try:
249 250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
250 251 except IOError:
251 252 self.log.debug("Connection File not found: %s", self.connection_file)
252 253 return
253 254 self.log.debug(u"Loading connection file %s", fname)
254 255 with open(fname) as f:
255 256 cfg = json.load(f)
256 257
257 258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
258 259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
259 260
260 261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
261 262 name = channel + '_port'
262 263 if getattr(self, name) == 0 and name in cfg:
263 264 # not overridden by config or cl_args
264 265 setattr(self, name, cfg[name])
265 266 if 'key' in cfg:
266 267 self.config.Session.key = str_to_bytes(cfg['key'])
267 268
268 269 def init_ssh(self):
269 270 """set up ssh tunnels, if needed."""
270 271 if not self.existing or (not self.sshserver and not self.sshkey):
271 272 return
272 273
273 274 self.load_connection_file()
274 275
275 276 transport = self.config.KernelManager.transport
276 277 ip = self.config.KernelManager.ip
277 278
278 279 if transport != 'tcp':
279 280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
280 281 sys.exit(-1)
281 282
282 283 if self.sshkey and not self.sshserver:
283 284 # specifying just the key implies that we are connecting directly
284 285 self.sshserver = ip
285 286 ip = LOCALHOST
286 287
287 288 # build connection dict for tunnels:
288 289 info = dict(ip=ip,
289 290 shell_port=self.shell_port,
290 291 iopub_port=self.iopub_port,
291 292 stdin_port=self.stdin_port,
292 293 hb_port=self.hb_port
293 294 )
294 295
295 296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
296 297
297 298 # tunnels return a new set of ports, which will be on localhost:
298 299 self.config.KernelManager.ip = LOCALHOST
299 300 try:
300 301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
301 302 except:
302 303 # even catch KeyboardInterrupt
303 304 self.log.error("Could not setup tunnels", exc_info=True)
304 305 self.exit(1)
305 306
306 307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
307 308
308 309 cf = self.connection_file
309 310 base,ext = os.path.splitext(cf)
310 311 base = os.path.basename(base)
311 312 self.connection_file = os.path.basename(base)+'-ssh'+ext
312 313 self.log.critical("To connect another client via this tunnel, use:")
313 314 self.log.critical("--existing %s" % self.connection_file)
314 315
315 316 def _new_connection_file(self):
316 317 cf = ''
317 318 while not cf:
318 319 # we don't need a 128b id to distinguish kernels, use more readable
319 320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
320 321 # kernels can subclass.
321 322 ident = str(uuid.uuid4()).split('-')[-1]
322 323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
323 324 # only keep if it's actually new. Protect against unlikely collision
324 325 # in 48b random search space
325 326 cf = cf if not os.path.exists(cf) else ''
326 327 return cf
327 328
328 329 def init_kernel_manager(self):
329 330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
330 331 signal.signal(signal.SIGINT, signal.SIG_DFL)
331 332
332 333 # Create a KernelManager and start a kernel.
333 334 self.kernel_manager = self.kernel_manager_class(
334 335 shell_port=self.shell_port,
335 336 iopub_port=self.iopub_port,
336 337 stdin_port=self.stdin_port,
337 338 hb_port=self.hb_port,
338 339 connection_file=self.connection_file,
339 340 config=self.config,
340 341 )
341 342 # start the kernel
342 343 if not self.existing:
343 344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
344 345 atexit.register(self.kernel_manager.cleanup_ipc_files)
345 346 elif self.sshserver:
346 347 # ssh, write new connection file
347 348 self.kernel_manager.write_connection_file()
348 349 atexit.register(self.kernel_manager.cleanup_connection_file)
349 350 self.kernel_manager.start_channels()
350 351
351 352
352 353 def initialize(self, argv=None):
353 354 """
354 355 Classes which mix this class in should call:
355 356 IPythonConsoleApp.initialize(self,argv)
356 357 """
357 358 self.init_connection_file()
358 359 default_secure(self.config)
359 360 self.init_ssh()
360 361 self.init_kernel_manager()
361 362
General Comments 0
You need to be logged in to leave comments. Login now