fcutil.py
296 lines
| 10.1 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2287 | #!/usr/bin/env python | ||
Brian E Granger
|
r1234 | # encoding: utf-8 | ||
Brian Granger
|
r2287 | """ | ||
Foolscap related utilities. | ||||
""" | ||||
Brian E Granger
|
r1234 | |||
Brian Granger
|
r2287 | #----------------------------------------------------------------------------- | ||
# Copyright (C) 2008-2009 The IPython Development Team | ||||
Brian E Granger
|
r1234 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
Brian Granger
|
r2287 | #----------------------------------------------------------------------------- | ||
Brian E Granger
|
r1234 | |||
Brian Granger
|
r2287 | #----------------------------------------------------------------------------- | ||
Brian E Granger
|
r1234 | # Imports | ||
Brian Granger
|
r2287 | #----------------------------------------------------------------------------- | ||
Brian E Granger
|
r1234 | |||
Brian Granger
|
r2302 | from __future__ import with_statement | ||
Brian E Granger
|
r1234 | import os | ||
Brian Granger
|
r2287 | import tempfile | ||
from twisted.internet import reactor, defer | ||||
from twisted.python import log | ||||
Brian E Granger
|
r1234 | |||
Brian Granger
|
r2517 | import foolscap | ||
Brian Granger
|
r2526 | try: | ||
from foolscap.api import Tub, UnauthenticatedTub | ||||
except ImportError: | ||||
from foolscap import Tub, UnauthenticatedTub | ||||
Brian E Granger
|
r1234 | |||
Brian Granger
|
r2287 | from IPython.config.loader import Config | ||
from IPython.kernel.configobjfactory import AdaptedConfiguredObjectFactory | ||||
from IPython.kernel.error import SecurityError | ||||
from IPython.utils.importstring import import_item | ||||
Brian Granger
|
r2517 | from IPython.utils.path import expand_path | ||
from IPython.utils.traitlets import Int, Str, Bool, Instance | ||||
Brian Granger
|
r2287 | |||
#----------------------------------------------------------------------------- | ||||
# Code | ||||
#----------------------------------------------------------------------------- | ||||
# We do this so if a user doesn't have OpenSSL installed, it will try to use | ||||
# an UnauthenticatedTub. But, they will still run into problems if they | ||||
# try to use encrypted furls. | ||||
try: | ||||
import OpenSSL | ||||
except: | ||||
Tub = UnauthenticatedTub | ||||
have_crypto = False | ||||
else: | ||||
have_crypto = True | ||||
Brian Granger
|
r2309 | class FURLError(Exception): | ||
pass | ||||
Brian E Granger
|
r1234 | def check_furl_file_security(furl_file, secure): | ||
"""Remove the old furl_file if changing security modes.""" | ||||
Brian Granger
|
r2517 | furl_file = expand_path(furl_file) | ||
Brian E Granger
|
r1234 | if os.path.isfile(furl_file): | ||
Brian Granger
|
r2517 | with open(furl_file, 'r') as f: | ||
oldfurl = f.read().strip() | ||||
Brian E Granger
|
r1234 | if (oldfurl.startswith('pb://') and not secure) or (oldfurl.startswith('pbu://') and secure): | ||
os.remove(furl_file) | ||||
Brian Granger
|
r2287 | |||
Brian E Granger
|
r1234 | def is_secure(furl): | ||
Brian Granger
|
r2287 | """Is the given FURL secure or not.""" | ||
Brian Granger
|
r2517 | if is_valid_furl(furl): | ||
Brian E Granger
|
r1234 | if furl.startswith("pb://"): | ||
return True | ||||
elif furl.startswith("pbu://"): | ||||
return False | ||||
else: | ||||
Brian Granger
|
r2309 | raise FURLError("invalid FURL: %s" % furl) | ||
Brian E Granger
|
r1234 | |||
Brian Granger
|
r2287 | |||
Brian Granger
|
r2517 | def is_valid_furl(furl): | ||
Brian Granger
|
r2297 | """Is the str a valid FURL or not.""" | ||
Brian E Granger
|
r1234 | if isinstance(furl, str): | ||
if furl.startswith("pb://") or furl.startswith("pbu://"): | ||||
return True | ||||
Brian Granger
|
r2517 | else: | ||
return False | ||||
Brian E Granger
|
r1234 | else: | ||
return False | ||||
Brian Granger
|
r2287 | |||
Brian Granger
|
r2517 | def is_valid_furl_file(furl_or_file): | ||
"""See if furl_or_file exists and contains a valid FURL. | ||||
This doesn't try to read the contents because often we have to validate | ||||
FURL files that are created, but don't yet have a FURL written to them. | ||||
""" | ||||
if isinstance(furl_or_file, (str, unicode)): | ||||
path, furl_filename = os.path.split(furl_or_file) | ||||
if os.path.isdir(path) and furl_filename.endswith('.furl'): | ||||
return True | ||||
return False | ||||
Brian E Granger
|
r1234 | def find_furl(furl_or_file): | ||
Brian Granger
|
r2517 | """Find, validate and return a FURL in a string or file. | ||
This calls :func:`IPython.utils.path.expand_path` on the argument to | ||||
properly handle ``~`` and ``$`` variables in the path. | ||||
""" | ||||
if is_valid_furl(furl_or_file): | ||||
return furl_or_file | ||||
furl_or_file = expand_path(furl_or_file) | ||||
if is_valid_furl_file(furl_or_file): | ||||
Brian Granger
|
r2302 | with open(furl_or_file, 'r') as f: | ||
furl = f.read().strip() | ||||
Brian Granger
|
r2517 | if is_valid_furl(furl): | ||
Brian E Granger
|
r1234 | return furl | ||
Brian Granger
|
r2517 | raise FURLError("Not a valid FURL or FURL file: %r" % furl_or_file) | ||
Brian Granger
|
r2309 | |||
def is_valid_furl_or_file(furl_or_file): | ||||
"""Validate a FURL or a FURL file. | ||||
If ``furl_or_file`` looks like a file, we simply make sure its directory | ||||
exists and that it has a ``.furl`` file extension. We don't try to see | ||||
if the FURL file exists or to read its contents. This is useful for | ||||
cases where auto re-connection is being used. | ||||
""" | ||||
Brian Granger
|
r2517 | if is_valid_furl(furl_or_file) or is_valid_furl_file(furl_or_file): | ||
return True | ||||
else: | ||||
return False | ||||
Brian Granger
|
r2309 | |||
def validate_furl_or_file(furl_or_file): | ||||
Brian Granger
|
r2517 | """Like :func:`is_valid_furl_or_file`, but raises an error.""" | ||
Brian Granger
|
r2309 | if not is_valid_furl_or_file(furl_or_file): | ||
raise FURLError('Not a valid FURL or FURL file: %r' % furl_or_file) | ||||
Brian E Granger
|
r1234 | |||
Brian Granger
|
r2287 | def get_temp_furlfile(filename): | ||
Brian Granger
|
r2297 | """Return a temporary FURL file.""" | ||
Brian Granger
|
r2287 | return tempfile.mktemp(dir=os.path.dirname(filename), | ||
prefix=os.path.basename(filename)) | ||||
def make_tub(ip, port, secure, cert_file): | ||||
"""Create a listening tub given an ip, port, and cert_file location. | ||||
Parameters | ||||
---------- | ||||
ip : str | ||||
The ip address or hostname that the tub should listen on. | ||||
Empty means all interfaces. | ||||
port : int | ||||
The port that the tub should listen on. A value of 0 means | ||||
pick a random port | ||||
secure: bool | ||||
Will the connection be secure (in the Foolscap sense). | ||||
cert_file: str | ||||
A filename of a file to be used for theSSL certificate. | ||||
Returns | ||||
------- | ||||
A tub, listener tuple. | ||||
""" | ||||
if secure: | ||||
if have_crypto: | ||||
tub = Tub(certFile=cert_file) | ||||
else: | ||||
raise SecurityError("OpenSSL/pyOpenSSL is not available, so we " | ||||
"can't run in secure mode. Try running without " | ||||
"security using 'ipcontroller -xy'.") | ||||
else: | ||||
tub = UnauthenticatedTub() | ||||
# Set the strport based on the ip and port and start listening | ||||
if ip == '': | ||||
strport = "tcp:%i" % port | ||||
else: | ||||
strport = "tcp:%i:interface=%s" % (port, ip) | ||||
Brian Granger
|
r2297 | log.msg("Starting listener with [secure=%r] on: %s" % (secure, strport)) | ||
Brian Granger
|
r2287 | listener = tub.listenOn(strport) | ||
return tub, listener | ||||
class FCServiceFactory(AdaptedConfiguredObjectFactory): | ||||
"""This class creates a tub with various services running in it. | ||||
The basic idea is that :meth:`create` returns a running :class:`Tub` | ||||
Brian Granger
|
r2731 | instance that has a number of Foolscap references registered in it. This | ||
class is a subclass of :class:`IPython.config.configurable.Configurable` | ||||
so the IPython configuration system is used. | ||||
Brian Granger
|
r2287 | |||
Attributes | ||||
---------- | ||||
Brian Granger
|
r2297 | interfaces : Config | ||
Brian Granger
|
r2287 | A Config instance whose values are sub-Config objects having two | ||
keys: furl_file and interface_chain. | ||||
The other attributes are the standard ones for Foolscap. | ||||
Brian Granger
|
r2294 | """ | ||
Brian Granger
|
r2287 | |||
ip = Str('', config=True) | ||||
port = Int(0, config=True) | ||||
secure = Bool(True, config=True) | ||||
cert_file = Str('', config=True) | ||||
location = Str('', config=True) | ||||
Brian Granger
|
r2294 | reuse_furls = Bool(False, config=True) | ||
Brian Granger
|
r2297 | interfaces = Instance(klass=Config, kw={}, allow_none=False, config=True) | ||
Brian Granger
|
r2287 | |||
Brian Granger
|
r2768 | def __init__(self, config=None, adaptee=None): | ||
super(FCServiceFactory, self).__init__(config=config, adaptee=adaptee) | ||||
Brian Granger
|
r2294 | self._check_reuse_furls() | ||
Brian Granger
|
r2287 | def _ip_changed(self, name, old, new): | ||
if new == 'localhost' or new == '127.0.0.1': | ||||
self.location = '127.0.0.1' | ||||
Brian Granger
|
r2294 | def _check_reuse_furls(self): | ||
Brian Granger
|
r2297 | furl_files = [i.furl_file for i in self.interfaces.values()] | ||
for ff in furl_files: | ||||
fullfile = self._get_security_file(ff) | ||||
if self.reuse_furls: | ||||
Brian Granger
|
r2323 | if self.port==0: | ||
raise FURLError("You are trying to reuse the FURL file " | ||||
"for this connection, but the port for this connection " | ||||
"is set to 0 (autoselect). To reuse the FURL file " | ||||
"you need to specify specific port to listen on." | ||||
) | ||||
else: | ||||
log.msg("Reusing FURL file: %s" % fullfile) | ||||
Brian Granger
|
r2297 | else: | ||
Brian Granger
|
r2294 | if os.path.isfile(fullfile): | ||
Brian Granger
|
r2297 | log.msg("Removing old FURL file: %s" % fullfile) | ||
Brian Granger
|
r2294 | os.remove(fullfile) | ||
def _get_security_file(self, filename): | ||||
return os.path.join(self.config.Global.security_dir, filename) | ||||
Brian Granger
|
r2287 | def create(self): | ||
"""Create and return the Foolscap tub with everything running.""" | ||||
self.tub, self.listener = make_tub( | ||||
Brian Granger
|
r2297 | self.ip, self.port, self.secure, | ||
self._get_security_file(self.cert_file) | ||||
) | ||||
# log.msg("Interfaces to register [%r]: %r" % \ | ||||
# (self.__class__, self.interfaces)) | ||||
Brian Granger
|
r2287 | if not self.secure: | ||
Brian Granger
|
r2297 | log.msg("WARNING: running with no security: %s" % \ | ||
self.__class__.__name__) | ||||
Brian Granger
|
r2287 | reactor.callWhenRunning(self.set_location_and_register) | ||
return self.tub | ||||
def set_location_and_register(self): | ||||
"""Set the location for the tub and return a deferred.""" | ||||
if self.location == '': | ||||
d = self.tub.setLocationAutomatically() | ||||
else: | ||||
d = defer.maybeDeferred(self.tub.setLocation, | ||||
"%s:%i" % (self.location, self.listener.getPortnum())) | ||||
self.adapt_to_interfaces(d) | ||||
def adapt_to_interfaces(self, d): | ||||
"""Run through the interfaces, adapt and register.""" | ||||
Brian Granger
|
r2297 | for ifname, ifconfig in self.interfaces.iteritems(): | ||
Brian Granger
|
r2294 | ff = self._get_security_file(ifconfig.furl_file) | ||
Brian Granger
|
r2297 | log.msg("Adapting [%s] to interface: %s" % \ | ||
(self.adaptee.__class__.__name__, ifname)) | ||||
log.msg("Saving FURL for interface [%s] to file: %s" % (ifname, ff)) | ||||
Brian Granger
|
r2294 | check_furl_file_security(ff, self.secure) | ||
Brian Granger
|
r2287 | adaptee = self.adaptee | ||
for i in ifconfig.interface_chain: | ||||
adaptee = import_item(i)(adaptee) | ||||
Brian Granger
|
r2294 | d.addCallback(self.register, adaptee, furl_file=ff) | ||
Brian Granger
|
r2287 | |||
def register(self, empty, ref, furl_file): | ||||
"""Register the reference with the FURL file. | ||||
The FURL file is created and then moved to make sure that when the | ||||
Brian Granger
|
r2511 | file appears, the buffer has been flushed and the file closed. This | ||
is not done if we are re-using FURLS however. | ||||
Brian Granger
|
r2287 | """ | ||
Brian Granger
|
r2511 | if self.reuse_furls: | ||
self.tub.registerReference(ref, furlFile=furl_file) | ||||
else: | ||||
temp_furl_file = get_temp_furlfile(furl_file) | ||||
self.tub.registerReference(ref, furlFile=temp_furl_file) | ||||
os.rename(temp_furl_file, furl_file) | ||||
Brian E Granger
|
r1234 | |||