##// END OF EJS Templates
Backport PR #5486: disambiguate to location when no IPs can be determined...
Thomas Kluyver -
Show More
@@ -1,380 +1,390
1 """Some generic utilities for dealing with classes, urls, and serialization.
1 """Some generic utilities for dealing with classes, urls, and serialization."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4
5
5 * Min RK
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 # Standard library imports.
19 import logging
6 import logging
20 import os
7 import os
21 import re
8 import re
22 import stat
9 import stat
23 import socket
10 import socket
24 import sys
11 import sys
12 import warnings
25 from signal import signal, SIGINT, SIGABRT, SIGTERM
13 from signal import signal, SIGINT, SIGABRT, SIGTERM
26 try:
14 try:
27 from signal import SIGKILL
15 from signal import SIGKILL
28 except ImportError:
16 except ImportError:
29 SIGKILL=None
17 SIGKILL=None
30 from types import FunctionType
18 from types import FunctionType
31
19
32 try:
20 try:
33 import cPickle
21 import cPickle
34 pickle = cPickle
22 pickle = cPickle
35 except:
23 except:
36 cPickle = None
24 cPickle = None
37 import pickle
25 import pickle
38
26
39 # System library imports
40 import zmq
27 import zmq
41 from zmq.log import handlers
28 from zmq.log import handlers
42
29
43 from IPython.external.decorator import decorator
30 from IPython.external.decorator import decorator
44
31
45 # IPython imports
46 from IPython.config.application import Application
32 from IPython.config.application import Application
47 from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips
33 from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips
48 from IPython.utils.py3compat import string_types, iteritems, itervalues
34 from IPython.utils.py3compat import string_types, iteritems, itervalues
49 from IPython.kernel.zmq.log import EnginePUBHandler
35 from IPython.kernel.zmq.log import EnginePUBHandler
50 from IPython.kernel.zmq.serialize import (
36 from IPython.kernel.zmq.serialize import (
51 unserialize_object, serialize_object, pack_apply_message, unpack_apply_message
37 unserialize_object, serialize_object, pack_apply_message, unpack_apply_message
52 )
38 )
53
39
54 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
55 # Classes
41 # Classes
56 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
57
43
58 class Namespace(dict):
44 class Namespace(dict):
59 """Subclass of dict for attribute access to keys."""
45 """Subclass of dict for attribute access to keys."""
60
46
61 def __getattr__(self, key):
47 def __getattr__(self, key):
62 """getattr aliased to getitem"""
48 """getattr aliased to getitem"""
63 if key in self:
49 if key in self:
64 return self[key]
50 return self[key]
65 else:
51 else:
66 raise NameError(key)
52 raise NameError(key)
67
53
68 def __setattr__(self, key, value):
54 def __setattr__(self, key, value):
69 """setattr aliased to setitem, with strict"""
55 """setattr aliased to setitem, with strict"""
70 if hasattr(dict, key):
56 if hasattr(dict, key):
71 raise KeyError("Cannot override dict keys %r"%key)
57 raise KeyError("Cannot override dict keys %r"%key)
72 self[key] = value
58 self[key] = value
73
59
74
60
75 class ReverseDict(dict):
61 class ReverseDict(dict):
76 """simple double-keyed subset of dict methods."""
62 """simple double-keyed subset of dict methods."""
77
63
78 def __init__(self, *args, **kwargs):
64 def __init__(self, *args, **kwargs):
79 dict.__init__(self, *args, **kwargs)
65 dict.__init__(self, *args, **kwargs)
80 self._reverse = dict()
66 self._reverse = dict()
81 for key, value in iteritems(self):
67 for key, value in iteritems(self):
82 self._reverse[value] = key
68 self._reverse[value] = key
83
69
84 def __getitem__(self, key):
70 def __getitem__(self, key):
85 try:
71 try:
86 return dict.__getitem__(self, key)
72 return dict.__getitem__(self, key)
87 except KeyError:
73 except KeyError:
88 return self._reverse[key]
74 return self._reverse[key]
89
75
90 def __setitem__(self, key, value):
76 def __setitem__(self, key, value):
91 if key in self._reverse:
77 if key in self._reverse:
92 raise KeyError("Can't have key %r on both sides!"%key)
78 raise KeyError("Can't have key %r on both sides!"%key)
93 dict.__setitem__(self, key, value)
79 dict.__setitem__(self, key, value)
94 self._reverse[value] = key
80 self._reverse[value] = key
95
81
96 def pop(self, key):
82 def pop(self, key):
97 value = dict.pop(self, key)
83 value = dict.pop(self, key)
98 self._reverse.pop(value)
84 self._reverse.pop(value)
99 return value
85 return value
100
86
101 def get(self, key, default=None):
87 def get(self, key, default=None):
102 try:
88 try:
103 return self[key]
89 return self[key]
104 except KeyError:
90 except KeyError:
105 return default
91 return default
106
92
107 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
108 # Functions
94 # Functions
109 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
110
96
111 @decorator
97 @decorator
112 def log_errors(f, self, *args, **kwargs):
98 def log_errors(f, self, *args, **kwargs):
113 """decorator to log unhandled exceptions raised in a method.
99 """decorator to log unhandled exceptions raised in a method.
114
100
115 For use wrapping on_recv callbacks, so that exceptions
101 For use wrapping on_recv callbacks, so that exceptions
116 do not cause the stream to be closed.
102 do not cause the stream to be closed.
117 """
103 """
118 try:
104 try:
119 return f(self, *args, **kwargs)
105 return f(self, *args, **kwargs)
120 except Exception:
106 except Exception:
121 self.log.error("Uncaught exception in %r" % f, exc_info=True)
107 self.log.error("Uncaught exception in %r" % f, exc_info=True)
122
108
123
109
124 def is_url(url):
110 def is_url(url):
125 """boolean check for whether a string is a zmq url"""
111 """boolean check for whether a string is a zmq url"""
126 if '://' not in url:
112 if '://' not in url:
127 return False
113 return False
128 proto, addr = url.split('://', 1)
114 proto, addr = url.split('://', 1)
129 if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']:
115 if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']:
130 return False
116 return False
131 return True
117 return True
132
118
133 def validate_url(url):
119 def validate_url(url):
134 """validate a url for zeromq"""
120 """validate a url for zeromq"""
135 if not isinstance(url, string_types):
121 if not isinstance(url, string_types):
136 raise TypeError("url must be a string, not %r"%type(url))
122 raise TypeError("url must be a string, not %r"%type(url))
137 url = url.lower()
123 url = url.lower()
138
124
139 proto_addr = url.split('://')
125 proto_addr = url.split('://')
140 assert len(proto_addr) == 2, 'Invalid url: %r'%url
126 assert len(proto_addr) == 2, 'Invalid url: %r'%url
141 proto, addr = proto_addr
127 proto, addr = proto_addr
142 assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto
128 assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto
143
129
144 # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391
130 # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391
145 # author: Remi Sabourin
131 # author: Remi Sabourin
146 pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$')
132 pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$')
147
133
148 if proto == 'tcp':
134 if proto == 'tcp':
149 lis = addr.split(':')
135 lis = addr.split(':')
150 assert len(lis) == 2, 'Invalid url: %r'%url
136 assert len(lis) == 2, 'Invalid url: %r'%url
151 addr,s_port = lis
137 addr,s_port = lis
152 try:
138 try:
153 port = int(s_port)
139 port = int(s_port)
154 except ValueError:
140 except ValueError:
155 raise AssertionError("Invalid port %r in url: %r"%(port, url))
141 raise AssertionError("Invalid port %r in url: %r"%(port, url))
156
142
157 assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url
143 assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url
158
144
159 else:
145 else:
160 # only validate tcp urls currently
146 # only validate tcp urls currently
161 pass
147 pass
162
148
163 return True
149 return True
164
150
165
151
166 def validate_url_container(container):
152 def validate_url_container(container):
167 """validate a potentially nested collection of urls."""
153 """validate a potentially nested collection of urls."""
168 if isinstance(container, string_types):
154 if isinstance(container, string_types):
169 url = container
155 url = container
170 return validate_url(url)
156 return validate_url(url)
171 elif isinstance(container, dict):
157 elif isinstance(container, dict):
172 container = itervalues(container)
158 container = itervalues(container)
173
159
174 for element in container:
160 for element in container:
175 validate_url_container(element)
161 validate_url_container(element)
176
162
177
163
178 def split_url(url):
164 def split_url(url):
179 """split a zmq url (tcp://ip:port) into ('tcp','ip','port')."""
165 """split a zmq url (tcp://ip:port) into ('tcp','ip','port')."""
180 proto_addr = url.split('://')
166 proto_addr = url.split('://')
181 assert len(proto_addr) == 2, 'Invalid url: %r'%url
167 assert len(proto_addr) == 2, 'Invalid url: %r'%url
182 proto, addr = proto_addr
168 proto, addr = proto_addr
183 lis = addr.split(':')
169 lis = addr.split(':')
184 assert len(lis) == 2, 'Invalid url: %r'%url
170 assert len(lis) == 2, 'Invalid url: %r'%url
185 addr,s_port = lis
171 addr,s_port = lis
186 return proto,addr,s_port
172 return proto,addr,s_port
187
173
174
188 def disambiguate_ip_address(ip, location=None):
175 def disambiguate_ip_address(ip, location=None):
189 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
176 """turn multi-ip interfaces '0.0.0.0' and '*' into a connectable address
190 ones, based on the location (default interpretation of location is localhost)."""
177
191 if ip in ('0.0.0.0', '*'):
178 Explicit IP addresses are returned unmodified.
192 if location is None or is_public_ip(location) or not public_ips():
179
193 # If location is unspecified or cannot be determined, assume local
180 Parameters
181 ----------
182
183 ip : IP address
184 An IP address, or the special values 0.0.0.0, or *
185 location: IP address, optional
186 A public IP of the target machine.
187 If location is an IP of the current machine,
188 localhost will be returned,
189 otherwise location will be returned.
190 """
191 if ip in {'0.0.0.0', '*'}:
192 if not location:
193 # unspecified location, localhost is the only choice
194 ip = localhost()
195 elif is_public_ip(location):
196 # location is a public IP on this machine, use localhost
194 ip = localhost()
197 ip = localhost()
195 elif location:
198 elif not public_ips():
196 return location
199 # this machine's public IPs cannot be determined,
200 # assume `location` is not this machine
201 warnings.warn("IPython could not determine public IPs", RuntimeWarning)
202 ip = location
203 else:
204 # location is not this machine, do not use loopback
205 ip = location
197 return ip
206 return ip
198
207
208
199 def disambiguate_url(url, location=None):
209 def disambiguate_url(url, location=None):
200 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
210 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
201 ones, based on the location (default interpretation is localhost).
211 ones, based on the location (default interpretation is localhost).
202
212
203 This is for zeromq urls, such as ``tcp://*:10101``.
213 This is for zeromq urls, such as ``tcp://*:10101``.
204 """
214 """
205 try:
215 try:
206 proto,ip,port = split_url(url)
216 proto,ip,port = split_url(url)
207 except AssertionError:
217 except AssertionError:
208 # probably not tcp url; could be ipc, etc.
218 # probably not tcp url; could be ipc, etc.
209 return url
219 return url
210
220
211 ip = disambiguate_ip_address(ip,location)
221 ip = disambiguate_ip_address(ip,location)
212
222
213 return "%s://%s:%s"%(proto,ip,port)
223 return "%s://%s:%s"%(proto,ip,port)
214
224
215
225
216 #--------------------------------------------------------------------------
226 #--------------------------------------------------------------------------
217 # helpers for implementing old MEC API via view.apply
227 # helpers for implementing old MEC API via view.apply
218 #--------------------------------------------------------------------------
228 #--------------------------------------------------------------------------
219
229
220 def interactive(f):
230 def interactive(f):
221 """decorator for making functions appear as interactively defined.
231 """decorator for making functions appear as interactively defined.
222 This results in the function being linked to the user_ns as globals()
232 This results in the function being linked to the user_ns as globals()
223 instead of the module globals().
233 instead of the module globals().
224 """
234 """
225
235
226 # build new FunctionType, so it can have the right globals
236 # build new FunctionType, so it can have the right globals
227 # interactive functions never have closures, that's kind of the point
237 # interactive functions never have closures, that's kind of the point
228 if isinstance(f, FunctionType):
238 if isinstance(f, FunctionType):
229 mainmod = __import__('__main__')
239 mainmod = __import__('__main__')
230 f = FunctionType(f.__code__, mainmod.__dict__,
240 f = FunctionType(f.__code__, mainmod.__dict__,
231 f.__name__, f.__defaults__,
241 f.__name__, f.__defaults__,
232 )
242 )
233 # associate with __main__ for uncanning
243 # associate with __main__ for uncanning
234 f.__module__ = '__main__'
244 f.__module__ = '__main__'
235 return f
245 return f
236
246
237 @interactive
247 @interactive
238 def _push(**ns):
248 def _push(**ns):
239 """helper method for implementing `client.push` via `client.apply`"""
249 """helper method for implementing `client.push` via `client.apply`"""
240 user_ns = globals()
250 user_ns = globals()
241 tmp = '_IP_PUSH_TMP_'
251 tmp = '_IP_PUSH_TMP_'
242 while tmp in user_ns:
252 while tmp in user_ns:
243 tmp = tmp + '_'
253 tmp = tmp + '_'
244 try:
254 try:
245 for name, value in ns.items():
255 for name, value in ns.items():
246 user_ns[tmp] = value
256 user_ns[tmp] = value
247 exec("%s = %s" % (name, tmp), user_ns)
257 exec("%s = %s" % (name, tmp), user_ns)
248 finally:
258 finally:
249 user_ns.pop(tmp, None)
259 user_ns.pop(tmp, None)
250
260
251 @interactive
261 @interactive
252 def _pull(keys):
262 def _pull(keys):
253 """helper method for implementing `client.pull` via `client.apply`"""
263 """helper method for implementing `client.pull` via `client.apply`"""
254 if isinstance(keys, (list,tuple, set)):
264 if isinstance(keys, (list,tuple, set)):
255 return [eval(key, globals()) for key in keys]
265 return [eval(key, globals()) for key in keys]
256 else:
266 else:
257 return eval(keys, globals())
267 return eval(keys, globals())
258
268
259 @interactive
269 @interactive
260 def _execute(code):
270 def _execute(code):
261 """helper method for implementing `client.execute` via `client.apply`"""
271 """helper method for implementing `client.execute` via `client.apply`"""
262 exec(code, globals())
272 exec(code, globals())
263
273
264 #--------------------------------------------------------------------------
274 #--------------------------------------------------------------------------
265 # extra process management utilities
275 # extra process management utilities
266 #--------------------------------------------------------------------------
276 #--------------------------------------------------------------------------
267
277
268 _random_ports = set()
278 _random_ports = set()
269
279
270 def select_random_ports(n):
280 def select_random_ports(n):
271 """Selects and return n random ports that are available."""
281 """Selects and return n random ports that are available."""
272 ports = []
282 ports = []
273 for i in range(n):
283 for i in range(n):
274 sock = socket.socket()
284 sock = socket.socket()
275 sock.bind(('', 0))
285 sock.bind(('', 0))
276 while sock.getsockname()[1] in _random_ports:
286 while sock.getsockname()[1] in _random_ports:
277 sock.close()
287 sock.close()
278 sock = socket.socket()
288 sock = socket.socket()
279 sock.bind(('', 0))
289 sock.bind(('', 0))
280 ports.append(sock)
290 ports.append(sock)
281 for i, sock in enumerate(ports):
291 for i, sock in enumerate(ports):
282 port = sock.getsockname()[1]
292 port = sock.getsockname()[1]
283 sock.close()
293 sock.close()
284 ports[i] = port
294 ports[i] = port
285 _random_ports.add(port)
295 _random_ports.add(port)
286 return ports
296 return ports
287
297
288 def signal_children(children):
298 def signal_children(children):
289 """Relay interupt/term signals to children, for more solid process cleanup."""
299 """Relay interupt/term signals to children, for more solid process cleanup."""
290 def terminate_children(sig, frame):
300 def terminate_children(sig, frame):
291 log = Application.instance().log
301 log = Application.instance().log
292 log.critical("Got signal %i, terminating children..."%sig)
302 log.critical("Got signal %i, terminating children..."%sig)
293 for child in children:
303 for child in children:
294 child.terminate()
304 child.terminate()
295
305
296 sys.exit(sig != SIGINT)
306 sys.exit(sig != SIGINT)
297 # sys.exit(sig)
307 # sys.exit(sig)
298 for sig in (SIGINT, SIGABRT, SIGTERM):
308 for sig in (SIGINT, SIGABRT, SIGTERM):
299 signal(sig, terminate_children)
309 signal(sig, terminate_children)
300
310
301 def generate_exec_key(keyfile):
311 def generate_exec_key(keyfile):
302 import uuid
312 import uuid
303 newkey = str(uuid.uuid4())
313 newkey = str(uuid.uuid4())
304 with open(keyfile, 'w') as f:
314 with open(keyfile, 'w') as f:
305 # f.write('ipython-key ')
315 # f.write('ipython-key ')
306 f.write(newkey+'\n')
316 f.write(newkey+'\n')
307 # set user-only RW permissions (0600)
317 # set user-only RW permissions (0600)
308 # this will have no effect on Windows
318 # this will have no effect on Windows
309 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
319 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
310
320
311
321
312 def integer_loglevel(loglevel):
322 def integer_loglevel(loglevel):
313 try:
323 try:
314 loglevel = int(loglevel)
324 loglevel = int(loglevel)
315 except ValueError:
325 except ValueError:
316 if isinstance(loglevel, str):
326 if isinstance(loglevel, str):
317 loglevel = getattr(logging, loglevel)
327 loglevel = getattr(logging, loglevel)
318 return loglevel
328 return loglevel
319
329
320 def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG):
330 def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG):
321 logger = logging.getLogger(logname)
331 logger = logging.getLogger(logname)
322 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
332 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
323 # don't add a second PUBHandler
333 # don't add a second PUBHandler
324 return
334 return
325 loglevel = integer_loglevel(loglevel)
335 loglevel = integer_loglevel(loglevel)
326 lsock = context.socket(zmq.PUB)
336 lsock = context.socket(zmq.PUB)
327 lsock.connect(iface)
337 lsock.connect(iface)
328 handler = handlers.PUBHandler(lsock)
338 handler = handlers.PUBHandler(lsock)
329 handler.setLevel(loglevel)
339 handler.setLevel(loglevel)
330 handler.root_topic = root
340 handler.root_topic = root
331 logger.addHandler(handler)
341 logger.addHandler(handler)
332 logger.setLevel(loglevel)
342 logger.setLevel(loglevel)
333
343
334 def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):
344 def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):
335 logger = logging.getLogger()
345 logger = logging.getLogger()
336 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
346 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
337 # don't add a second PUBHandler
347 # don't add a second PUBHandler
338 return
348 return
339 loglevel = integer_loglevel(loglevel)
349 loglevel = integer_loglevel(loglevel)
340 lsock = context.socket(zmq.PUB)
350 lsock = context.socket(zmq.PUB)
341 lsock.connect(iface)
351 lsock.connect(iface)
342 handler = EnginePUBHandler(engine, lsock)
352 handler = EnginePUBHandler(engine, lsock)
343 handler.setLevel(loglevel)
353 handler.setLevel(loglevel)
344 logger.addHandler(handler)
354 logger.addHandler(handler)
345 logger.setLevel(loglevel)
355 logger.setLevel(loglevel)
346 return logger
356 return logger
347
357
348 def local_logger(logname, loglevel=logging.DEBUG):
358 def local_logger(logname, loglevel=logging.DEBUG):
349 loglevel = integer_loglevel(loglevel)
359 loglevel = integer_loglevel(loglevel)
350 logger = logging.getLogger(logname)
360 logger = logging.getLogger(logname)
351 if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]):
361 if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]):
352 # don't add a second StreamHandler
362 # don't add a second StreamHandler
353 return
363 return
354 handler = logging.StreamHandler()
364 handler = logging.StreamHandler()
355 handler.setLevel(loglevel)
365 handler.setLevel(loglevel)
356 formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s",
366 formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s",
357 datefmt="%Y-%m-%d %H:%M:%S")
367 datefmt="%Y-%m-%d %H:%M:%S")
358 handler.setFormatter(formatter)
368 handler.setFormatter(formatter)
359
369
360 logger.addHandler(handler)
370 logger.addHandler(handler)
361 logger.setLevel(loglevel)
371 logger.setLevel(loglevel)
362 return logger
372 return logger
363
373
364 def set_hwm(sock, hwm=0):
374 def set_hwm(sock, hwm=0):
365 """set zmq High Water Mark on a socket
375 """set zmq High Water Mark on a socket
366
376
367 in a way that always works for various pyzmq / libzmq versions.
377 in a way that always works for various pyzmq / libzmq versions.
368 """
378 """
369 import zmq
379 import zmq
370
380
371 for key in ('HWM', 'SNDHWM', 'RCVHWM'):
381 for key in ('HWM', 'SNDHWM', 'RCVHWM'):
372 opt = getattr(zmq, key, None)
382 opt = getattr(zmq, key, None)
373 if opt is None:
383 if opt is None:
374 continue
384 continue
375 try:
385 try:
376 sock.setsockopt(opt, hwm)
386 sock.setsockopt(opt, hwm)
377 except zmq.ZMQError:
387 except zmq.ZMQError:
378 pass
388 pass
379
389
380 No newline at end of file
390
General Comments 0
You need to be logged in to leave comments. Login now