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