##// END OF EJS Templates
Merge pull request #8247 from minrk/localinterfaces...
Thomas Kluyver -
r21136:ad8172ef merge
parent child Browse files
Show More
@@ -0,0 +1,268 b''
1 """Simple utility for building a list of local IPs using the socket module.
2 This module defines two constants:
3
4 LOCALHOST : The loopback interface, or the first interface that points to this
5 machine. It will *almost* always be '127.0.0.1'
6
7 LOCAL_IPS : A list of IP addresses, loopback first, that point to this machine.
8 This will include LOCALHOST, PUBLIC_IPS, and aliases for all hosts,
9 such as '0.0.0.0'.
10
11 PUBLIC_IPS : A list of public IP addresses that point to this machine.
12 Use these to tell remote clients where to find you.
13 """
14
15 # Copyright (c) IPython Development Team.
16 # Distributed under the terms of the Modified BSD License.
17
18 import os
19 import re
20 import socket
21
22 from IPython.utils.data import uniq_stable
23 from IPython.utils.process import get_output_error_code
24 from warnings import warn
25
26
27 LOCAL_IPS = []
28 PUBLIC_IPS = []
29
30 LOCALHOST = ''
31
32 def _only_once(f):
33 """decorator to only run a function once"""
34 f.called = False
35 def wrapped(**kwargs):
36 if f.called:
37 return
38 ret = f(**kwargs)
39 f.called = True
40 return ret
41 return wrapped
42
43 def _requires_ips(f):
44 """decorator to ensure load_ips has been run before f"""
45 def ips_loaded(*args, **kwargs):
46 _load_ips()
47 return f(*args, **kwargs)
48 return ips_loaded
49
50 # subprocess-parsing ip finders
51 class NoIPAddresses(Exception):
52 pass
53
54 def _populate_from_list(addrs):
55 """populate local and public IPs from flat list of all IPs"""
56 if not addrs:
57 raise NoIPAddresses
58
59 global LOCALHOST
60 public_ips = []
61 local_ips = []
62
63 for ip in addrs:
64 local_ips.append(ip)
65 if not ip.startswith('127.'):
66 public_ips.append(ip)
67 elif not LOCALHOST:
68 LOCALHOST = ip
69
70 if not LOCALHOST:
71 LOCALHOST = '127.0.0.1'
72 local_ips.insert(0, LOCALHOST)
73
74 local_ips.extend(['0.0.0.0', ''])
75
76 LOCAL_IPS[:] = uniq_stable(local_ips)
77 PUBLIC_IPS[:] = uniq_stable(public_ips)
78
79 def _load_ips_ifconfig():
80 """load ip addresses from `ifconfig` output (posix)"""
81
82 out, err, rc = get_output_error_code('ifconfig')
83 if rc:
84 # no ifconfig, it's usually in /sbin and /sbin is not on everyone's PATH
85 out, err, rc = get_output_error_code('/sbin/ifconfig')
86 if rc:
87 raise IOError("no ifconfig: %s" % err)
88
89 lines = out.splitlines()
90 addrs = []
91 for line in lines:
92 blocks = line.lower().split()
93 if (len(blocks) >= 2) and (blocks[0] == 'inet'):
94 if blocks[1].startswith("addr:"):
95 addrs.append(blocks[1].split(":")[1])
96 else:
97 addrs.append(blocks[1])
98 _populate_from_list(addrs)
99
100
101 def _load_ips_ip():
102 """load ip addresses from `ip addr` output (Linux)"""
103 out, err, rc = get_output_error_code('ip addr')
104 if rc:
105 raise IOError("no ip: %s" % err)
106
107 lines = out.splitlines()
108 addrs = []
109 for line in lines:
110 blocks = line.lower().split()
111 if (len(blocks) >= 2) and (blocks[0] == 'inet'):
112 addrs.append(blocks[1].split('/')[0])
113 _populate_from_list(addrs)
114
115 _ipconfig_ipv4_pat = re.compile(r'ipv4.*?(\d+\.\d+\.\d+\.\d+)$', re.IGNORECASE)
116
117 def _load_ips_ipconfig():
118 """load ip addresses from `ipconfig` output (Windows)"""
119 out, err, rc = get_output_error_code('ipconfig')
120 if rc:
121 raise IOError("no ipconfig: %s" % err)
122
123 lines = out.splitlines()
124 addrs = []
125 for line in lines:
126 m = _ipconfig_ipv4_pat.match(line.strip())
127 if m:
128 addrs.append(m.group(1))
129 _populate_from_list(addrs)
130
131
132 def _load_ips_netifaces():
133 """load ip addresses with netifaces"""
134 import netifaces
135 global LOCALHOST
136 local_ips = []
137 public_ips = []
138
139 # list of iface names, 'lo0', 'eth0', etc.
140 for iface in netifaces.interfaces():
141 # list of ipv4 addrinfo dicts
142 ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
143 for entry in ipv4s:
144 addr = entry.get('addr')
145 if not addr:
146 continue
147 if not (iface.startswith('lo') or addr.startswith('127.')):
148 public_ips.append(addr)
149 elif not LOCALHOST:
150 LOCALHOST = addr
151 local_ips.append(addr)
152 if not LOCALHOST:
153 # we never found a loopback interface (can this ever happen?), assume common default
154 LOCALHOST = '127.0.0.1'
155 local_ips.insert(0, LOCALHOST)
156 local_ips.extend(['0.0.0.0', ''])
157 LOCAL_IPS[:] = uniq_stable(local_ips)
158 PUBLIC_IPS[:] = uniq_stable(public_ips)
159
160
161 def _load_ips_gethostbyname():
162 """load ip addresses with socket.gethostbyname_ex
163
164 This can be slow.
165 """
166 global LOCALHOST
167 try:
168 LOCAL_IPS[:] = socket.gethostbyname_ex('localhost')[2]
169 except socket.error:
170 # assume common default
171 LOCAL_IPS[:] = ['127.0.0.1']
172
173 try:
174 hostname = socket.gethostname()
175 PUBLIC_IPS[:] = socket.gethostbyname_ex(hostname)[2]
176 # try hostname.local, in case hostname has been short-circuited to loopback
177 if not hostname.endswith('.local') and all(ip.startswith('127') for ip in PUBLIC_IPS):
178 PUBLIC_IPS[:] = socket.gethostbyname_ex(socket.gethostname() + '.local')[2]
179 except socket.error:
180 pass
181 finally:
182 PUBLIC_IPS[:] = uniq_stable(PUBLIC_IPS)
183 LOCAL_IPS.extend(PUBLIC_IPS)
184
185 # include all-interface aliases: 0.0.0.0 and ''
186 LOCAL_IPS.extend(['0.0.0.0', ''])
187
188 LOCAL_IPS[:] = uniq_stable(LOCAL_IPS)
189
190 LOCALHOST = LOCAL_IPS[0]
191
192 def _load_ips_dumb():
193 """Fallback in case of unexpected failure"""
194 global LOCALHOST
195 LOCALHOST = '127.0.0.1'
196 LOCAL_IPS[:] = [LOCALHOST, '0.0.0.0', '']
197 PUBLIC_IPS[:] = []
198
199 @_only_once
200 def _load_ips(suppress_exceptions=True):
201 """load the IPs that point to this machine
202
203 This function will only ever be called once.
204
205 It will use netifaces to do it quickly if available.
206 Then it will fallback on parsing the output of ifconfig / ip addr / ipconfig, as appropriate.
207 Finally, it will fallback on socket.gethostbyname_ex, which can be slow.
208 """
209
210 try:
211 # first priority, use netifaces
212 try:
213 return _load_ips_netifaces()
214 except ImportError:
215 pass
216
217 # second priority, parse subprocess output (how reliable is this?)
218
219 if os.name == 'nt':
220 try:
221 return _load_ips_ipconfig()
222 except (IOError, NoIPAddresses):
223 pass
224 else:
225 try:
226 return _load_ips_ifconfig()
227 except (IOError, NoIPAddresses):
228 pass
229 try:
230 return _load_ips_ip()
231 except (IOError, NoIPAddresses):
232 pass
233
234 # lowest priority, use gethostbyname
235
236 return _load_ips_gethostbyname()
237 except Exception as e:
238 if not suppress_exceptions:
239 raise
240 # unexpected error shouldn't crash, load dumb default values instead.
241 warn("Unexpected error discovering local network interfaces: %s" % e)
242 _load_ips_dumb()
243
244
245 @_requires_ips
246 def local_ips():
247 """return the IP addresses that point to this machine"""
248 return LOCAL_IPS
249
250 @_requires_ips
251 def public_ips():
252 """return the IP addresses for this machine that are visible to other machines"""
253 return PUBLIC_IPS
254
255 @_requires_ips
256 def localhost():
257 """return ip for localhost (almost always 127.0.0.1)"""
258 return LOCALHOST
259
260 @_requires_ips
261 def is_local_ip(ip):
262 """does `ip` point to this machine?"""
263 return ip in LOCAL_IPS
264
265 @_requires_ips
266 def is_public_ip(ip):
267 """is `ip` a publicly visible address?"""
268 return ip in PUBLIC_IPS
@@ -1,278 +1,5 b''
1 """Simple utility for building a list of local IPs using the socket module.
1 from warnings import warn
2 This module defines two constants:
3
2
4 LOCALHOST : The loopback interface, or the first interface that points to this
3 warn("IPython.utils.localinterfaces has moved to jupyter_client.localinterfaces")
5 machine. It will *almost* always be '127.0.0.1'
6
4
7 LOCAL_IPS : A list of IP addresses, loopback first, that point to this machine.
5 from jupyter_client.localinterfaces import *
8 This will include LOCALHOST, PUBLIC_IPS, and aliases for all hosts,
9 such as '0.0.0.0'.
10
11 PUBLIC_IPS : A list of public IP addresses that point to this machine.
12 Use these to tell remote clients where to find you.
13 """
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2010 The IPython Development Team
16 #
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
19 #-----------------------------------------------------------------------------
20
21 #-----------------------------------------------------------------------------
22 # Imports
23 #-----------------------------------------------------------------------------
24
25 import os
26 import re
27 import socket
28
29 from .data import uniq_stable
30 from .process import get_output_error_code
31 from .warn import warn
32
33 #-----------------------------------------------------------------------------
34 # Code
35 #-----------------------------------------------------------------------------
36
37 LOCAL_IPS = []
38 PUBLIC_IPS = []
39
40 LOCALHOST = ''
41
42 def _only_once(f):
43 """decorator to only run a function once"""
44 f.called = False
45 def wrapped(**kwargs):
46 if f.called:
47 return
48 ret = f(**kwargs)
49 f.called = True
50 return ret
51 return wrapped
52
53 def _requires_ips(f):
54 """decorator to ensure load_ips has been run before f"""
55 def ips_loaded(*args, **kwargs):
56 _load_ips()
57 return f(*args, **kwargs)
58 return ips_loaded
59
60 # subprocess-parsing ip finders
61 class NoIPAddresses(Exception):
62 pass
63
64 def _populate_from_list(addrs):
65 """populate local and public IPs from flat list of all IPs"""
66 if not addrs:
67 raise NoIPAddresses
68
69 global LOCALHOST
70 public_ips = []
71 local_ips = []
72
73 for ip in addrs:
74 local_ips.append(ip)
75 if not ip.startswith('127.'):
76 public_ips.append(ip)
77 elif not LOCALHOST:
78 LOCALHOST = ip
79
80 if not LOCALHOST:
81 LOCALHOST = '127.0.0.1'
82 local_ips.insert(0, LOCALHOST)
83
84 local_ips.extend(['0.0.0.0', ''])
85
86 LOCAL_IPS[:] = uniq_stable(local_ips)
87 PUBLIC_IPS[:] = uniq_stable(public_ips)
88
89 def _load_ips_ifconfig():
90 """load ip addresses from `ifconfig` output (posix)"""
91
92 out, err, rc = get_output_error_code('ifconfig')
93 if rc:
94 # no ifconfig, it's usually in /sbin and /sbin is not on everyone's PATH
95 out, err, rc = get_output_error_code('/sbin/ifconfig')
96 if rc:
97 raise IOError("no ifconfig: %s" % err)
98
99 lines = out.splitlines()
100 addrs = []
101 for line in lines:
102 blocks = line.lower().split()
103 if (len(blocks) >= 2) and (blocks[0] == 'inet'):
104 if blocks[1].startswith("addr:"):
105 addrs.append(blocks[1].split(":")[1])
106 else:
107 addrs.append(blocks[1])
108 _populate_from_list(addrs)
109
110
111 def _load_ips_ip():
112 """load ip addresses from `ip addr` output (Linux)"""
113 out, err, rc = get_output_error_code('ip addr')
114 if rc:
115 raise IOError("no ip: %s" % err)
116
117 lines = out.splitlines()
118 addrs = []
119 for line in lines:
120 blocks = line.lower().split()
121 if (len(blocks) >= 2) and (blocks[0] == 'inet'):
122 addrs.append(blocks[1].split('/')[0])
123 _populate_from_list(addrs)
124
125 _ipconfig_ipv4_pat = re.compile(r'ipv4.*?(\d+\.\d+\.\d+\.\d+)$', re.IGNORECASE)
126
127 def _load_ips_ipconfig():
128 """load ip addresses from `ipconfig` output (Windows)"""
129 out, err, rc = get_output_error_code('ipconfig')
130 if rc:
131 raise IOError("no ipconfig: %s" % err)
132
133 lines = out.splitlines()
134 addrs = []
135 for line in lines:
136 m = _ipconfig_ipv4_pat.match(line.strip())
137 if m:
138 addrs.append(m.group(1))
139 _populate_from_list(addrs)
140
141
142 def _load_ips_netifaces():
143 """load ip addresses with netifaces"""
144 import netifaces
145 global LOCALHOST
146 local_ips = []
147 public_ips = []
148
149 # list of iface names, 'lo0', 'eth0', etc.
150 for iface in netifaces.interfaces():
151 # list of ipv4 addrinfo dicts
152 ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])
153 for entry in ipv4s:
154 addr = entry.get('addr')
155 if not addr:
156 continue
157 if not (iface.startswith('lo') or addr.startswith('127.')):
158 public_ips.append(addr)
159 elif not LOCALHOST:
160 LOCALHOST = addr
161 local_ips.append(addr)
162 if not LOCALHOST:
163 # we never found a loopback interface (can this ever happen?), assume common default
164 LOCALHOST = '127.0.0.1'
165 local_ips.insert(0, LOCALHOST)
166 local_ips.extend(['0.0.0.0', ''])
167 LOCAL_IPS[:] = uniq_stable(local_ips)
168 PUBLIC_IPS[:] = uniq_stable(public_ips)
169
170
171 def _load_ips_gethostbyname():
172 """load ip addresses with socket.gethostbyname_ex
173
174 This can be slow.
175 """
176 global LOCALHOST
177 try:
178 LOCAL_IPS[:] = socket.gethostbyname_ex('localhost')[2]
179 except socket.error:
180 # assume common default
181 LOCAL_IPS[:] = ['127.0.0.1']
182
183 try:
184 hostname = socket.gethostname()
185 PUBLIC_IPS[:] = socket.gethostbyname_ex(hostname)[2]
186 # try hostname.local, in case hostname has been short-circuited to loopback
187 if not hostname.endswith('.local') and all(ip.startswith('127') for ip in PUBLIC_IPS):
188 PUBLIC_IPS[:] = socket.gethostbyname_ex(socket.gethostname() + '.local')[2]
189 except socket.error:
190 pass
191 finally:
192 PUBLIC_IPS[:] = uniq_stable(PUBLIC_IPS)
193 LOCAL_IPS.extend(PUBLIC_IPS)
194
195 # include all-interface aliases: 0.0.0.0 and ''
196 LOCAL_IPS.extend(['0.0.0.0', ''])
197
198 LOCAL_IPS[:] = uniq_stable(LOCAL_IPS)
199
200 LOCALHOST = LOCAL_IPS[0]
201
202 def _load_ips_dumb():
203 """Fallback in case of unexpected failure"""
204 global LOCALHOST
205 LOCALHOST = '127.0.0.1'
206 LOCAL_IPS[:] = [LOCALHOST, '0.0.0.0', '']
207 PUBLIC_IPS[:] = []
208
209 @_only_once
210 def _load_ips(suppress_exceptions=True):
211 """load the IPs that point to this machine
212
213 This function will only ever be called once.
214
215 It will use netifaces to do it quickly if available.
216 Then it will fallback on parsing the output of ifconfig / ip addr / ipconfig, as appropriate.
217 Finally, it will fallback on socket.gethostbyname_ex, which can be slow.
218 """
219
220 try:
221 # first priority, use netifaces
222 try:
223 return _load_ips_netifaces()
224 except ImportError:
225 pass
226
227 # second priority, parse subprocess output (how reliable is this?)
228
229 if os.name == 'nt':
230 try:
231 return _load_ips_ipconfig()
232 except (IOError, NoIPAddresses):
233 pass
234 else:
235 try:
236 return _load_ips_ifconfig()
237 except (IOError, NoIPAddresses):
238 pass
239 try:
240 return _load_ips_ip()
241 except (IOError, NoIPAddresses):
242 pass
243
244 # lowest priority, use gethostbyname
245
246 return _load_ips_gethostbyname()
247 except Exception as e:
248 if not suppress_exceptions:
249 raise
250 # unexpected error shouldn't crash, load dumb default values instead.
251 warn("Unexpected error discovering local network interfaces: %s" % e)
252 _load_ips_dumb()
253
254
255 @_requires_ips
256 def local_ips():
257 """return the IP addresses that point to this machine"""
258 return LOCAL_IPS
259
260 @_requires_ips
261 def public_ips():
262 """return the IP addresses for this machine that are visible to other machines"""
263 return PUBLIC_IPS
264
265 @_requires_ips
266 def localhost():
267 """return ip for localhost (almost always 127.0.0.1)"""
268 return LOCALHOST
269
270 @_requires_ips
271 def is_local_ip(ip):
272 """does `ip` point to this machine?"""
273 return ip in LOCAL_IPS
274
275 @_requires_ips
276 def is_public_ip(ip):
277 """is `ip` a publicly visible address?"""
278 return ip in PUBLIC_IPS
@@ -1,448 +1,448 b''
1 """Utilities for connecting to jupyter kernels
1 """Utilities for connecting to jupyter kernels
2
2
3 The :class:`ConnectionFileMixin` class in this module encapsulates the logic
3 The :class:`ConnectionFileMixin` class in this module encapsulates the logic
4 related to writing and reading connections files.
4 related to writing and reading connections files.
5 """
5 """
6
6
7 # Copyright (c) Jupyter Development Team.
7 # Copyright (c) Jupyter Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import glob
13 import glob
14 import json
14 import json
15 import os
15 import os
16 import socket
16 import socket
17 from getpass import getpass
17 from getpass import getpass
18 import tempfile
18 import tempfile
19
19
20 import zmq
20 import zmq
21
21
22 from IPython.config import LoggingConfigurable
22 from IPython.config import LoggingConfigurable
23 from IPython.utils.localinterfaces import localhost
23 from .localinterfaces import localhost
24 from IPython.utils.path import filefind
24 from IPython.utils.path import filefind
25 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
25 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
26 string_types)
26 string_types)
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 Bool, Integer, Unicode, CaselessStrEnum, Instance,
28 Bool, Integer, Unicode, CaselessStrEnum, Instance,
29 )
29 )
30
30
31
31
32 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
32 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
33 control_port=0, ip='', key=b'', transport='tcp',
33 control_port=0, ip='', key=b'', transport='tcp',
34 signature_scheme='hmac-sha256',
34 signature_scheme='hmac-sha256',
35 ):
35 ):
36 """Generates a JSON config file, including the selection of random ports.
36 """Generates a JSON config file, including the selection of random ports.
37
37
38 Parameters
38 Parameters
39 ----------
39 ----------
40
40
41 fname : unicode
41 fname : unicode
42 The path to the file to write
42 The path to the file to write
43
43
44 shell_port : int, optional
44 shell_port : int, optional
45 The port to use for ROUTER (shell) channel.
45 The port to use for ROUTER (shell) channel.
46
46
47 iopub_port : int, optional
47 iopub_port : int, optional
48 The port to use for the SUB channel.
48 The port to use for the SUB channel.
49
49
50 stdin_port : int, optional
50 stdin_port : int, optional
51 The port to use for the ROUTER (raw input) channel.
51 The port to use for the ROUTER (raw input) channel.
52
52
53 control_port : int, optional
53 control_port : int, optional
54 The port to use for the ROUTER (control) channel.
54 The port to use for the ROUTER (control) channel.
55
55
56 hb_port : int, optional
56 hb_port : int, optional
57 The port to use for the heartbeat REP channel.
57 The port to use for the heartbeat REP channel.
58
58
59 ip : str, optional
59 ip : str, optional
60 The ip address the kernel will bind to.
60 The ip address the kernel will bind to.
61
61
62 key : str, optional
62 key : str, optional
63 The Session key used for message authentication.
63 The Session key used for message authentication.
64
64
65 signature_scheme : str, optional
65 signature_scheme : str, optional
66 The scheme used for message authentication.
66 The scheme used for message authentication.
67 This has the form 'digest-hash', where 'digest'
67 This has the form 'digest-hash', where 'digest'
68 is the scheme used for digests, and 'hash' is the name of the hash function
68 is the scheme used for digests, and 'hash' is the name of the hash function
69 used by the digest scheme.
69 used by the digest scheme.
70 Currently, 'hmac' is the only supported digest scheme,
70 Currently, 'hmac' is the only supported digest scheme,
71 and 'sha256' is the default hash function.
71 and 'sha256' is the default hash function.
72
72
73 """
73 """
74 if not ip:
74 if not ip:
75 ip = localhost()
75 ip = localhost()
76 # default to temporary connector file
76 # default to temporary connector file
77 if not fname:
77 if not fname:
78 fd, fname = tempfile.mkstemp('.json')
78 fd, fname = tempfile.mkstemp('.json')
79 os.close(fd)
79 os.close(fd)
80
80
81 # Find open ports as necessary.
81 # Find open ports as necessary.
82
82
83 ports = []
83 ports = []
84 ports_needed = int(shell_port <= 0) + \
84 ports_needed = int(shell_port <= 0) + \
85 int(iopub_port <= 0) + \
85 int(iopub_port <= 0) + \
86 int(stdin_port <= 0) + \
86 int(stdin_port <= 0) + \
87 int(control_port <= 0) + \
87 int(control_port <= 0) + \
88 int(hb_port <= 0)
88 int(hb_port <= 0)
89 if transport == 'tcp':
89 if transport == 'tcp':
90 for i in range(ports_needed):
90 for i in range(ports_needed):
91 sock = socket.socket()
91 sock = socket.socket()
92 # struct.pack('ii', (0,0)) is 8 null bytes
92 # struct.pack('ii', (0,0)) is 8 null bytes
93 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
93 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
94 sock.bind(('', 0))
94 sock.bind(('', 0))
95 ports.append(sock)
95 ports.append(sock)
96 for i, sock in enumerate(ports):
96 for i, sock in enumerate(ports):
97 port = sock.getsockname()[1]
97 port = sock.getsockname()[1]
98 sock.close()
98 sock.close()
99 ports[i] = port
99 ports[i] = port
100 else:
100 else:
101 N = 1
101 N = 1
102 for i in range(ports_needed):
102 for i in range(ports_needed):
103 while os.path.exists("%s-%s" % (ip, str(N))):
103 while os.path.exists("%s-%s" % (ip, str(N))):
104 N += 1
104 N += 1
105 ports.append(N)
105 ports.append(N)
106 N += 1
106 N += 1
107 if shell_port <= 0:
107 if shell_port <= 0:
108 shell_port = ports.pop(0)
108 shell_port = ports.pop(0)
109 if iopub_port <= 0:
109 if iopub_port <= 0:
110 iopub_port = ports.pop(0)
110 iopub_port = ports.pop(0)
111 if stdin_port <= 0:
111 if stdin_port <= 0:
112 stdin_port = ports.pop(0)
112 stdin_port = ports.pop(0)
113 if control_port <= 0:
113 if control_port <= 0:
114 control_port = ports.pop(0)
114 control_port = ports.pop(0)
115 if hb_port <= 0:
115 if hb_port <= 0:
116 hb_port = ports.pop(0)
116 hb_port = ports.pop(0)
117
117
118 cfg = dict( shell_port=shell_port,
118 cfg = dict( shell_port=shell_port,
119 iopub_port=iopub_port,
119 iopub_port=iopub_port,
120 stdin_port=stdin_port,
120 stdin_port=stdin_port,
121 control_port=control_port,
121 control_port=control_port,
122 hb_port=hb_port,
122 hb_port=hb_port,
123 )
123 )
124 cfg['ip'] = ip
124 cfg['ip'] = ip
125 cfg['key'] = bytes_to_str(key)
125 cfg['key'] = bytes_to_str(key)
126 cfg['transport'] = transport
126 cfg['transport'] = transport
127 cfg['signature_scheme'] = signature_scheme
127 cfg['signature_scheme'] = signature_scheme
128
128
129 with open(fname, 'w') as f:
129 with open(fname, 'w') as f:
130 f.write(json.dumps(cfg, indent=2))
130 f.write(json.dumps(cfg, indent=2))
131
131
132 return fname, cfg
132 return fname, cfg
133
133
134
134
135 def find_connection_file(filename='kernel-*.json', path=None):
135 def find_connection_file(filename='kernel-*.json', path=None):
136 """find a connection file, and return its absolute path.
136 """find a connection file, and return its absolute path.
137
137
138 The current working directory and the profile's security
138 The current working directory and the profile's security
139 directory will be searched for the file if it is not given by
139 directory will be searched for the file if it is not given by
140 absolute path.
140 absolute path.
141
141
142 If profile is unspecified, then the current running application's
142 If profile is unspecified, then the current running application's
143 profile will be used, or 'default', if not run from IPython.
143 profile will be used, or 'default', if not run from IPython.
144
144
145 If the argument does not match an existing file, it will be interpreted as a
145 If the argument does not match an existing file, it will be interpreted as a
146 fileglob, and the matching file in the profile's security dir with
146 fileglob, and the matching file in the profile's security dir with
147 the latest access time will be used.
147 the latest access time will be used.
148
148
149 Parameters
149 Parameters
150 ----------
150 ----------
151 filename : str
151 filename : str
152 The connection file or fileglob to search for.
152 The connection file or fileglob to search for.
153 path : str or list of strs[optional]
153 path : str or list of strs[optional]
154 Paths in which to search for connection files.
154 Paths in which to search for connection files.
155
155
156 Returns
156 Returns
157 -------
157 -------
158 str : The absolute path of the connection file.
158 str : The absolute path of the connection file.
159 """
159 """
160 if path is None:
160 if path is None:
161 path = ['.']
161 path = ['.']
162 if isinstance(path, string_types):
162 if isinstance(path, string_types):
163 path = [path]
163 path = [path]
164
164
165 try:
165 try:
166 # first, try explicit name
166 # first, try explicit name
167 return filefind(filename, path)
167 return filefind(filename, path)
168 except IOError:
168 except IOError:
169 pass
169 pass
170
170
171 # not found by full name
171 # not found by full name
172
172
173 if '*' in filename:
173 if '*' in filename:
174 # given as a glob already
174 # given as a glob already
175 pat = filename
175 pat = filename
176 else:
176 else:
177 # accept any substring match
177 # accept any substring match
178 pat = '*%s*' % filename
178 pat = '*%s*' % filename
179
179
180 matches = []
180 matches = []
181 for p in path:
181 for p in path:
182 matches.extend(glob.glob(os.path.join(p, pat)))
182 matches.extend(glob.glob(os.path.join(p, pat)))
183
183
184 if not matches:
184 if not matches:
185 raise IOError("Could not find %r in %r" % (filename, path))
185 raise IOError("Could not find %r in %r" % (filename, path))
186 elif len(matches) == 1:
186 elif len(matches) == 1:
187 return matches[0]
187 return matches[0]
188 else:
188 else:
189 # get most recent match, by access time:
189 # get most recent match, by access time:
190 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
190 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
191
191
192
192
193 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
193 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
194 """tunnel connections to a kernel via ssh
194 """tunnel connections to a kernel via ssh
195
195
196 This will open four SSH tunnels from localhost on this machine to the
196 This will open four SSH tunnels from localhost on this machine to the
197 ports associated with the kernel. They can be either direct
197 ports associated with the kernel. They can be either direct
198 localhost-localhost tunnels, or if an intermediate server is necessary,
198 localhost-localhost tunnels, or if an intermediate server is necessary,
199 the kernel must be listening on a public IP.
199 the kernel must be listening on a public IP.
200
200
201 Parameters
201 Parameters
202 ----------
202 ----------
203 connection_info : dict or str (path)
203 connection_info : dict or str (path)
204 Either a connection dict, or the path to a JSON connection file
204 Either a connection dict, or the path to a JSON connection file
205 sshserver : str
205 sshserver : str
206 The ssh sever to use to tunnel to the kernel. Can be a full
206 The ssh sever to use to tunnel to the kernel. Can be a full
207 `user@server:port` string. ssh config aliases are respected.
207 `user@server:port` string. ssh config aliases are respected.
208 sshkey : str [optional]
208 sshkey : str [optional]
209 Path to file containing ssh key to use for authentication.
209 Path to file containing ssh key to use for authentication.
210 Only necessary if your ssh config does not already associate
210 Only necessary if your ssh config does not already associate
211 a keyfile with the host.
211 a keyfile with the host.
212
212
213 Returns
213 Returns
214 -------
214 -------
215
215
216 (shell, iopub, stdin, hb) : ints
216 (shell, iopub, stdin, hb) : ints
217 The four ports on localhost that have been forwarded to the kernel.
217 The four ports on localhost that have been forwarded to the kernel.
218 """
218 """
219 from zmq.ssh import tunnel
219 from zmq.ssh import tunnel
220 if isinstance(connection_info, string_types):
220 if isinstance(connection_info, string_types):
221 # it's a path, unpack it
221 # it's a path, unpack it
222 with open(connection_info) as f:
222 with open(connection_info) as f:
223 connection_info = json.loads(f.read())
223 connection_info = json.loads(f.read())
224
224
225 cf = connection_info
225 cf = connection_info
226
226
227 lports = tunnel.select_random_ports(4)
227 lports = tunnel.select_random_ports(4)
228 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
228 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
229
229
230 remote_ip = cf['ip']
230 remote_ip = cf['ip']
231
231
232 if tunnel.try_passwordless_ssh(sshserver, sshkey):
232 if tunnel.try_passwordless_ssh(sshserver, sshkey):
233 password=False
233 password=False
234 else:
234 else:
235 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
235 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
236
236
237 for lp,rp in zip(lports, rports):
237 for lp,rp in zip(lports, rports):
238 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
238 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
239
239
240 return tuple(lports)
240 return tuple(lports)
241
241
242
242
243 #-----------------------------------------------------------------------------
243 #-----------------------------------------------------------------------------
244 # Mixin for classes that work with connection files
244 # Mixin for classes that work with connection files
245 #-----------------------------------------------------------------------------
245 #-----------------------------------------------------------------------------
246
246
247 channel_socket_types = {
247 channel_socket_types = {
248 'hb' : zmq.REQ,
248 'hb' : zmq.REQ,
249 'shell' : zmq.DEALER,
249 'shell' : zmq.DEALER,
250 'iopub' : zmq.SUB,
250 'iopub' : zmq.SUB,
251 'stdin' : zmq.DEALER,
251 'stdin' : zmq.DEALER,
252 'control': zmq.DEALER,
252 'control': zmq.DEALER,
253 }
253 }
254
254
255 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
255 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
256
256
257 class ConnectionFileMixin(LoggingConfigurable):
257 class ConnectionFileMixin(LoggingConfigurable):
258 """Mixin for configurable classes that work with connection files"""
258 """Mixin for configurable classes that work with connection files"""
259
259
260 # The addresses for the communication channels
260 # The addresses for the communication channels
261 connection_file = Unicode('', config=True,
261 connection_file = Unicode('', config=True,
262 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
262 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
263
263
264 This file will contain the IP, ports, and authentication key needed to connect
264 This file will contain the IP, ports, and authentication key needed to connect
265 clients to this kernel. By default, this file will be created in the security dir
265 clients to this kernel. By default, this file will be created in the security dir
266 of the current profile, but can be specified by absolute path.
266 of the current profile, but can be specified by absolute path.
267 """)
267 """)
268 _connection_file_written = Bool(False)
268 _connection_file_written = Bool(False)
269
269
270 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
270 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
271
271
272 ip = Unicode(config=True,
272 ip = Unicode(config=True,
273 help="""Set the kernel\'s IP address [default localhost].
273 help="""Set the kernel\'s IP address [default localhost].
274 If the IP address is something other than localhost, then
274 If the IP address is something other than localhost, then
275 Consoles on other machines will be able to connect
275 Consoles on other machines will be able to connect
276 to the Kernel, so be careful!"""
276 to the Kernel, so be careful!"""
277 )
277 )
278
278
279 def _ip_default(self):
279 def _ip_default(self):
280 if self.transport == 'ipc':
280 if self.transport == 'ipc':
281 if self.connection_file:
281 if self.connection_file:
282 return os.path.splitext(self.connection_file)[0] + '-ipc'
282 return os.path.splitext(self.connection_file)[0] + '-ipc'
283 else:
283 else:
284 return 'kernel-ipc'
284 return 'kernel-ipc'
285 else:
285 else:
286 return localhost()
286 return localhost()
287
287
288 def _ip_changed(self, name, old, new):
288 def _ip_changed(self, name, old, new):
289 if new == '*':
289 if new == '*':
290 self.ip = '0.0.0.0'
290 self.ip = '0.0.0.0'
291
291
292 # protected traits
292 # protected traits
293
293
294 hb_port = Integer(0, config=True,
294 hb_port = Integer(0, config=True,
295 help="set the heartbeat port [default: random]")
295 help="set the heartbeat port [default: random]")
296 shell_port = Integer(0, config=True,
296 shell_port = Integer(0, config=True,
297 help="set the shell (ROUTER) port [default: random]")
297 help="set the shell (ROUTER) port [default: random]")
298 iopub_port = Integer(0, config=True,
298 iopub_port = Integer(0, config=True,
299 help="set the iopub (PUB) port [default: random]")
299 help="set the iopub (PUB) port [default: random]")
300 stdin_port = Integer(0, config=True,
300 stdin_port = Integer(0, config=True,
301 help="set the stdin (ROUTER) port [default: random]")
301 help="set the stdin (ROUTER) port [default: random]")
302 control_port = Integer(0, config=True,
302 control_port = Integer(0, config=True,
303 help="set the control (ROUTER) port [default: random]")
303 help="set the control (ROUTER) port [default: random]")
304
304
305 @property
305 @property
306 def ports(self):
306 def ports(self):
307 return [ getattr(self, name) for name in port_names ]
307 return [ getattr(self, name) for name in port_names ]
308
308
309 # The Session to use for communication with the kernel.
309 # The Session to use for communication with the kernel.
310 session = Instance('jupyter_client.session.Session')
310 session = Instance('jupyter_client.session.Session')
311 def _session_default(self):
311 def _session_default(self):
312 from jupyter_client.session import Session
312 from jupyter_client.session import Session
313 return Session(parent=self)
313 return Session(parent=self)
314
314
315 #--------------------------------------------------------------------------
315 #--------------------------------------------------------------------------
316 # Connection and ipc file management
316 # Connection and ipc file management
317 #--------------------------------------------------------------------------
317 #--------------------------------------------------------------------------
318
318
319 def get_connection_info(self):
319 def get_connection_info(self):
320 """return the connection info as a dict"""
320 """return the connection info as a dict"""
321 return dict(
321 return dict(
322 transport=self.transport,
322 transport=self.transport,
323 ip=self.ip,
323 ip=self.ip,
324 shell_port=self.shell_port,
324 shell_port=self.shell_port,
325 iopub_port=self.iopub_port,
325 iopub_port=self.iopub_port,
326 stdin_port=self.stdin_port,
326 stdin_port=self.stdin_port,
327 hb_port=self.hb_port,
327 hb_port=self.hb_port,
328 control_port=self.control_port,
328 control_port=self.control_port,
329 signature_scheme=self.session.signature_scheme,
329 signature_scheme=self.session.signature_scheme,
330 key=self.session.key,
330 key=self.session.key,
331 )
331 )
332
332
333 def cleanup_connection_file(self):
333 def cleanup_connection_file(self):
334 """Cleanup connection file *if we wrote it*
334 """Cleanup connection file *if we wrote it*
335
335
336 Will not raise if the connection file was already removed somehow.
336 Will not raise if the connection file was already removed somehow.
337 """
337 """
338 if self._connection_file_written:
338 if self._connection_file_written:
339 # cleanup connection files on full shutdown of kernel we started
339 # cleanup connection files on full shutdown of kernel we started
340 self._connection_file_written = False
340 self._connection_file_written = False
341 try:
341 try:
342 os.remove(self.connection_file)
342 os.remove(self.connection_file)
343 except (IOError, OSError, AttributeError):
343 except (IOError, OSError, AttributeError):
344 pass
344 pass
345
345
346 def cleanup_ipc_files(self):
346 def cleanup_ipc_files(self):
347 """Cleanup ipc files if we wrote them."""
347 """Cleanup ipc files if we wrote them."""
348 if self.transport != 'ipc':
348 if self.transport != 'ipc':
349 return
349 return
350 for port in self.ports:
350 for port in self.ports:
351 ipcfile = "%s-%i" % (self.ip, port)
351 ipcfile = "%s-%i" % (self.ip, port)
352 try:
352 try:
353 os.remove(ipcfile)
353 os.remove(ipcfile)
354 except (IOError, OSError):
354 except (IOError, OSError):
355 pass
355 pass
356
356
357 def write_connection_file(self):
357 def write_connection_file(self):
358 """Write connection info to JSON dict in self.connection_file."""
358 """Write connection info to JSON dict in self.connection_file."""
359 if self._connection_file_written and os.path.exists(self.connection_file):
359 if self._connection_file_written and os.path.exists(self.connection_file):
360 return
360 return
361
361
362 self.connection_file, cfg = write_connection_file(self.connection_file,
362 self.connection_file, cfg = write_connection_file(self.connection_file,
363 transport=self.transport, ip=self.ip, key=self.session.key,
363 transport=self.transport, ip=self.ip, key=self.session.key,
364 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
364 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
365 shell_port=self.shell_port, hb_port=self.hb_port,
365 shell_port=self.shell_port, hb_port=self.hb_port,
366 control_port=self.control_port,
366 control_port=self.control_port,
367 signature_scheme=self.session.signature_scheme,
367 signature_scheme=self.session.signature_scheme,
368 )
368 )
369 # write_connection_file also sets default ports:
369 # write_connection_file also sets default ports:
370 for name in port_names:
370 for name in port_names:
371 setattr(self, name, cfg[name])
371 setattr(self, name, cfg[name])
372
372
373 self._connection_file_written = True
373 self._connection_file_written = True
374
374
375 def load_connection_file(self):
375 def load_connection_file(self):
376 """Load connection info from JSON dict in self.connection_file."""
376 """Load connection info from JSON dict in self.connection_file."""
377 self.log.debug(u"Loading connection file %s", self.connection_file)
377 self.log.debug(u"Loading connection file %s", self.connection_file)
378 with open(self.connection_file) as f:
378 with open(self.connection_file) as f:
379 cfg = json.load(f)
379 cfg = json.load(f)
380 self.transport = cfg.get('transport', self.transport)
380 self.transport = cfg.get('transport', self.transport)
381 self.ip = cfg.get('ip', self._ip_default())
381 self.ip = cfg.get('ip', self._ip_default())
382
382
383 for name in port_names:
383 for name in port_names:
384 if getattr(self, name) == 0 and name in cfg:
384 if getattr(self, name) == 0 and name in cfg:
385 # not overridden by config or cl_args
385 # not overridden by config or cl_args
386 setattr(self, name, cfg[name])
386 setattr(self, name, cfg[name])
387
387
388 if 'key' in cfg:
388 if 'key' in cfg:
389 self.session.key = str_to_bytes(cfg['key'])
389 self.session.key = str_to_bytes(cfg['key'])
390 if 'signature_scheme' in cfg:
390 if 'signature_scheme' in cfg:
391 self.session.signature_scheme = cfg['signature_scheme']
391 self.session.signature_scheme = cfg['signature_scheme']
392
392
393 #--------------------------------------------------------------------------
393 #--------------------------------------------------------------------------
394 # Creating connected sockets
394 # Creating connected sockets
395 #--------------------------------------------------------------------------
395 #--------------------------------------------------------------------------
396
396
397 def _make_url(self, channel):
397 def _make_url(self, channel):
398 """Make a ZeroMQ URL for a given channel."""
398 """Make a ZeroMQ URL for a given channel."""
399 transport = self.transport
399 transport = self.transport
400 ip = self.ip
400 ip = self.ip
401 port = getattr(self, '%s_port' % channel)
401 port = getattr(self, '%s_port' % channel)
402
402
403 if transport == 'tcp':
403 if transport == 'tcp':
404 return "tcp://%s:%i" % (ip, port)
404 return "tcp://%s:%i" % (ip, port)
405 else:
405 else:
406 return "%s://%s-%s" % (transport, ip, port)
406 return "%s://%s-%s" % (transport, ip, port)
407
407
408 def _create_connected_socket(self, channel, identity=None):
408 def _create_connected_socket(self, channel, identity=None):
409 """Create a zmq Socket and connect it to the kernel."""
409 """Create a zmq Socket and connect it to the kernel."""
410 url = self._make_url(channel)
410 url = self._make_url(channel)
411 socket_type = channel_socket_types[channel]
411 socket_type = channel_socket_types[channel]
412 self.log.debug("Connecting to: %s" % url)
412 self.log.debug("Connecting to: %s" % url)
413 sock = self.context.socket(socket_type)
413 sock = self.context.socket(socket_type)
414 # set linger to 1s to prevent hangs at exit
414 # set linger to 1s to prevent hangs at exit
415 sock.linger = 1000
415 sock.linger = 1000
416 if identity:
416 if identity:
417 sock.identity = identity
417 sock.identity = identity
418 sock.connect(url)
418 sock.connect(url)
419 return sock
419 return sock
420
420
421 def connect_iopub(self, identity=None):
421 def connect_iopub(self, identity=None):
422 """return zmq Socket connected to the IOPub channel"""
422 """return zmq Socket connected to the IOPub channel"""
423 sock = self._create_connected_socket('iopub', identity=identity)
423 sock = self._create_connected_socket('iopub', identity=identity)
424 sock.setsockopt(zmq.SUBSCRIBE, b'')
424 sock.setsockopt(zmq.SUBSCRIBE, b'')
425 return sock
425 return sock
426
426
427 def connect_shell(self, identity=None):
427 def connect_shell(self, identity=None):
428 """return zmq Socket connected to the Shell channel"""
428 """return zmq Socket connected to the Shell channel"""
429 return self._create_connected_socket('shell', identity=identity)
429 return self._create_connected_socket('shell', identity=identity)
430
430
431 def connect_stdin(self, identity=None):
431 def connect_stdin(self, identity=None):
432 """return zmq Socket connected to the StdIn channel"""
432 """return zmq Socket connected to the StdIn channel"""
433 return self._create_connected_socket('stdin', identity=identity)
433 return self._create_connected_socket('stdin', identity=identity)
434
434
435 def connect_hb(self, identity=None):
435 def connect_hb(self, identity=None):
436 """return zmq Socket connected to the Heartbeat channel"""
436 """return zmq Socket connected to the Heartbeat channel"""
437 return self._create_connected_socket('hb', identity=identity)
437 return self._create_connected_socket('hb', identity=identity)
438
438
439 def connect_control(self, identity=None):
439 def connect_control(self, identity=None):
440 """return zmq Socket connected to the Control channel"""
440 """return zmq Socket connected to the Control channel"""
441 return self._create_connected_socket('control', identity=identity)
441 return self._create_connected_socket('control', identity=identity)
442
442
443
443
444 __all__ = [
444 __all__ = [
445 'write_connection_file',
445 'write_connection_file',
446 'find_connection_file',
446 'find_connection_file',
447 'tunnel_to_kernel',
447 'tunnel_to_kernel',
448 ]
448 ]
@@ -1,331 +1,331 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6 """
6 """
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import atexit
10 import atexit
11 import os
11 import os
12 import signal
12 import signal
13 import sys
13 import sys
14 import uuid
14 import uuid
15
15
16
16
17 from IPython.config.application import boolean_flag
17 from IPython.config.application import boolean_flag
18 from IPython.core.profiledir import ProfileDir
18 from IPython.core.profiledir import ProfileDir
19 from IPython.utils.path import filefind
19 from IPython.utils.path import filefind
20 from IPython.utils.traitlets import (
20 from IPython.utils.traitlets import (
21 Dict, List, Unicode, CUnicode, CBool, Any
21 Dict, List, Unicode, CUnicode, CBool, Any
22 )
22 )
23
23
24 from .blocking import BlockingKernelClient
24 from .blocking import BlockingKernelClient
25 from . import KernelManager, tunnel_to_kernel, find_connection_file, connect
25 from . import KernelManager, tunnel_to_kernel, find_connection_file, connect
26 from .kernelspec import NoSuchKernel
26 from .kernelspec import NoSuchKernel
27 from .session import Session
27 from .session import Session
28
28
29 ConnectionFileMixin = connect.ConnectionFileMixin
29 ConnectionFileMixin = connect.ConnectionFileMixin
30
30
31 from IPython.utils.localinterfaces import localhost
31 from .localinterfaces import localhost
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Aliases and Flags
34 # Aliases and Flags
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 flags = {}
37 flags = {}
38
38
39 # the flags that are specific to the frontend
39 # the flags that are specific to the frontend
40 # these must be scrubbed before being passed to the kernel,
40 # these must be scrubbed before being passed to the kernel,
41 # or it will raise an error on unrecognized flags
41 # or it will raise an error on unrecognized flags
42 app_flags = {
42 app_flags = {
43 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
43 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
44 "Connect to an existing kernel. If no argument specified, guess most recent"),
44 "Connect to an existing kernel. If no argument specified, guess most recent"),
45 }
45 }
46 app_flags.update(boolean_flag(
46 app_flags.update(boolean_flag(
47 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
47 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
48 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
48 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
49 to force a direct exit without any confirmation.
49 to force a direct exit without any confirmation.
50 """,
50 """,
51 """Don't prompt the user when exiting. This will terminate the kernel
51 """Don't prompt the user when exiting. This will terminate the kernel
52 if it is owned by the frontend, and leave it alive if it is external.
52 if it is owned by the frontend, and leave it alive if it is external.
53 """
53 """
54 ))
54 ))
55 flags.update(app_flags)
55 flags.update(app_flags)
56
56
57 aliases = {}
57 aliases = {}
58
58
59 # also scrub aliases from the frontend
59 # also scrub aliases from the frontend
60 app_aliases = dict(
60 app_aliases = dict(
61 ip = 'IPythonConsoleApp.ip',
61 ip = 'IPythonConsoleApp.ip',
62 transport = 'IPythonConsoleApp.transport',
62 transport = 'IPythonConsoleApp.transport',
63 hb = 'IPythonConsoleApp.hb_port',
63 hb = 'IPythonConsoleApp.hb_port',
64 shell = 'IPythonConsoleApp.shell_port',
64 shell = 'IPythonConsoleApp.shell_port',
65 iopub = 'IPythonConsoleApp.iopub_port',
65 iopub = 'IPythonConsoleApp.iopub_port',
66 stdin = 'IPythonConsoleApp.stdin_port',
66 stdin = 'IPythonConsoleApp.stdin_port',
67 existing = 'IPythonConsoleApp.existing',
67 existing = 'IPythonConsoleApp.existing',
68 f = 'IPythonConsoleApp.connection_file',
68 f = 'IPythonConsoleApp.connection_file',
69
69
70 kernel = 'IPythonConsoleApp.kernel_name',
70 kernel = 'IPythonConsoleApp.kernel_name',
71
71
72 ssh = 'IPythonConsoleApp.sshserver',
72 ssh = 'IPythonConsoleApp.sshserver',
73 )
73 )
74 aliases.update(app_aliases)
74 aliases.update(app_aliases)
75
75
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77 # Classes
77 # Classes
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79
79
80 classes = [KernelManager, ProfileDir, Session]
80 classes = [KernelManager, ProfileDir, Session]
81
81
82 class IPythonConsoleApp(ConnectionFileMixin):
82 class IPythonConsoleApp(ConnectionFileMixin):
83 name = 'ipython-console-mixin'
83 name = 'ipython-console-mixin'
84
84
85 description = """
85 description = """
86 The IPython Mixin Console.
86 The IPython Mixin Console.
87
87
88 This class contains the common portions of console client (QtConsole,
88 This class contains the common portions of console client (QtConsole,
89 ZMQ-based terminal console, etc). It is not a full console, in that
89 ZMQ-based terminal console, etc). It is not a full console, in that
90 launched terminal subprocesses will not be able to accept input.
90 launched terminal subprocesses will not be able to accept input.
91
91
92 The Console using this mixing supports various extra features beyond
92 The Console using this mixing supports various extra features beyond
93 the single-process Terminal IPython shell, such as connecting to
93 the single-process Terminal IPython shell, such as connecting to
94 existing kernel, via:
94 existing kernel, via:
95
95
96 ipython <appname> --existing
96 ipython <appname> --existing
97
97
98 as well as tunnel via SSH
98 as well as tunnel via SSH
99
99
100 """
100 """
101
101
102 classes = classes
102 classes = classes
103 flags = Dict(flags)
103 flags = Dict(flags)
104 aliases = Dict(aliases)
104 aliases = Dict(aliases)
105 kernel_manager_class = KernelManager
105 kernel_manager_class = KernelManager
106 kernel_client_class = BlockingKernelClient
106 kernel_client_class = BlockingKernelClient
107
107
108 kernel_argv = List(Unicode)
108 kernel_argv = List(Unicode)
109 # frontend flags&aliases to be stripped when building kernel_argv
109 # frontend flags&aliases to be stripped when building kernel_argv
110 frontend_flags = Any(app_flags)
110 frontend_flags = Any(app_flags)
111 frontend_aliases = Any(app_aliases)
111 frontend_aliases = Any(app_aliases)
112
112
113 # create requested profiles by default, if they don't exist:
113 # create requested profiles by default, if they don't exist:
114 auto_create = CBool(True)
114 auto_create = CBool(True)
115 # connection info:
115 # connection info:
116
116
117 sshserver = Unicode('', config=True,
117 sshserver = Unicode('', config=True,
118 help="""The SSH server to use to connect to the kernel.""")
118 help="""The SSH server to use to connect to the kernel.""")
119 sshkey = Unicode('', config=True,
119 sshkey = Unicode('', config=True,
120 help="""Path to the ssh key to use for logging in to the ssh server.""")
120 help="""Path to the ssh key to use for logging in to the ssh server.""")
121
121
122 def _connection_file_default(self):
122 def _connection_file_default(self):
123 return 'kernel-%i.json' % os.getpid()
123 return 'kernel-%i.json' % os.getpid()
124
124
125 existing = CUnicode('', config=True,
125 existing = CUnicode('', config=True,
126 help="""Connect to an already running kernel""")
126 help="""Connect to an already running kernel""")
127
127
128 kernel_name = Unicode('python', config=True,
128 kernel_name = Unicode('python', config=True,
129 help="""The name of the default kernel to start.""")
129 help="""The name of the default kernel to start.""")
130
130
131 confirm_exit = CBool(True, config=True,
131 confirm_exit = CBool(True, config=True,
132 help="""
132 help="""
133 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
133 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
134 to force a direct exit without any confirmation.""",
134 to force a direct exit without any confirmation.""",
135 )
135 )
136
136
137 def build_kernel_argv(self, argv=None):
137 def build_kernel_argv(self, argv=None):
138 """build argv to be passed to kernel subprocess
138 """build argv to be passed to kernel subprocess
139
139
140 Override in subclasses if any args should be passed to the kernel
140 Override in subclasses if any args should be passed to the kernel
141 """
141 """
142 self.kernel_argv = self.extra_args
142 self.kernel_argv = self.extra_args
143
143
144 def init_connection_file(self):
144 def init_connection_file(self):
145 """find the connection file, and load the info if found.
145 """find the connection file, and load the info if found.
146
146
147 The current working directory and the current profile's security
147 The current working directory and the current profile's security
148 directory will be searched for the file if it is not given by
148 directory will be searched for the file if it is not given by
149 absolute path.
149 absolute path.
150
150
151 When attempting to connect to an existing kernel and the `--existing`
151 When attempting to connect to an existing kernel and the `--existing`
152 argument does not match an existing file, it will be interpreted as a
152 argument does not match an existing file, it will be interpreted as a
153 fileglob, and the matching file in the current profile's security dir
153 fileglob, and the matching file in the current profile's security dir
154 with the latest access time will be used.
154 with the latest access time will be used.
155
155
156 After this method is called, self.connection_file contains the *full path*
156 After this method is called, self.connection_file contains the *full path*
157 to the connection file, never just its name.
157 to the connection file, never just its name.
158 """
158 """
159 if self.existing:
159 if self.existing:
160 try:
160 try:
161 cf = find_connection_file(self.existing)
161 cf = find_connection_file(self.existing)
162 except Exception:
162 except Exception:
163 self.log.critical("Could not find existing kernel connection file %s", self.existing)
163 self.log.critical("Could not find existing kernel connection file %s", self.existing)
164 self.exit(1)
164 self.exit(1)
165 self.log.debug("Connecting to existing kernel: %s" % cf)
165 self.log.debug("Connecting to existing kernel: %s" % cf)
166 self.connection_file = cf
166 self.connection_file = cf
167 else:
167 else:
168 # not existing, check if we are going to write the file
168 # not existing, check if we are going to write the file
169 # and ensure that self.connection_file is a full path, not just the shortname
169 # and ensure that self.connection_file is a full path, not just the shortname
170 try:
170 try:
171 cf = find_connection_file(self.connection_file)
171 cf = find_connection_file(self.connection_file)
172 except Exception:
172 except Exception:
173 # file might not exist
173 # file might not exist
174 if self.connection_file == os.path.basename(self.connection_file):
174 if self.connection_file == os.path.basename(self.connection_file):
175 # just shortname, put it in security dir
175 # just shortname, put it in security dir
176 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
176 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
177 else:
177 else:
178 cf = self.connection_file
178 cf = self.connection_file
179 self.connection_file = cf
179 self.connection_file = cf
180 try:
180 try:
181 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
181 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
182 except IOError:
182 except IOError:
183 self.log.debug("Connection File not found: %s", self.connection_file)
183 self.log.debug("Connection File not found: %s", self.connection_file)
184 return
184 return
185
185
186 # should load_connection_file only be used for existing?
186 # should load_connection_file only be used for existing?
187 # as it is now, this allows reusing ports if an existing
187 # as it is now, this allows reusing ports if an existing
188 # file is requested
188 # file is requested
189 try:
189 try:
190 self.load_connection_file()
190 self.load_connection_file()
191 except Exception:
191 except Exception:
192 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
192 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
193 self.exit(1)
193 self.exit(1)
194
194
195 def init_ssh(self):
195 def init_ssh(self):
196 """set up ssh tunnels, if needed."""
196 """set up ssh tunnels, if needed."""
197 if not self.existing or (not self.sshserver and not self.sshkey):
197 if not self.existing or (not self.sshserver and not self.sshkey):
198 return
198 return
199 self.load_connection_file()
199 self.load_connection_file()
200
200
201 transport = self.transport
201 transport = self.transport
202 ip = self.ip
202 ip = self.ip
203
203
204 if transport != 'tcp':
204 if transport != 'tcp':
205 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
205 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
206 sys.exit(-1)
206 sys.exit(-1)
207
207
208 if self.sshkey and not self.sshserver:
208 if self.sshkey and not self.sshserver:
209 # specifying just the key implies that we are connecting directly
209 # specifying just the key implies that we are connecting directly
210 self.sshserver = ip
210 self.sshserver = ip
211 ip = localhost()
211 ip = localhost()
212
212
213 # build connection dict for tunnels:
213 # build connection dict for tunnels:
214 info = dict(ip=ip,
214 info = dict(ip=ip,
215 shell_port=self.shell_port,
215 shell_port=self.shell_port,
216 iopub_port=self.iopub_port,
216 iopub_port=self.iopub_port,
217 stdin_port=self.stdin_port,
217 stdin_port=self.stdin_port,
218 hb_port=self.hb_port
218 hb_port=self.hb_port
219 )
219 )
220
220
221 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
221 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
222
222
223 # tunnels return a new set of ports, which will be on localhost:
223 # tunnels return a new set of ports, which will be on localhost:
224 self.ip = localhost()
224 self.ip = localhost()
225 try:
225 try:
226 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
226 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
227 except:
227 except:
228 # even catch KeyboardInterrupt
228 # even catch KeyboardInterrupt
229 self.log.error("Could not setup tunnels", exc_info=True)
229 self.log.error("Could not setup tunnels", exc_info=True)
230 self.exit(1)
230 self.exit(1)
231
231
232 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
232 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
233
233
234 cf = self.connection_file
234 cf = self.connection_file
235 base,ext = os.path.splitext(cf)
235 base,ext = os.path.splitext(cf)
236 base = os.path.basename(base)
236 base = os.path.basename(base)
237 self.connection_file = os.path.basename(base)+'-ssh'+ext
237 self.connection_file = os.path.basename(base)+'-ssh'+ext
238 self.log.info("To connect another client via this tunnel, use:")
238 self.log.info("To connect another client via this tunnel, use:")
239 self.log.info("--existing %s" % self.connection_file)
239 self.log.info("--existing %s" % self.connection_file)
240
240
241 def _new_connection_file(self):
241 def _new_connection_file(self):
242 cf = ''
242 cf = ''
243 while not cf:
243 while not cf:
244 # we don't need a 128b id to distinguish kernels, use more readable
244 # we don't need a 128b id to distinguish kernels, use more readable
245 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
245 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
246 # kernels can subclass.
246 # kernels can subclass.
247 ident = str(uuid.uuid4()).split('-')[-1]
247 ident = str(uuid.uuid4()).split('-')[-1]
248 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
248 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
249 # only keep if it's actually new. Protect against unlikely collision
249 # only keep if it's actually new. Protect against unlikely collision
250 # in 48b random search space
250 # in 48b random search space
251 cf = cf if not os.path.exists(cf) else ''
251 cf = cf if not os.path.exists(cf) else ''
252 return cf
252 return cf
253
253
254 def init_kernel_manager(self):
254 def init_kernel_manager(self):
255 # Don't let Qt or ZMQ swallow KeyboardInterupts.
255 # Don't let Qt or ZMQ swallow KeyboardInterupts.
256 if self.existing:
256 if self.existing:
257 self.kernel_manager = None
257 self.kernel_manager = None
258 return
258 return
259 signal.signal(signal.SIGINT, signal.SIG_DFL)
259 signal.signal(signal.SIGINT, signal.SIG_DFL)
260
260
261 # Create a KernelManager and start a kernel.
261 # Create a KernelManager and start a kernel.
262 try:
262 try:
263 self.kernel_manager = self.kernel_manager_class(
263 self.kernel_manager = self.kernel_manager_class(
264 ip=self.ip,
264 ip=self.ip,
265 session=self.session,
265 session=self.session,
266 transport=self.transport,
266 transport=self.transport,
267 shell_port=self.shell_port,
267 shell_port=self.shell_port,
268 iopub_port=self.iopub_port,
268 iopub_port=self.iopub_port,
269 stdin_port=self.stdin_port,
269 stdin_port=self.stdin_port,
270 hb_port=self.hb_port,
270 hb_port=self.hb_port,
271 connection_file=self.connection_file,
271 connection_file=self.connection_file,
272 kernel_name=self.kernel_name,
272 kernel_name=self.kernel_name,
273 parent=self,
273 parent=self,
274 ipython_dir=self.ipython_dir,
274 ipython_dir=self.ipython_dir,
275 )
275 )
276 except NoSuchKernel:
276 except NoSuchKernel:
277 self.log.critical("Could not find kernel %s", self.kernel_name)
277 self.log.critical("Could not find kernel %s", self.kernel_name)
278 self.exit(1)
278 self.exit(1)
279
279
280 self.kernel_manager.client_factory = self.kernel_client_class
280 self.kernel_manager.client_factory = self.kernel_client_class
281 # FIXME: remove special treatment of IPython kernels
281 # FIXME: remove special treatment of IPython kernels
282 kwargs = {}
282 kwargs = {}
283 if self.kernel_manager.ipython_kernel:
283 if self.kernel_manager.ipython_kernel:
284 kwargs['extra_arguments'] = self.kernel_argv
284 kwargs['extra_arguments'] = self.kernel_argv
285 self.kernel_manager.start_kernel(**kwargs)
285 self.kernel_manager.start_kernel(**kwargs)
286 atexit.register(self.kernel_manager.cleanup_ipc_files)
286 atexit.register(self.kernel_manager.cleanup_ipc_files)
287
287
288 if self.sshserver:
288 if self.sshserver:
289 # ssh, write new connection file
289 # ssh, write new connection file
290 self.kernel_manager.write_connection_file()
290 self.kernel_manager.write_connection_file()
291
291
292 # in case KM defaults / ssh writing changes things:
292 # in case KM defaults / ssh writing changes things:
293 km = self.kernel_manager
293 km = self.kernel_manager
294 self.shell_port=km.shell_port
294 self.shell_port=km.shell_port
295 self.iopub_port=km.iopub_port
295 self.iopub_port=km.iopub_port
296 self.stdin_port=km.stdin_port
296 self.stdin_port=km.stdin_port
297 self.hb_port=km.hb_port
297 self.hb_port=km.hb_port
298 self.connection_file = km.connection_file
298 self.connection_file = km.connection_file
299
299
300 atexit.register(self.kernel_manager.cleanup_connection_file)
300 atexit.register(self.kernel_manager.cleanup_connection_file)
301
301
302 def init_kernel_client(self):
302 def init_kernel_client(self):
303 if self.kernel_manager is not None:
303 if self.kernel_manager is not None:
304 self.kernel_client = self.kernel_manager.client()
304 self.kernel_client = self.kernel_manager.client()
305 else:
305 else:
306 self.kernel_client = self.kernel_client_class(
306 self.kernel_client = self.kernel_client_class(
307 session=self.session,
307 session=self.session,
308 ip=self.ip,
308 ip=self.ip,
309 transport=self.transport,
309 transport=self.transport,
310 shell_port=self.shell_port,
310 shell_port=self.shell_port,
311 iopub_port=self.iopub_port,
311 iopub_port=self.iopub_port,
312 stdin_port=self.stdin_port,
312 stdin_port=self.stdin_port,
313 hb_port=self.hb_port,
313 hb_port=self.hb_port,
314 connection_file=self.connection_file,
314 connection_file=self.connection_file,
315 parent=self,
315 parent=self,
316 )
316 )
317
317
318 self.kernel_client.start_channels()
318 self.kernel_client.start_channels()
319
319
320
320
321
321
322 def initialize(self, argv=None):
322 def initialize(self, argv=None):
323 """
323 """
324 Classes which mix this class in should call:
324 Classes which mix this class in should call:
325 IPythonConsoleApp.initialize(self,argv)
325 IPythonConsoleApp.initialize(self,argv)
326 """
326 """
327 self.init_connection_file()
327 self.init_connection_file()
328 self.init_ssh()
328 self.init_ssh()
329 self.init_kernel_manager()
329 self.init_kernel_manager()
330 self.init_kernel_client()
330 self.init_kernel_client()
331
331
@@ -1,442 +1,442 b''
1 """Base class to manage a running kernel"""
1 """Base class to manage a running kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from contextlib import contextmanager
8 from contextlib import contextmanager
9 import os
9 import os
10 import re
10 import re
11 import signal
11 import signal
12 import sys
12 import sys
13 import time
13 import time
14 import warnings
14 import warnings
15 try:
15 try:
16 from queue import Empty # Py 3
16 from queue import Empty # Py 3
17 except ImportError:
17 except ImportError:
18 from Queue import Empty # Py 2
18 from Queue import Empty # Py 2
19
19
20 import zmq
20 import zmq
21
21
22 from IPython.utils.importstring import import_item
22 from IPython.utils.importstring import import_item
23 from IPython.utils.localinterfaces import is_local_ip, local_ips
23 from .localinterfaces import is_local_ip, local_ips
24 from IPython.utils.path import get_ipython_dir
24 from IPython.utils.path import get_ipython_dir
25 from IPython.utils.traitlets import (
25 from IPython.utils.traitlets import (
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
26 Any, Instance, Unicode, List, Bool, Type, DottedObjectName
27 )
27 )
28 from jupyter_client import (
28 from jupyter_client import (
29 launch_kernel,
29 launch_kernel,
30 kernelspec,
30 kernelspec,
31 )
31 )
32 from .connect import ConnectionFileMixin
32 from .connect import ConnectionFileMixin
33 from .session import Session
33 from .session import Session
34 from .managerabc import (
34 from .managerabc import (
35 KernelManagerABC
35 KernelManagerABC
36 )
36 )
37
37
38
38
39 class KernelManager(ConnectionFileMixin):
39 class KernelManager(ConnectionFileMixin):
40 """Manages a single kernel in a subprocess on this host.
40 """Manages a single kernel in a subprocess on this host.
41
41
42 This version starts kernels with Popen.
42 This version starts kernels with Popen.
43 """
43 """
44
44
45 # The PyZMQ Context to use for communication with the kernel.
45 # The PyZMQ Context to use for communication with the kernel.
46 context = Instance(zmq.Context)
46 context = Instance(zmq.Context)
47 def _context_default(self):
47 def _context_default(self):
48 return zmq.Context.instance()
48 return zmq.Context.instance()
49
49
50 # the class to create with our `client` method
50 # the class to create with our `client` method
51 client_class = DottedObjectName('jupyter_client.blocking.BlockingKernelClient')
51 client_class = DottedObjectName('jupyter_client.blocking.BlockingKernelClient')
52 client_factory = Type(allow_none=True)
52 client_factory = Type(allow_none=True)
53 def _client_class_changed(self, name, old, new):
53 def _client_class_changed(self, name, old, new):
54 self.client_factory = import_item(str(new))
54 self.client_factory = import_item(str(new))
55
55
56 # The kernel process with which the KernelManager is communicating.
56 # The kernel process with which the KernelManager is communicating.
57 # generally a Popen instance
57 # generally a Popen instance
58 kernel = Any()
58 kernel = Any()
59
59
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
60 kernel_spec_manager = Instance(kernelspec.KernelSpecManager)
61
61
62 def _kernel_spec_manager_default(self):
62 def _kernel_spec_manager_default(self):
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
63 return kernelspec.KernelSpecManager(ipython_dir=self.ipython_dir)
64
64
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
65 kernel_name = Unicode(kernelspec.NATIVE_KERNEL_NAME)
66
66
67 kernel_spec = Instance(kernelspec.KernelSpec)
67 kernel_spec = Instance(kernelspec.KernelSpec)
68
68
69 def _kernel_spec_default(self):
69 def _kernel_spec_default(self):
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
70 return self.kernel_spec_manager.get_kernel_spec(self.kernel_name)
71
71
72 def _kernel_name_changed(self, name, old, new):
72 def _kernel_name_changed(self, name, old, new):
73 if new == 'python':
73 if new == 'python':
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
74 self.kernel_name = kernelspec.NATIVE_KERNEL_NAME
75 # This triggered another run of this function, so we can exit now
75 # This triggered another run of this function, so we can exit now
76 return
76 return
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
77 self.kernel_spec = self.kernel_spec_manager.get_kernel_spec(new)
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
78 self.ipython_kernel = new in {'python', 'python2', 'python3'}
79
79
80 kernel_cmd = List(Unicode, config=True,
80 kernel_cmd = List(Unicode, config=True,
81 help="""DEPRECATED: Use kernel_name instead.
81 help="""DEPRECATED: Use kernel_name instead.
82
82
83 The Popen Command to launch the kernel.
83 The Popen Command to launch the kernel.
84 Override this if you have a custom kernel.
84 Override this if you have a custom kernel.
85 If kernel_cmd is specified in a configuration file,
85 If kernel_cmd is specified in a configuration file,
86 IPython does not pass any arguments to the kernel,
86 IPython does not pass any arguments to the kernel,
87 because it cannot make any assumptions about the
87 because it cannot make any assumptions about the
88 arguments that the kernel understands. In particular,
88 arguments that the kernel understands. In particular,
89 this means that the kernel does not receive the
89 this means that the kernel does not receive the
90 option --debug if it given on the IPython command line.
90 option --debug if it given on the IPython command line.
91 """
91 """
92 )
92 )
93
93
94 def _kernel_cmd_changed(self, name, old, new):
94 def _kernel_cmd_changed(self, name, old, new):
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
95 warnings.warn("Setting kernel_cmd is deprecated, use kernel_spec to "
96 "start different kernels.")
96 "start different kernels.")
97 self.ipython_kernel = False
97 self.ipython_kernel = False
98
98
99 ipython_kernel = Bool(True)
99 ipython_kernel = Bool(True)
100
100
101 ipython_dir = Unicode()
101 ipython_dir = Unicode()
102 def _ipython_dir_default(self):
102 def _ipython_dir_default(self):
103 return get_ipython_dir()
103 return get_ipython_dir()
104
104
105 # Protected traits
105 # Protected traits
106 _launch_args = Any()
106 _launch_args = Any()
107 _control_socket = Any()
107 _control_socket = Any()
108
108
109 _restarter = Any()
109 _restarter = Any()
110
110
111 autorestart = Bool(False, config=True,
111 autorestart = Bool(False, config=True,
112 help="""Should we autorestart the kernel if it dies."""
112 help="""Should we autorestart the kernel if it dies."""
113 )
113 )
114
114
115 def __del__(self):
115 def __del__(self):
116 self._close_control_socket()
116 self._close_control_socket()
117 self.cleanup_connection_file()
117 self.cleanup_connection_file()
118
118
119 #--------------------------------------------------------------------------
119 #--------------------------------------------------------------------------
120 # Kernel restarter
120 # Kernel restarter
121 #--------------------------------------------------------------------------
121 #--------------------------------------------------------------------------
122
122
123 def start_restarter(self):
123 def start_restarter(self):
124 pass
124 pass
125
125
126 def stop_restarter(self):
126 def stop_restarter(self):
127 pass
127 pass
128
128
129 def add_restart_callback(self, callback, event='restart'):
129 def add_restart_callback(self, callback, event='restart'):
130 """register a callback to be called when a kernel is restarted"""
130 """register a callback to be called when a kernel is restarted"""
131 if self._restarter is None:
131 if self._restarter is None:
132 return
132 return
133 self._restarter.add_callback(callback, event)
133 self._restarter.add_callback(callback, event)
134
134
135 def remove_restart_callback(self, callback, event='restart'):
135 def remove_restart_callback(self, callback, event='restart'):
136 """unregister a callback to be called when a kernel is restarted"""
136 """unregister a callback to be called when a kernel is restarted"""
137 if self._restarter is None:
137 if self._restarter is None:
138 return
138 return
139 self._restarter.remove_callback(callback, event)
139 self._restarter.remove_callback(callback, event)
140
140
141 #--------------------------------------------------------------------------
141 #--------------------------------------------------------------------------
142 # create a Client connected to our Kernel
142 # create a Client connected to our Kernel
143 #--------------------------------------------------------------------------
143 #--------------------------------------------------------------------------
144
144
145 def client(self, **kwargs):
145 def client(self, **kwargs):
146 """Create a client configured to connect to our kernel"""
146 """Create a client configured to connect to our kernel"""
147 if self.client_factory is None:
147 if self.client_factory is None:
148 self.client_factory = import_item(self.client_class)
148 self.client_factory = import_item(self.client_class)
149
149
150 kw = {}
150 kw = {}
151 kw.update(self.get_connection_info())
151 kw.update(self.get_connection_info())
152 kw.update(dict(
152 kw.update(dict(
153 connection_file=self.connection_file,
153 connection_file=self.connection_file,
154 session=self.session,
154 session=self.session,
155 parent=self,
155 parent=self,
156 ))
156 ))
157
157
158 # add kwargs last, for manual overrides
158 # add kwargs last, for manual overrides
159 kw.update(kwargs)
159 kw.update(kwargs)
160 return self.client_factory(**kw)
160 return self.client_factory(**kw)
161
161
162 #--------------------------------------------------------------------------
162 #--------------------------------------------------------------------------
163 # Kernel management
163 # Kernel management
164 #--------------------------------------------------------------------------
164 #--------------------------------------------------------------------------
165
165
166 def format_kernel_cmd(self, extra_arguments=None):
166 def format_kernel_cmd(self, extra_arguments=None):
167 """replace templated args (e.g. {connection_file})"""
167 """replace templated args (e.g. {connection_file})"""
168 extra_arguments = extra_arguments or []
168 extra_arguments = extra_arguments or []
169 if self.kernel_cmd:
169 if self.kernel_cmd:
170 cmd = self.kernel_cmd + extra_arguments
170 cmd = self.kernel_cmd + extra_arguments
171 else:
171 else:
172 cmd = self.kernel_spec.argv + extra_arguments
172 cmd = self.kernel_spec.argv + extra_arguments
173
173
174 ns = dict(connection_file=self.connection_file)
174 ns = dict(connection_file=self.connection_file)
175 ns.update(self._launch_args)
175 ns.update(self._launch_args)
176
176
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
177 pat = re.compile(r'\{([A-Za-z0-9_]+)\}')
178 def from_ns(match):
178 def from_ns(match):
179 """Get the key out of ns if it's there, otherwise no change."""
179 """Get the key out of ns if it's there, otherwise no change."""
180 return ns.get(match.group(1), match.group())
180 return ns.get(match.group(1), match.group())
181
181
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
182 return [ pat.sub(from_ns, arg) for arg in cmd ]
183
183
184 def _launch_kernel(self, kernel_cmd, **kw):
184 def _launch_kernel(self, kernel_cmd, **kw):
185 """actually launch the kernel
185 """actually launch the kernel
186
186
187 override in a subclass to launch kernel subprocesses differently
187 override in a subclass to launch kernel subprocesses differently
188 """
188 """
189 return launch_kernel(kernel_cmd, **kw)
189 return launch_kernel(kernel_cmd, **kw)
190
190
191 # Control socket used for polite kernel shutdown
191 # Control socket used for polite kernel shutdown
192
192
193 def _connect_control_socket(self):
193 def _connect_control_socket(self):
194 if self._control_socket is None:
194 if self._control_socket is None:
195 self._control_socket = self.connect_control()
195 self._control_socket = self.connect_control()
196 self._control_socket.linger = 100
196 self._control_socket.linger = 100
197
197
198 def _close_control_socket(self):
198 def _close_control_socket(self):
199 if self._control_socket is None:
199 if self._control_socket is None:
200 return
200 return
201 self._control_socket.close()
201 self._control_socket.close()
202 self._control_socket = None
202 self._control_socket = None
203
203
204 def start_kernel(self, **kw):
204 def start_kernel(self, **kw):
205 """Starts a kernel on this host in a separate process.
205 """Starts a kernel on this host in a separate process.
206
206
207 If random ports (port=0) are being used, this method must be called
207 If random ports (port=0) are being used, this method must be called
208 before the channels are created.
208 before the channels are created.
209
209
210 Parameters
210 Parameters
211 ----------
211 ----------
212 **kw : optional
212 **kw : optional
213 keyword arguments that are passed down to build the kernel_cmd
213 keyword arguments that are passed down to build the kernel_cmd
214 and launching the kernel (e.g. Popen kwargs).
214 and launching the kernel (e.g. Popen kwargs).
215 """
215 """
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
216 if self.transport == 'tcp' and not is_local_ip(self.ip):
217 raise RuntimeError("Can only launch a kernel on a local interface. "
217 raise RuntimeError("Can only launch a kernel on a local interface. "
218 "Make sure that the '*_address' attributes are "
218 "Make sure that the '*_address' attributes are "
219 "configured properly. "
219 "configured properly. "
220 "Currently valid addresses are: %s" % local_ips()
220 "Currently valid addresses are: %s" % local_ips()
221 )
221 )
222
222
223 # write connection file / get default ports
223 # write connection file / get default ports
224 self.write_connection_file()
224 self.write_connection_file()
225
225
226 # save kwargs for use in restart
226 # save kwargs for use in restart
227 self._launch_args = kw.copy()
227 self._launch_args = kw.copy()
228 # build the Popen cmd
228 # build the Popen cmd
229 extra_arguments = kw.pop('extra_arguments', [])
229 extra_arguments = kw.pop('extra_arguments', [])
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
230 kernel_cmd = self.format_kernel_cmd(extra_arguments=extra_arguments)
231 if self.kernel_cmd:
231 if self.kernel_cmd:
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
232 # If kernel_cmd has been set manually, don't refer to a kernel spec
233 env = os.environ
233 env = os.environ
234 else:
234 else:
235 # Environment variables from kernel spec are added to os.environ
235 # Environment variables from kernel spec are added to os.environ
236 env = os.environ.copy()
236 env = os.environ.copy()
237 env.update(self.kernel_spec.env or {})
237 env.update(self.kernel_spec.env or {})
238 # launch the kernel subprocess
238 # launch the kernel subprocess
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
239 self.kernel = self._launch_kernel(kernel_cmd, env=env,
240 **kw)
240 **kw)
241 self.start_restarter()
241 self.start_restarter()
242 self._connect_control_socket()
242 self._connect_control_socket()
243
243
244 def request_shutdown(self, restart=False):
244 def request_shutdown(self, restart=False):
245 """Send a shutdown request via control channel
245 """Send a shutdown request via control channel
246
246
247 On Windows, this just kills kernels instead, because the shutdown
247 On Windows, this just kills kernels instead, because the shutdown
248 messages don't work.
248 messages don't work.
249 """
249 """
250 content = dict(restart=restart)
250 content = dict(restart=restart)
251 msg = self.session.msg("shutdown_request", content=content)
251 msg = self.session.msg("shutdown_request", content=content)
252 self.session.send(self._control_socket, msg)
252 self.session.send(self._control_socket, msg)
253
253
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
254 def finish_shutdown(self, waittime=1, pollinterval=0.1):
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
255 """Wait for kernel shutdown, then kill process if it doesn't shutdown.
256
256
257 This does not send shutdown requests - use :meth:`request_shutdown`
257 This does not send shutdown requests - use :meth:`request_shutdown`
258 first.
258 first.
259 """
259 """
260 for i in range(int(waittime/pollinterval)):
260 for i in range(int(waittime/pollinterval)):
261 if self.is_alive():
261 if self.is_alive():
262 time.sleep(pollinterval)
262 time.sleep(pollinterval)
263 else:
263 else:
264 break
264 break
265 else:
265 else:
266 # OK, we've waited long enough.
266 # OK, we've waited long enough.
267 if self.has_kernel:
267 if self.has_kernel:
268 self._kill_kernel()
268 self._kill_kernel()
269
269
270 def cleanup(self, connection_file=True):
270 def cleanup(self, connection_file=True):
271 """Clean up resources when the kernel is shut down"""
271 """Clean up resources when the kernel is shut down"""
272 if connection_file:
272 if connection_file:
273 self.cleanup_connection_file()
273 self.cleanup_connection_file()
274
274
275 self.cleanup_ipc_files()
275 self.cleanup_ipc_files()
276 self._close_control_socket()
276 self._close_control_socket()
277
277
278 def shutdown_kernel(self, now=False, restart=False):
278 def shutdown_kernel(self, now=False, restart=False):
279 """Attempts to the stop the kernel process cleanly.
279 """Attempts to the stop the kernel process cleanly.
280
280
281 This attempts to shutdown the kernels cleanly by:
281 This attempts to shutdown the kernels cleanly by:
282
282
283 1. Sending it a shutdown message over the shell channel.
283 1. Sending it a shutdown message over the shell channel.
284 2. If that fails, the kernel is shutdown forcibly by sending it
284 2. If that fails, the kernel is shutdown forcibly by sending it
285 a signal.
285 a signal.
286
286
287 Parameters
287 Parameters
288 ----------
288 ----------
289 now : bool
289 now : bool
290 Should the kernel be forcible killed *now*. This skips the
290 Should the kernel be forcible killed *now*. This skips the
291 first, nice shutdown attempt.
291 first, nice shutdown attempt.
292 restart: bool
292 restart: bool
293 Will this kernel be restarted after it is shutdown. When this
293 Will this kernel be restarted after it is shutdown. When this
294 is True, connection files will not be cleaned up.
294 is True, connection files will not be cleaned up.
295 """
295 """
296 # Stop monitoring for restarting while we shutdown.
296 # Stop monitoring for restarting while we shutdown.
297 self.stop_restarter()
297 self.stop_restarter()
298
298
299 if now:
299 if now:
300 self._kill_kernel()
300 self._kill_kernel()
301 else:
301 else:
302 self.request_shutdown(restart=restart)
302 self.request_shutdown(restart=restart)
303 # Don't send any additional kernel kill messages immediately, to give
303 # Don't send any additional kernel kill messages immediately, to give
304 # the kernel a chance to properly execute shutdown actions. Wait for at
304 # the kernel a chance to properly execute shutdown actions. Wait for at
305 # most 1s, checking every 0.1s.
305 # most 1s, checking every 0.1s.
306 self.finish_shutdown()
306 self.finish_shutdown()
307
307
308 self.cleanup(connection_file=not restart)
308 self.cleanup(connection_file=not restart)
309
309
310 def restart_kernel(self, now=False, **kw):
310 def restart_kernel(self, now=False, **kw):
311 """Restarts a kernel with the arguments that were used to launch it.
311 """Restarts a kernel with the arguments that were used to launch it.
312
312
313 If the old kernel was launched with random ports, the same ports will be
313 If the old kernel was launched with random ports, the same ports will be
314 used for the new kernel. The same connection file is used again.
314 used for the new kernel. The same connection file is used again.
315
315
316 Parameters
316 Parameters
317 ----------
317 ----------
318 now : bool, optional
318 now : bool, optional
319 If True, the kernel is forcefully restarted *immediately*, without
319 If True, the kernel is forcefully restarted *immediately*, without
320 having a chance to do any cleanup action. Otherwise the kernel is
320 having a chance to do any cleanup action. Otherwise the kernel is
321 given 1s to clean up before a forceful restart is issued.
321 given 1s to clean up before a forceful restart is issued.
322
322
323 In all cases the kernel is restarted, the only difference is whether
323 In all cases the kernel is restarted, the only difference is whether
324 it is given a chance to perform a clean shutdown or not.
324 it is given a chance to perform a clean shutdown or not.
325
325
326 **kw : optional
326 **kw : optional
327 Any options specified here will overwrite those used to launch the
327 Any options specified here will overwrite those used to launch the
328 kernel.
328 kernel.
329 """
329 """
330 if self._launch_args is None:
330 if self._launch_args is None:
331 raise RuntimeError("Cannot restart the kernel. "
331 raise RuntimeError("Cannot restart the kernel. "
332 "No previous call to 'start_kernel'.")
332 "No previous call to 'start_kernel'.")
333 else:
333 else:
334 # Stop currently running kernel.
334 # Stop currently running kernel.
335 self.shutdown_kernel(now=now, restart=True)
335 self.shutdown_kernel(now=now, restart=True)
336
336
337 # Start new kernel.
337 # Start new kernel.
338 self._launch_args.update(kw)
338 self._launch_args.update(kw)
339 self.start_kernel(**self._launch_args)
339 self.start_kernel(**self._launch_args)
340
340
341 @property
341 @property
342 def has_kernel(self):
342 def has_kernel(self):
343 """Has a kernel been started that we are managing."""
343 """Has a kernel been started that we are managing."""
344 return self.kernel is not None
344 return self.kernel is not None
345
345
346 def _kill_kernel(self):
346 def _kill_kernel(self):
347 """Kill the running kernel.
347 """Kill the running kernel.
348
348
349 This is a private method, callers should use shutdown_kernel(now=True).
349 This is a private method, callers should use shutdown_kernel(now=True).
350 """
350 """
351 if self.has_kernel:
351 if self.has_kernel:
352
352
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
353 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
354 # TerminateProcess() on Win32).
354 # TerminateProcess() on Win32).
355 try:
355 try:
356 self.kernel.kill()
356 self.kernel.kill()
357 except OSError as e:
357 except OSError as e:
358 # In Windows, we will get an Access Denied error if the process
358 # In Windows, we will get an Access Denied error if the process
359 # has already terminated. Ignore it.
359 # has already terminated. Ignore it.
360 if sys.platform == 'win32':
360 if sys.platform == 'win32':
361 if e.winerror != 5:
361 if e.winerror != 5:
362 raise
362 raise
363 # On Unix, we may get an ESRCH error if the process has already
363 # On Unix, we may get an ESRCH error if the process has already
364 # terminated. Ignore it.
364 # terminated. Ignore it.
365 else:
365 else:
366 from errno import ESRCH
366 from errno import ESRCH
367 if e.errno != ESRCH:
367 if e.errno != ESRCH:
368 raise
368 raise
369
369
370 # Block until the kernel terminates.
370 # Block until the kernel terminates.
371 self.kernel.wait()
371 self.kernel.wait()
372 self.kernel = None
372 self.kernel = None
373 else:
373 else:
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
374 raise RuntimeError("Cannot kill kernel. No kernel is running!")
375
375
376 def interrupt_kernel(self):
376 def interrupt_kernel(self):
377 """Interrupts the kernel by sending it a signal.
377 """Interrupts the kernel by sending it a signal.
378
378
379 Unlike ``signal_kernel``, this operation is well supported on all
379 Unlike ``signal_kernel``, this operation is well supported on all
380 platforms.
380 platforms.
381 """
381 """
382 if self.has_kernel:
382 if self.has_kernel:
383 if sys.platform == 'win32':
383 if sys.platform == 'win32':
384 from .win_interrupt import send_interrupt
384 from .win_interrupt import send_interrupt
385 send_interrupt(self.kernel.win32_interrupt_event)
385 send_interrupt(self.kernel.win32_interrupt_event)
386 else:
386 else:
387 self.kernel.send_signal(signal.SIGINT)
387 self.kernel.send_signal(signal.SIGINT)
388 else:
388 else:
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
389 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
390
390
391 def signal_kernel(self, signum):
391 def signal_kernel(self, signum):
392 """Sends a signal to the kernel.
392 """Sends a signal to the kernel.
393
393
394 Note that since only SIGTERM is supported on Windows, this function is
394 Note that since only SIGTERM is supported on Windows, this function is
395 only useful on Unix systems.
395 only useful on Unix systems.
396 """
396 """
397 if self.has_kernel:
397 if self.has_kernel:
398 self.kernel.send_signal(signum)
398 self.kernel.send_signal(signum)
399 else:
399 else:
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
400 raise RuntimeError("Cannot signal kernel. No kernel is running!")
401
401
402 def is_alive(self):
402 def is_alive(self):
403 """Is the kernel process still running?"""
403 """Is the kernel process still running?"""
404 if self.has_kernel:
404 if self.has_kernel:
405 if self.kernel.poll() is None:
405 if self.kernel.poll() is None:
406 return True
406 return True
407 else:
407 else:
408 return False
408 return False
409 else:
409 else:
410 # we don't have a kernel
410 # we don't have a kernel
411 return False
411 return False
412
412
413
413
414 KernelManagerABC.register(KernelManager)
414 KernelManagerABC.register(KernelManager)
415
415
416
416
417 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
417 def start_new_kernel(startup_timeout=60, kernel_name='python', **kwargs):
418 """Start a new kernel, and return its Manager and Client"""
418 """Start a new kernel, and return its Manager and Client"""
419 km = KernelManager(kernel_name=kernel_name)
419 km = KernelManager(kernel_name=kernel_name)
420 km.start_kernel(**kwargs)
420 km.start_kernel(**kwargs)
421 kc = km.client()
421 kc = km.client()
422 kc.start_channels()
422 kc.start_channels()
423 kc.wait_for_ready()
423 kc.wait_for_ready()
424
424
425 return km, kc
425 return km, kc
426
426
427 @contextmanager
427 @contextmanager
428 def run_kernel(**kwargs):
428 def run_kernel(**kwargs):
429 """Context manager to create a kernel in a subprocess.
429 """Context manager to create a kernel in a subprocess.
430
430
431 The kernel is shut down when the context exits.
431 The kernel is shut down when the context exits.
432
432
433 Returns
433 Returns
434 -------
434 -------
435 kernel_client: connected KernelClient instance
435 kernel_client: connected KernelClient instance
436 """
436 """
437 km, kc = start_new_kernel(**kwargs)
437 km, kc = start_new_kernel(**kwargs)
438 try:
438 try:
439 yield kc
439 yield kc
440 finally:
440 finally:
441 kc.stop_channels()
441 kc.stop_channels()
442 km.shutdown_kernel(now=True)
442 km.shutdown_kernel(now=True)
1 NO CONTENT: file renamed from IPython/utils/tests/test_localinterfaces.py to jupyter_client/tests/test_localinterfaces.py
NO CONTENT: file renamed from IPython/utils/tests/test_localinterfaces.py to jupyter_client/tests/test_localinterfaces.py
@@ -1,86 +1,86 b''
1 """Tests for the notebook kernel and session manager."""
1 """Tests for the notebook kernel and session manager."""
2
2
3 from subprocess import PIPE
3 from subprocess import PIPE
4 import time
4 import time
5 from unittest import TestCase
5 from unittest import TestCase
6
6
7 from IPython.testing import decorators as dec
7 from IPython.testing import decorators as dec
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.utils.localinterfaces import localhost
10 from ..localinterfaces import localhost
11 from jupyter_client import KernelManager
11 from jupyter_client import KernelManager
12 from jupyter_client.multikernelmanager import MultiKernelManager
12 from jupyter_client.multikernelmanager import MultiKernelManager
13
13
14 class TestKernelManager(TestCase):
14 class TestKernelManager(TestCase):
15
15
16 def _get_tcp_km(self):
16 def _get_tcp_km(self):
17 c = Config()
17 c = Config()
18 km = MultiKernelManager(config=c)
18 km = MultiKernelManager(config=c)
19 return km
19 return km
20
20
21 def _get_ipc_km(self):
21 def _get_ipc_km(self):
22 c = Config()
22 c = Config()
23 c.KernelManager.transport = 'ipc'
23 c.KernelManager.transport = 'ipc'
24 c.KernelManager.ip = 'test'
24 c.KernelManager.ip = 'test'
25 km = MultiKernelManager(config=c)
25 km = MultiKernelManager(config=c)
26 return km
26 return km
27
27
28 def _run_lifecycle(self, km):
28 def _run_lifecycle(self, km):
29 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
29 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
30 self.assertTrue(km.is_alive(kid))
30 self.assertTrue(km.is_alive(kid))
31 self.assertTrue(kid in km)
31 self.assertTrue(kid in km)
32 self.assertTrue(kid in km.list_kernel_ids())
32 self.assertTrue(kid in km.list_kernel_ids())
33 self.assertEqual(len(km),1)
33 self.assertEqual(len(km),1)
34 km.restart_kernel(kid, now=True)
34 km.restart_kernel(kid, now=True)
35 self.assertTrue(km.is_alive(kid))
35 self.assertTrue(km.is_alive(kid))
36 self.assertTrue(kid in km.list_kernel_ids())
36 self.assertTrue(kid in km.list_kernel_ids())
37 km.interrupt_kernel(kid)
37 km.interrupt_kernel(kid)
38 k = km.get_kernel(kid)
38 k = km.get_kernel(kid)
39 self.assertTrue(isinstance(k, KernelManager))
39 self.assertTrue(isinstance(k, KernelManager))
40 km.shutdown_kernel(kid, now=True)
40 km.shutdown_kernel(kid, now=True)
41 self.assertTrue(not kid in km)
41 self.assertTrue(not kid in km)
42
42
43 def _run_cinfo(self, km, transport, ip):
43 def _run_cinfo(self, km, transport, ip):
44 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
44 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
45 k = km.get_kernel(kid)
45 k = km.get_kernel(kid)
46 cinfo = km.get_connection_info(kid)
46 cinfo = km.get_connection_info(kid)
47 self.assertEqual(transport, cinfo['transport'])
47 self.assertEqual(transport, cinfo['transport'])
48 self.assertEqual(ip, cinfo['ip'])
48 self.assertEqual(ip, cinfo['ip'])
49 self.assertTrue('stdin_port' in cinfo)
49 self.assertTrue('stdin_port' in cinfo)
50 self.assertTrue('iopub_port' in cinfo)
50 self.assertTrue('iopub_port' in cinfo)
51 stream = km.connect_iopub(kid)
51 stream = km.connect_iopub(kid)
52 stream.close()
52 stream.close()
53 self.assertTrue('shell_port' in cinfo)
53 self.assertTrue('shell_port' in cinfo)
54 stream = km.connect_shell(kid)
54 stream = km.connect_shell(kid)
55 stream.close()
55 stream.close()
56 self.assertTrue('hb_port' in cinfo)
56 self.assertTrue('hb_port' in cinfo)
57 stream = km.connect_hb(kid)
57 stream = km.connect_hb(kid)
58 stream.close()
58 stream.close()
59 km.shutdown_kernel(kid, now=True)
59 km.shutdown_kernel(kid, now=True)
60
60
61 def test_tcp_lifecycle(self):
61 def test_tcp_lifecycle(self):
62 km = self._get_tcp_km()
62 km = self._get_tcp_km()
63 self._run_lifecycle(km)
63 self._run_lifecycle(km)
64
64
65 def test_shutdown_all(self):
65 def test_shutdown_all(self):
66 km = self._get_tcp_km()
66 km = self._get_tcp_km()
67 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
67 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
68 self.assertIn(kid, km)
68 self.assertIn(kid, km)
69 km.shutdown_all()
69 km.shutdown_all()
70 self.assertNotIn(kid, km)
70 self.assertNotIn(kid, km)
71 # shutdown again is okay, because we have no kernels
71 # shutdown again is okay, because we have no kernels
72 km.shutdown_all()
72 km.shutdown_all()
73
73
74 def test_tcp_cinfo(self):
74 def test_tcp_cinfo(self):
75 km = self._get_tcp_km()
75 km = self._get_tcp_km()
76 self._run_cinfo(km, 'tcp', localhost())
76 self._run_cinfo(km, 'tcp', localhost())
77
77
78 @dec.skip_win32
78 @dec.skip_win32
79 def test_ipc_lifecycle(self):
79 def test_ipc_lifecycle(self):
80 km = self._get_ipc_km()
80 km = self._get_ipc_km()
81 self._run_lifecycle(km)
81 self._run_lifecycle(km)
82
82
83 @dec.skip_win32
83 @dec.skip_win32
84 def test_ipc_cinfo(self):
84 def test_ipc_cinfo(self):
85 km = self._get_ipc_km()
85 km = self._get_ipc_km()
86 self._run_cinfo(km, 'ipc', 'test')
86 self._run_cinfo(km, 'ipc', 'test')
General Comments 0
You need to be logged in to leave comments. Login now