##// END OF EJS Templates
cleanup socket cleanup...
MinRK -
Show More
@@ -1,195 +1,198 b''
1 """Tornado handlers for the notebook.
1 """Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import logging
19 import logging
20 from tornado import web
20 from tornado import web
21
21
22 from zmq.utils import jsonapi
22 from zmq.utils import jsonapi
23
23
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25 from IPython.html.utils import url_path_join, url_escape
25 from IPython.html.utils import url_path_join, url_escape
26
26
27 from ...base.handlers import IPythonHandler, json_errors
27 from ...base.handlers import IPythonHandler, json_errors
28 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
28 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Kernel handlers
31 # Kernel handlers
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 class MainKernelHandler(IPythonHandler):
35 class MainKernelHandler(IPythonHandler):
36
36
37 @web.authenticated
37 @web.authenticated
38 @json_errors
38 @json_errors
39 def get(self):
39 def get(self):
40 km = self.kernel_manager
40 km = self.kernel_manager
41 self.finish(jsonapi.dumps(km.list_kernels()))
41 self.finish(jsonapi.dumps(km.list_kernels()))
42
42
43 @web.authenticated
43 @web.authenticated
44 @json_errors
44 @json_errors
45 def post(self):
45 def post(self):
46 km = self.kernel_manager
46 km = self.kernel_manager
47 kernel_id = km.start_kernel()
47 kernel_id = km.start_kernel()
48 model = km.kernel_model(kernel_id)
48 model = km.kernel_model(kernel_id)
49 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
49 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
50 self.set_header('Location', url_escape(location))
50 self.set_header('Location', url_escape(location))
51 self.set_status(201)
51 self.set_status(201)
52 self.finish(jsonapi.dumps(model))
52 self.finish(jsonapi.dumps(model))
53
53
54
54
55 class KernelHandler(IPythonHandler):
55 class KernelHandler(IPythonHandler):
56
56
57 SUPPORTED_METHODS = ('DELETE', 'GET')
57 SUPPORTED_METHODS = ('DELETE', 'GET')
58
58
59 @web.authenticated
59 @web.authenticated
60 @json_errors
60 @json_errors
61 def get(self, kernel_id):
61 def get(self, kernel_id):
62 km = self.kernel_manager
62 km = self.kernel_manager
63 km._check_kernel_id(kernel_id)
63 km._check_kernel_id(kernel_id)
64 model = km.kernel_model(kernel_id)
64 model = km.kernel_model(kernel_id)
65 self.finish(jsonapi.dumps(model))
65 self.finish(jsonapi.dumps(model))
66
66
67 @web.authenticated
67 @web.authenticated
68 @json_errors
68 @json_errors
69 def delete(self, kernel_id):
69 def delete(self, kernel_id):
70 km = self.kernel_manager
70 km = self.kernel_manager
71 km.shutdown_kernel(kernel_id)
71 km.shutdown_kernel(kernel_id)
72 self.set_status(204)
72 self.set_status(204)
73 self.finish()
73 self.finish()
74
74
75
75
76 class KernelActionHandler(IPythonHandler):
76 class KernelActionHandler(IPythonHandler):
77
77
78 @web.authenticated
78 @web.authenticated
79 @json_errors
79 @json_errors
80 def post(self, kernel_id, action):
80 def post(self, kernel_id, action):
81 km = self.kernel_manager
81 km = self.kernel_manager
82 if action == 'interrupt':
82 if action == 'interrupt':
83 km.interrupt_kernel(kernel_id)
83 km.interrupt_kernel(kernel_id)
84 self.set_status(204)
84 self.set_status(204)
85 if action == 'restart':
85 if action == 'restart':
86 km.restart_kernel(kernel_id)
86 km.restart_kernel(kernel_id)
87 model = km.kernel_model(kernel_id)
87 model = km.kernel_model(kernel_id)
88 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_url, kernel_id))
88 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_url, kernel_id))
89 self.write(jsonapi.dumps(model))
89 self.write(jsonapi.dumps(model))
90 self.finish()
90 self.finish()
91
91
92
92
93 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
93 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
94
94
95 def create_stream(self):
95 def create_stream(self):
96 km = self.kernel_manager
96 km = self.kernel_manager
97 meth = getattr(km, 'connect_%s' % self.channel)
97 meth = getattr(km, 'connect_%s' % self.channel)
98 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
98 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
99
99
100 def initialize(self, *args, **kwargs):
100 def initialize(self, *args, **kwargs):
101 self.zmq_stream = None
101 self.zmq_stream = None
102
102
103 def on_first_message(self, msg):
103 def on_first_message(self, msg):
104 try:
104 try:
105 super(ZMQChannelHandler, self).on_first_message(msg)
105 super(ZMQChannelHandler, self).on_first_message(msg)
106 except web.HTTPError:
106 except web.HTTPError:
107 self.close()
107 self.close()
108 return
108 return
109 try:
109 try:
110 self.create_stream()
110 self.create_stream()
111 except web.HTTPError:
111 except web.HTTPError:
112 # WebSockets don't response to traditional error codes so we
112 # WebSockets don't response to traditional error codes so we
113 # close the connection.
113 # close the connection.
114 if not self.stream.closed():
114 if not self.stream.closed():
115 self.stream.close()
115 self.stream.close()
116 self.close()
116 self.close()
117 else:
117 else:
118 self.zmq_stream.on_recv(self._on_zmq_reply)
118 self.zmq_stream.on_recv(self._on_zmq_reply)
119
119
120 def on_message(self, msg):
120 def on_message(self, msg):
121 msg = jsonapi.loads(msg)
121 msg = jsonapi.loads(msg)
122 self.session.send(self.zmq_stream, msg)
122 self.session.send(self.zmq_stream, msg)
123
123
124 def on_close(self):
124 def on_close(self):
125 # This method can be called twice, once by self.kernel_died and once
125 # This method can be called twice, once by self.kernel_died and once
126 # from the WebSocket close event. If the WebSocket connection is
126 # from the WebSocket close event. If the WebSocket connection is
127 # closed before the ZMQ streams are setup, they could be None.
127 # closed before the ZMQ streams are setup, they could be None.
128 if self.zmq_stream is not None and not self.zmq_stream.closed():
128 if self.zmq_stream is not None and not self.zmq_stream.closed():
129 self.zmq_stream.on_recv(None)
129 self.zmq_stream.on_recv(None)
130 # close the socket directly, don't wait for the stream
131 socket = self.zmq_stream.socket
130 self.zmq_stream.close()
132 self.zmq_stream.close()
133 socket.close()
131
134
132
135
133 class IOPubHandler(ZMQChannelHandler):
136 class IOPubHandler(ZMQChannelHandler):
134 channel = 'iopub'
137 channel = 'iopub'
135
138
136 def create_stream(self):
139 def create_stream(self):
137 super(IOPubHandler, self).create_stream()
140 super(IOPubHandler, self).create_stream()
138 km = self.kernel_manager
141 km = self.kernel_manager
139 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
142 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
140 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
143 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
141
144
142 def on_close(self):
145 def on_close(self):
143 km = self.kernel_manager
146 km = self.kernel_manager
144 if self.kernel_id in km:
147 if self.kernel_id in km:
145 km.remove_restart_callback(
148 km.remove_restart_callback(
146 self.kernel_id, self.on_kernel_restarted,
149 self.kernel_id, self.on_kernel_restarted,
147 )
150 )
148 km.remove_restart_callback(
151 km.remove_restart_callback(
149 self.kernel_id, self.on_restart_failed, 'dead',
152 self.kernel_id, self.on_restart_failed, 'dead',
150 )
153 )
151 super(IOPubHandler, self).on_close()
154 super(IOPubHandler, self).on_close()
152
155
153 def _send_status_message(self, status):
156 def _send_status_message(self, status):
154 msg = self.session.msg("status",
157 msg = self.session.msg("status",
155 {'execution_state': status}
158 {'execution_state': status}
156 )
159 )
157 self.write_message(jsonapi.dumps(msg, default=date_default))
160 self.write_message(jsonapi.dumps(msg, default=date_default))
158
161
159 def on_kernel_restarted(self):
162 def on_kernel_restarted(self):
160 logging.warn("kernel %s restarted", self.kernel_id)
163 logging.warn("kernel %s restarted", self.kernel_id)
161 self._send_status_message('restarting')
164 self._send_status_message('restarting')
162
165
163 def on_restart_failed(self):
166 def on_restart_failed(self):
164 logging.error("kernel %s restarted failed!", self.kernel_id)
167 logging.error("kernel %s restarted failed!", self.kernel_id)
165 self._send_status_message('dead')
168 self._send_status_message('dead')
166
169
167 def on_message(self, msg):
170 def on_message(self, msg):
168 """IOPub messages make no sense"""
171 """IOPub messages make no sense"""
169 pass
172 pass
170
173
171
174
172 class ShellHandler(ZMQChannelHandler):
175 class ShellHandler(ZMQChannelHandler):
173 channel = 'shell'
176 channel = 'shell'
174
177
175
178
176 class StdinHandler(ZMQChannelHandler):
179 class StdinHandler(ZMQChannelHandler):
177 channel = 'stdin'
180 channel = 'stdin'
178
181
179
182
180 #-----------------------------------------------------------------------------
183 #-----------------------------------------------------------------------------
181 # URL to handler mappings
184 # URL to handler mappings
182 #-----------------------------------------------------------------------------
185 #-----------------------------------------------------------------------------
183
186
184
187
185 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
188 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
186 _kernel_action_regex = r"(?P<action>restart|interrupt)"
189 _kernel_action_regex = r"(?P<action>restart|interrupt)"
187
190
188 default_handlers = [
191 default_handlers = [
189 (r"/api/kernels", MainKernelHandler),
192 (r"/api/kernels", MainKernelHandler),
190 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
193 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
191 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
194 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
192 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
195 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
193 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
196 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
194 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
197 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
195 ]
198 ]
@@ -1,562 +1,564 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Min Ragan-Kelley
5 * Min Ragan-Kelley
6
6
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from __future__ import absolute_import
20 from __future__ import absolute_import
21
21
22 import glob
22 import glob
23 import json
23 import json
24 import os
24 import os
25 import socket
25 import socket
26 import sys
26 import sys
27 from getpass import getpass
27 from getpass import getpass
28 from subprocess import Popen, PIPE
28 from subprocess import Popen, PIPE
29 import tempfile
29 import tempfile
30
30
31 import zmq
31 import zmq
32
32
33 # external imports
33 # external imports
34 from IPython.external.ssh import tunnel
34 from IPython.external.ssh import tunnel
35
35
36 # IPython imports
36 # IPython imports
37 from IPython.config import Configurable
37 from IPython.config import Configurable
38 from IPython.core.profiledir import ProfileDir
38 from IPython.core.profiledir import ProfileDir
39 from IPython.utils.localinterfaces import localhost
39 from IPython.utils.localinterfaces import localhost
40 from IPython.utils.path import filefind, get_ipython_dir
40 from IPython.utils.path import filefind, get_ipython_dir
41 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
41 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
42 string_types)
42 string_types)
43 from IPython.utils.traitlets import (
43 from IPython.utils.traitlets import (
44 Bool, Integer, Unicode, CaselessStrEnum,
44 Bool, Integer, Unicode, CaselessStrEnum,
45 )
45 )
46
46
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Working with Connection Files
49 # Working with Connection Files
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
52 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',
53 control_port=0, ip='', key=b'', transport='tcp',
54 signature_scheme='hmac-sha256',
54 signature_scheme='hmac-sha256',
55 ):
55 ):
56 """Generates a JSON config file, including the selection of random ports.
56 """Generates a JSON config file, including the selection of random ports.
57
57
58 Parameters
58 Parameters
59 ----------
59 ----------
60
60
61 fname : unicode
61 fname : unicode
62 The path to the file to write
62 The path to the file to write
63
63
64 shell_port : int, optional
64 shell_port : int, optional
65 The port to use for ROUTER (shell) channel.
65 The port to use for ROUTER (shell) channel.
66
66
67 iopub_port : int, optional
67 iopub_port : int, optional
68 The port to use for the SUB channel.
68 The port to use for the SUB channel.
69
69
70 stdin_port : int, optional
70 stdin_port : int, optional
71 The port to use for the ROUTER (raw input) channel.
71 The port to use for the ROUTER (raw input) channel.
72
72
73 control_port : int, optional
73 control_port : int, optional
74 The port to use for the ROUTER (control) channel.
74 The port to use for the ROUTER (control) channel.
75
75
76 hb_port : int, optional
76 hb_port : int, optional
77 The port to use for the heartbeat REP channel.
77 The port to use for the heartbeat REP channel.
78
78
79 ip : str, optional
79 ip : str, optional
80 The ip address the kernel will bind to.
80 The ip address the kernel will bind to.
81
81
82 key : str, optional
82 key : str, optional
83 The Session key used for message authentication.
83 The Session key used for message authentication.
84
84
85 signature_scheme : str, optional
85 signature_scheme : str, optional
86 The scheme used for message authentication.
86 The scheme used for message authentication.
87 This has the form 'digest-hash', where 'digest'
87 This has the form 'digest-hash', where 'digest'
88 is the scheme used for digests, and 'hash' is the name of the hash function
88 is the scheme used for digests, and 'hash' is the name of the hash function
89 used by the digest scheme.
89 used by the digest scheme.
90 Currently, 'hmac' is the only supported digest scheme,
90 Currently, 'hmac' is the only supported digest scheme,
91 and 'sha256' is the default hash function.
91 and 'sha256' is the default hash function.
92
92
93 """
93 """
94 if not ip:
94 if not ip:
95 ip = localhost()
95 ip = localhost()
96 # default to temporary connector file
96 # default to temporary connector file
97 if not fname:
97 if not fname:
98 fd, fname = tempfile.mkstemp('.json')
98 fd, fname = tempfile.mkstemp('.json')
99 os.close(fd)
99 os.close(fd)
100
100
101 # Find open ports as necessary.
101 # Find open ports as necessary.
102
102
103 ports = []
103 ports = []
104 ports_needed = int(shell_port <= 0) + \
104 ports_needed = int(shell_port <= 0) + \
105 int(iopub_port <= 0) + \
105 int(iopub_port <= 0) + \
106 int(stdin_port <= 0) + \
106 int(stdin_port <= 0) + \
107 int(control_port <= 0) + \
107 int(control_port <= 0) + \
108 int(hb_port <= 0)
108 int(hb_port <= 0)
109 if transport == 'tcp':
109 if transport == 'tcp':
110 for i in range(ports_needed):
110 for i in range(ports_needed):
111 sock = socket.socket()
111 sock = socket.socket()
112 # struct.pack('ii', (0,0)) is 8 null bytes
112 # struct.pack('ii', (0,0)) is 8 null bytes
113 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
113 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
114 sock.bind(('', 0))
114 sock.bind(('', 0))
115 ports.append(sock)
115 ports.append(sock)
116 for i, sock in enumerate(ports):
116 for i, sock in enumerate(ports):
117 port = sock.getsockname()[1]
117 port = sock.getsockname()[1]
118 sock.close()
118 sock.close()
119 ports[i] = port
119 ports[i] = port
120 else:
120 else:
121 N = 1
121 N = 1
122 for i in range(ports_needed):
122 for i in range(ports_needed):
123 while os.path.exists("%s-%s" % (ip, str(N))):
123 while os.path.exists("%s-%s" % (ip, str(N))):
124 N += 1
124 N += 1
125 ports.append(N)
125 ports.append(N)
126 N += 1
126 N += 1
127 if shell_port <= 0:
127 if shell_port <= 0:
128 shell_port = ports.pop(0)
128 shell_port = ports.pop(0)
129 if iopub_port <= 0:
129 if iopub_port <= 0:
130 iopub_port = ports.pop(0)
130 iopub_port = ports.pop(0)
131 if stdin_port <= 0:
131 if stdin_port <= 0:
132 stdin_port = ports.pop(0)
132 stdin_port = ports.pop(0)
133 if control_port <= 0:
133 if control_port <= 0:
134 control_port = ports.pop(0)
134 control_port = ports.pop(0)
135 if hb_port <= 0:
135 if hb_port <= 0:
136 hb_port = ports.pop(0)
136 hb_port = ports.pop(0)
137
137
138 cfg = dict( shell_port=shell_port,
138 cfg = dict( shell_port=shell_port,
139 iopub_port=iopub_port,
139 iopub_port=iopub_port,
140 stdin_port=stdin_port,
140 stdin_port=stdin_port,
141 control_port=control_port,
141 control_port=control_port,
142 hb_port=hb_port,
142 hb_port=hb_port,
143 )
143 )
144 cfg['ip'] = ip
144 cfg['ip'] = ip
145 cfg['key'] = bytes_to_str(key)
145 cfg['key'] = bytes_to_str(key)
146 cfg['transport'] = transport
146 cfg['transport'] = transport
147 cfg['signature_scheme'] = signature_scheme
147 cfg['signature_scheme'] = signature_scheme
148
148
149 with open(fname, 'w') as f:
149 with open(fname, 'w') as f:
150 f.write(json.dumps(cfg, indent=2))
150 f.write(json.dumps(cfg, indent=2))
151
151
152 return fname, cfg
152 return fname, cfg
153
153
154
154
155 def get_connection_file(app=None):
155 def get_connection_file(app=None):
156 """Return the path to the connection file of an app
156 """Return the path to the connection file of an app
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 app : IPKernelApp instance [optional]
160 app : IPKernelApp instance [optional]
161 If unspecified, the currently running app will be used
161 If unspecified, the currently running app will be used
162 """
162 """
163 if app is None:
163 if app is None:
164 from IPython.kernel.zmq.kernelapp import IPKernelApp
164 from IPython.kernel.zmq.kernelapp import IPKernelApp
165 if not IPKernelApp.initialized():
165 if not IPKernelApp.initialized():
166 raise RuntimeError("app not specified, and not in a running Kernel")
166 raise RuntimeError("app not specified, and not in a running Kernel")
167
167
168 app = IPKernelApp.instance()
168 app = IPKernelApp.instance()
169 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
169 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
170
170
171
171
172 def find_connection_file(filename, profile=None):
172 def find_connection_file(filename, profile=None):
173 """find a connection file, and return its absolute path.
173 """find a connection file, and return its absolute path.
174
174
175 The current working directory and the profile's security
175 The current working directory and the profile's security
176 directory will be searched for the file if it is not given by
176 directory will be searched for the file if it is not given by
177 absolute path.
177 absolute path.
178
178
179 If profile is unspecified, then the current running application's
179 If profile is unspecified, then the current running application's
180 profile will be used, or 'default', if not run from IPython.
180 profile will be used, or 'default', if not run from IPython.
181
181
182 If the argument does not match an existing file, it will be interpreted as a
182 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
183 fileglob, and the matching file in the profile's security dir with
184 the latest access time will be used.
184 the latest access time will be used.
185
185
186 Parameters
186 Parameters
187 ----------
187 ----------
188 filename : str
188 filename : str
189 The connection file or fileglob to search for.
189 The connection file or fileglob to search for.
190 profile : str [optional]
190 profile : str [optional]
191 The name of the profile to use when searching for the connection file,
191 The name of the profile to use when searching for the connection file,
192 if different from the current IPython session or 'default'.
192 if different from the current IPython session or 'default'.
193
193
194 Returns
194 Returns
195 -------
195 -------
196 str : The absolute path of the connection file.
196 str : The absolute path of the connection file.
197 """
197 """
198 from IPython.core.application import BaseIPythonApplication as IPApp
198 from IPython.core.application import BaseIPythonApplication as IPApp
199 try:
199 try:
200 # quick check for absolute path, before going through logic
200 # quick check for absolute path, before going through logic
201 return filefind(filename)
201 return filefind(filename)
202 except IOError:
202 except IOError:
203 pass
203 pass
204
204
205 if profile is None:
205 if profile is None:
206 # profile unspecified, check if running from an IPython app
206 # profile unspecified, check if running from an IPython app
207 if IPApp.initialized():
207 if IPApp.initialized():
208 app = IPApp.instance()
208 app = IPApp.instance()
209 profile_dir = app.profile_dir
209 profile_dir = app.profile_dir
210 else:
210 else:
211 # not running in IPython, use default profile
211 # not running in IPython, use default profile
212 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
212 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
213 else:
213 else:
214 # find profiledir by profile name:
214 # find profiledir by profile name:
215 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
215 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
216 security_dir = profile_dir.security_dir
216 security_dir = profile_dir.security_dir
217
217
218 try:
218 try:
219 # first, try explicit name
219 # first, try explicit name
220 return filefind(filename, ['.', security_dir])
220 return filefind(filename, ['.', security_dir])
221 except IOError:
221 except IOError:
222 pass
222 pass
223
223
224 # not found by full name
224 # not found by full name
225
225
226 if '*' in filename:
226 if '*' in filename:
227 # given as a glob already
227 # given as a glob already
228 pat = filename
228 pat = filename
229 else:
229 else:
230 # accept any substring match
230 # accept any substring match
231 pat = '*%s*' % filename
231 pat = '*%s*' % filename
232 matches = glob.glob( os.path.join(security_dir, pat) )
232 matches = glob.glob( os.path.join(security_dir, pat) )
233 if not matches:
233 if not matches:
234 raise IOError("Could not find %r in %r" % (filename, security_dir))
234 raise IOError("Could not find %r in %r" % (filename, security_dir))
235 elif len(matches) == 1:
235 elif len(matches) == 1:
236 return matches[0]
236 return matches[0]
237 else:
237 else:
238 # get most recent match, by access time:
238 # get most recent match, by access time:
239 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
239 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
240
240
241
241
242 def get_connection_info(connection_file=None, unpack=False, profile=None):
242 def get_connection_info(connection_file=None, unpack=False, profile=None):
243 """Return the connection information for the current Kernel.
243 """Return the connection information for the current Kernel.
244
244
245 Parameters
245 Parameters
246 ----------
246 ----------
247 connection_file : str [optional]
247 connection_file : str [optional]
248 The connection file to be used. Can be given by absolute path, or
248 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.
249 IPython will search in the security directory of a given profile.
250 If run from IPython,
250 If run from IPython,
251
251
252 If unspecified, the connection file for the currently running
252 If unspecified, the connection file for the currently running
253 IPython Kernel will be used, which is only allowed from inside a kernel.
253 IPython Kernel will be used, which is only allowed from inside a kernel.
254 unpack : bool [default: False]
254 unpack : bool [default: False]
255 if True, return the unpacked dict, otherwise just the string contents
255 if True, return the unpacked dict, otherwise just the string contents
256 of the file.
256 of the file.
257 profile : str [optional]
257 profile : str [optional]
258 The name of the profile to use when searching for the connection file,
258 The name of the profile to use when searching for the connection file,
259 if different from the current IPython session or 'default'.
259 if different from the current IPython session or 'default'.
260
260
261
261
262 Returns
262 Returns
263 -------
263 -------
264 The connection dictionary of the current kernel, as string or dict,
264 The connection dictionary of the current kernel, as string or dict,
265 depending on `unpack`.
265 depending on `unpack`.
266 """
266 """
267 if connection_file is None:
267 if connection_file is None:
268 # get connection file from current kernel
268 # get connection file from current kernel
269 cf = get_connection_file()
269 cf = get_connection_file()
270 else:
270 else:
271 # connection file specified, allow shortnames:
271 # connection file specified, allow shortnames:
272 cf = find_connection_file(connection_file, profile=profile)
272 cf = find_connection_file(connection_file, profile=profile)
273
273
274 with open(cf) as f:
274 with open(cf) as f:
275 info = f.read()
275 info = f.read()
276
276
277 if unpack:
277 if unpack:
278 info = json.loads(info)
278 info = json.loads(info)
279 # ensure key is bytes:
279 # ensure key is bytes:
280 info['key'] = str_to_bytes(info.get('key', ''))
280 info['key'] = str_to_bytes(info.get('key', ''))
281 return info
281 return info
282
282
283
283
284 def connect_qtconsole(connection_file=None, argv=None, profile=None):
284 def connect_qtconsole(connection_file=None, argv=None, profile=None):
285 """Connect a qtconsole to the current kernel.
285 """Connect a qtconsole to the current kernel.
286
286
287 This is useful for connecting a second qtconsole to a kernel, or to a
287 This is useful for connecting a second qtconsole to a kernel, or to a
288 local notebook.
288 local notebook.
289
289
290 Parameters
290 Parameters
291 ----------
291 ----------
292 connection_file : str [optional]
292 connection_file : str [optional]
293 The connection file to be used. Can be given by absolute path, or
293 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.
294 IPython will search in the security directory of a given profile.
295 If run from IPython,
295 If run from IPython,
296
296
297 If unspecified, the connection file for the currently running
297 If unspecified, the connection file for the currently running
298 IPython Kernel will be used, which is only allowed from inside a kernel.
298 IPython Kernel will be used, which is only allowed from inside a kernel.
299 argv : list [optional]
299 argv : list [optional]
300 Any extra args to be passed to the console.
300 Any extra args to be passed to the console.
301 profile : str [optional]
301 profile : str [optional]
302 The name of the profile to use when searching for the connection file,
302 The name of the profile to use when searching for the connection file,
303 if different from the current IPython session or 'default'.
303 if different from the current IPython session or 'default'.
304
304
305
305
306 Returns
306 Returns
307 -------
307 -------
308 subprocess.Popen instance running the qtconsole frontend
308 subprocess.Popen instance running the qtconsole frontend
309 """
309 """
310 argv = [] if argv is None else argv
310 argv = [] if argv is None else argv
311
311
312 if connection_file is None:
312 if connection_file is None:
313 # get connection file from current kernel
313 # get connection file from current kernel
314 cf = get_connection_file()
314 cf = get_connection_file()
315 else:
315 else:
316 cf = find_connection_file(connection_file, profile=profile)
316 cf = find_connection_file(connection_file, profile=profile)
317
317
318 cmd = ';'.join([
318 cmd = ';'.join([
319 "from IPython.qt.console import qtconsoleapp",
319 "from IPython.qt.console import qtconsoleapp",
320 "qtconsoleapp.main()"
320 "qtconsoleapp.main()"
321 ])
321 ])
322
322
323 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
323 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
324 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
324 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
325 )
325 )
326
326
327
327
328 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
328 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
329 """tunnel connections to a kernel via ssh
329 """tunnel connections to a kernel via ssh
330
330
331 This will open four SSH tunnels from localhost on this machine to the
331 This will open four SSH tunnels from localhost on this machine to the
332 ports associated with the kernel. They can be either direct
332 ports associated with the kernel. They can be either direct
333 localhost-localhost tunnels, or if an intermediate server is necessary,
333 localhost-localhost tunnels, or if an intermediate server is necessary,
334 the kernel must be listening on a public IP.
334 the kernel must be listening on a public IP.
335
335
336 Parameters
336 Parameters
337 ----------
337 ----------
338 connection_info : dict or str (path)
338 connection_info : dict or str (path)
339 Either a connection dict, or the path to a JSON connection file
339 Either a connection dict, or the path to a JSON connection file
340 sshserver : str
340 sshserver : str
341 The ssh sever to use to tunnel to the kernel. Can be a full
341 The ssh sever to use to tunnel to the kernel. Can be a full
342 `user@server:port` string. ssh config aliases are respected.
342 `user@server:port` string. ssh config aliases are respected.
343 sshkey : str [optional]
343 sshkey : str [optional]
344 Path to file containing ssh key to use for authentication.
344 Path to file containing ssh key to use for authentication.
345 Only necessary if your ssh config does not already associate
345 Only necessary if your ssh config does not already associate
346 a keyfile with the host.
346 a keyfile with the host.
347
347
348 Returns
348 Returns
349 -------
349 -------
350
350
351 (shell, iopub, stdin, hb) : ints
351 (shell, iopub, stdin, hb) : ints
352 The four ports on localhost that have been forwarded to the kernel.
352 The four ports on localhost that have been forwarded to the kernel.
353 """
353 """
354 if isinstance(connection_info, string_types):
354 if isinstance(connection_info, string_types):
355 # it's a path, unpack it
355 # it's a path, unpack it
356 with open(connection_info) as f:
356 with open(connection_info) as f:
357 connection_info = json.loads(f.read())
357 connection_info = json.loads(f.read())
358
358
359 cf = connection_info
359 cf = connection_info
360
360
361 lports = tunnel.select_random_ports(4)
361 lports = tunnel.select_random_ports(4)
362 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
362 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
363
363
364 remote_ip = cf['ip']
364 remote_ip = cf['ip']
365
365
366 if tunnel.try_passwordless_ssh(sshserver, sshkey):
366 if tunnel.try_passwordless_ssh(sshserver, sshkey):
367 password=False
367 password=False
368 else:
368 else:
369 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
369 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
370
370
371 for lp,rp in zip(lports, rports):
371 for lp,rp in zip(lports, rports):
372 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
372 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
373
373
374 return tuple(lports)
374 return tuple(lports)
375
375
376
376
377 #-----------------------------------------------------------------------------
377 #-----------------------------------------------------------------------------
378 # Mixin for classes that work with connection files
378 # Mixin for classes that work with connection files
379 #-----------------------------------------------------------------------------
379 #-----------------------------------------------------------------------------
380
380
381 channel_socket_types = {
381 channel_socket_types = {
382 'hb' : zmq.REQ,
382 'hb' : zmq.REQ,
383 'shell' : zmq.DEALER,
383 'shell' : zmq.DEALER,
384 'iopub' : zmq.SUB,
384 'iopub' : zmq.SUB,
385 'stdin' : zmq.DEALER,
385 'stdin' : zmq.DEALER,
386 'control': zmq.DEALER,
386 'control': zmq.DEALER,
387 }
387 }
388
388
389 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
389 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
390
390
391 class ConnectionFileMixin(Configurable):
391 class ConnectionFileMixin(Configurable):
392 """Mixin for configurable classes that work with connection files"""
392 """Mixin for configurable classes that work with connection files"""
393
393
394 # The addresses for the communication channels
394 # The addresses for the communication channels
395 connection_file = Unicode('')
395 connection_file = Unicode('')
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 shell_port = Integer(0)
423 iopub_port = Integer(0)
423 iopub_port = Integer(0)
424 stdin_port = Integer(0)
424 stdin_port = Integer(0)
425 control_port = Integer(0)
425 control_port = Integer(0)
426 hb_port = Integer(0)
426 hb_port = Integer(0)
427
427
428 @property
428 @property
429 def ports(self):
429 def ports(self):
430 return [ getattr(self, name) for name in port_names ]
430 return [ getattr(self, name) for name in port_names ]
431
431
432 #--------------------------------------------------------------------------
432 #--------------------------------------------------------------------------
433 # Connection and ipc file management
433 # Connection and ipc file management
434 #--------------------------------------------------------------------------
434 #--------------------------------------------------------------------------
435
435
436 def get_connection_info(self):
436 def get_connection_info(self):
437 """return the connection info as a dict"""
437 """return the connection info as a dict"""
438 return dict(
438 return dict(
439 transport=self.transport,
439 transport=self.transport,
440 ip=self.ip,
440 ip=self.ip,
441 shell_port=self.shell_port,
441 shell_port=self.shell_port,
442 iopub_port=self.iopub_port,
442 iopub_port=self.iopub_port,
443 stdin_port=self.stdin_port,
443 stdin_port=self.stdin_port,
444 hb_port=self.hb_port,
444 hb_port=self.hb_port,
445 control_port=self.control_port,
445 control_port=self.control_port,
446 signature_scheme=self.session.signature_scheme,
446 signature_scheme=self.session.signature_scheme,
447 key=self.session.key,
447 key=self.session.key,
448 )
448 )
449
449
450 def cleanup_connection_file(self):
450 def cleanup_connection_file(self):
451 """Cleanup connection file *if we wrote it*
451 """Cleanup connection file *if we wrote it*
452
452
453 Will not raise if the connection file was already removed somehow.
453 Will not raise if the connection file was already removed somehow.
454 """
454 """
455 if self._connection_file_written:
455 if self._connection_file_written:
456 # cleanup connection files on full shutdown of kernel we started
456 # cleanup connection files on full shutdown of kernel we started
457 self._connection_file_written = False
457 self._connection_file_written = False
458 try:
458 try:
459 os.remove(self.connection_file)
459 os.remove(self.connection_file)
460 except (IOError, OSError, AttributeError):
460 except (IOError, OSError, AttributeError):
461 pass
461 pass
462
462
463 def cleanup_ipc_files(self):
463 def cleanup_ipc_files(self):
464 """Cleanup ipc files if we wrote them."""
464 """Cleanup ipc files if we wrote them."""
465 if self.transport != 'ipc':
465 if self.transport != 'ipc':
466 return
466 return
467 for port in self.ports:
467 for port in self.ports:
468 ipcfile = "%s-%i" % (self.ip, port)
468 ipcfile = "%s-%i" % (self.ip, port)
469 try:
469 try:
470 os.remove(ipcfile)
470 os.remove(ipcfile)
471 except (IOError, OSError):
471 except (IOError, OSError):
472 pass
472 pass
473
473
474 def write_connection_file(self):
474 def write_connection_file(self):
475 """Write connection info to JSON dict in self.connection_file."""
475 """Write connection info to JSON dict in self.connection_file."""
476 if self._connection_file_written and os.path.exists(self.connection_file):
476 if self._connection_file_written and os.path.exists(self.connection_file):
477 return
477 return
478
478
479 self.connection_file, cfg = write_connection_file(self.connection_file,
479 self.connection_file, cfg = write_connection_file(self.connection_file,
480 transport=self.transport, ip=self.ip, key=self.session.key,
480 transport=self.transport, ip=self.ip, key=self.session.key,
481 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
481 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
482 shell_port=self.shell_port, hb_port=self.hb_port,
482 shell_port=self.shell_port, hb_port=self.hb_port,
483 control_port=self.control_port,
483 control_port=self.control_port,
484 signature_scheme=self.session.signature_scheme,
484 signature_scheme=self.session.signature_scheme,
485 )
485 )
486 # write_connection_file also sets default ports:
486 # write_connection_file also sets default ports:
487 for name in port_names:
487 for name in port_names:
488 setattr(self, name, cfg[name])
488 setattr(self, name, cfg[name])
489
489
490 self._connection_file_written = True
490 self._connection_file_written = True
491
491
492 def load_connection_file(self):
492 def load_connection_file(self):
493 """Load connection info from JSON dict in self.connection_file."""
493 """Load connection info from JSON dict in self.connection_file."""
494 with open(self.connection_file) as f:
494 with open(self.connection_file) as f:
495 cfg = json.loads(f.read())
495 cfg = json.loads(f.read())
496
496
497 self.transport = cfg.get('transport', 'tcp')
497 self.transport = cfg.get('transport', 'tcp')
498 self.ip = cfg['ip']
498 self.ip = cfg['ip']
499 for name in port_names:
499 for name in port_names:
500 setattr(self, name, cfg[name])
500 setattr(self, name, cfg[name])
501 if 'key' in cfg:
501 if 'key' in cfg:
502 self.session.key = str_to_bytes(cfg['key'])
502 self.session.key = str_to_bytes(cfg['key'])
503 if cfg.get('signature_scheme'):
503 if cfg.get('signature_scheme'):
504 self.session.signature_scheme = cfg['signature_scheme']
504 self.session.signature_scheme = cfg['signature_scheme']
505
505
506 #--------------------------------------------------------------------------
506 #--------------------------------------------------------------------------
507 # Creating connected sockets
507 # Creating connected sockets
508 #--------------------------------------------------------------------------
508 #--------------------------------------------------------------------------
509
509
510 def _make_url(self, channel):
510 def _make_url(self, channel):
511 """Make a ZeroMQ URL for a given channel."""
511 """Make a ZeroMQ URL for a given channel."""
512 transport = self.transport
512 transport = self.transport
513 ip = self.ip
513 ip = self.ip
514 port = getattr(self, '%s_port' % channel)
514 port = getattr(self, '%s_port' % channel)
515
515
516 if transport == 'tcp':
516 if transport == 'tcp':
517 return "tcp://%s:%i" % (ip, port)
517 return "tcp://%s:%i" % (ip, port)
518 else:
518 else:
519 return "%s://%s-%s" % (transport, ip, port)
519 return "%s://%s-%s" % (transport, ip, port)
520
520
521 def _create_connected_socket(self, channel, identity=None):
521 def _create_connected_socket(self, channel, identity=None):
522 """Create a zmq Socket and connect it to the kernel."""
522 """Create a zmq Socket and connect it to the kernel."""
523 url = self._make_url(channel)
523 url = self._make_url(channel)
524 socket_type = channel_socket_types[channel]
524 socket_type = channel_socket_types[channel]
525 self.log.debug("Connecting to: %s" % url)
525 self.log.debug("Connecting to: %s" % url)
526 sock = self.context.socket(socket_type)
526 sock = self.context.socket(socket_type)
527 # set linger to 1s to prevent hangs at exit
528 sock.linger = 1000
527 if identity:
529 if identity:
528 sock.identity = identity
530 sock.identity = identity
529 sock.connect(url)
531 sock.connect(url)
530 return sock
532 return sock
531
533
532 def connect_iopub(self, identity=None):
534 def connect_iopub(self, identity=None):
533 """return zmq Socket connected to the IOPub channel"""
535 """return zmq Socket connected to the IOPub channel"""
534 sock = self._create_connected_socket('iopub', identity=identity)
536 sock = self._create_connected_socket('iopub', identity=identity)
535 sock.setsockopt(zmq.SUBSCRIBE, b'')
537 sock.setsockopt(zmq.SUBSCRIBE, b'')
536 return sock
538 return sock
537
539
538 def connect_shell(self, identity=None):
540 def connect_shell(self, identity=None):
539 """return zmq Socket connected to the Shell channel"""
541 """return zmq Socket connected to the Shell channel"""
540 return self._create_connected_socket('shell', identity=identity)
542 return self._create_connected_socket('shell', identity=identity)
541
543
542 def connect_stdin(self, identity=None):
544 def connect_stdin(self, identity=None):
543 """return zmq Socket connected to the StdIn channel"""
545 """return zmq Socket connected to the StdIn channel"""
544 return self._create_connected_socket('stdin', identity=identity)
546 return self._create_connected_socket('stdin', identity=identity)
545
547
546 def connect_hb(self, identity=None):
548 def connect_hb(self, identity=None):
547 """return zmq Socket connected to the Heartbeat channel"""
549 """return zmq Socket connected to the Heartbeat channel"""
548 return self._create_connected_socket('hb', identity=identity)
550 return self._create_connected_socket('hb', identity=identity)
549
551
550 def connect_control(self, identity=None):
552 def connect_control(self, identity=None):
551 """return zmq Socket connected to the Heartbeat channel"""
553 """return zmq Socket connected to the Heartbeat channel"""
552 return self._create_connected_socket('control', identity=identity)
554 return self._create_connected_socket('control', identity=identity)
553
555
554
556
555 __all__ = [
557 __all__ = [
556 'write_connection_file',
558 'write_connection_file',
557 'get_connection_file',
559 'get_connection_file',
558 'find_connection_file',
560 'find_connection_file',
559 'get_connection_info',
561 'get_connection_info',
560 'connect_qtconsole',
562 'connect_qtconsole',
561 'tunnel_to_kernel',
563 'tunnel_to_kernel',
562 ]
564 ]
General Comments 0
You need to be logged in to leave comments. Login now