Home | History | Annotate | Download | only in cros
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import contextlib
      6 import dbus
      7 import errno
      8 import functools
      9 import logging
     10 import select
     11 import signal
     12 import threading
     13 import SimpleXMLRPCServer
     14 
     15 
     16 class XmlRpcServer(threading.Thread):
     17     """Simple XMLRPC server implementation.
     18 
     19     In theory, Python should provide a sane XMLRPC server implementation as
     20     part of its standard library.  In practice the provided implementation
     21     doesn't handle signals, not even EINTR.  As a result, we have this class.
     22 
     23     Usage:
     24 
     25     server = XmlRpcServer(('localhost', 43212))
     26     server.register_delegate(my_delegate_instance)
     27     server.run()
     28 
     29     """
     30 
     31     def __init__(self, host, port):
     32         """Construct an XmlRpcServer.
     33 
     34         @param host string hostname to bind to.
     35         @param port int port number to bind to.
     36 
     37         """
     38         super(XmlRpcServer, self).__init__()
     39         logging.info('Binding server to %s:%d', host, port)
     40         self._server = SimpleXMLRPCServer.SimpleXMLRPCServer((host, port),
     41                                                              allow_none=True)
     42         self._server.register_introspection_functions()
     43         # After python 2.7.10, BaseServer.handle_request automatically retries
     44         # on EINTR, so handle_request will be blocked at select.select forever
     45         # if timeout is None. Set a timeout so server can be shut down
     46         # gracefully. Check issue crbug.com/571737 and
     47         # https://bugs.python.org/issue7978 for the explanation.
     48         self._server.timeout = 0.5
     49         self._keep_running = True
     50         self._delegates = []
     51         # Gracefully shut down on signals.  This is how we expect to be shut
     52         # down by autotest.
     53         signal.signal(signal.SIGTERM, self._handle_signal)
     54         signal.signal(signal.SIGINT, self._handle_signal)
     55 
     56 
     57     def register_delegate(self, delegate):
     58         """Register delegate objects with the server.
     59 
     60         The server will automagically look up all methods not prefixed with an
     61         underscore and treat them as potential RPC calls.  These methods may
     62         only take basic Python objects as parameters, as noted by the
     63         SimpleXMLRPCServer documentation.  The state of the delegate is
     64         persisted across calls.
     65 
     66         @param delegate object Python object to be exposed via RPC.
     67 
     68         """
     69         self._server.register_instance(delegate)
     70         self._delegates.append(delegate)
     71 
     72 
     73     def run(self):
     74         """Block and handle many XmlRpc requests."""
     75         logging.info('XmlRpcServer starting...')
     76         # TODO(wiley) nested is deprecated, but we can't use the replacement
     77         #       until we move to Python 3.0.
     78         with contextlib.nested(*self._delegates):
     79             while self._keep_running:
     80                 try:
     81                     self._server.handle_request()
     82                 except select.error as v:
     83                     # In a cruel twist of fate, the python library doesn't
     84                     # handle this kind of error.
     85                     if v[0] != errno.EINTR:
     86                         raise
     87         logging.info('XmlRpcServer exited.')
     88 
     89 
     90     def _handle_signal(self, _signum, _frame):
     91         """Handle a process signal by gracefully quitting.
     92 
     93         SimpleXMLRPCServer helpfully exposes a method called shutdown() which
     94         clears a flag similar to _keep_running, and then blocks until it sees
     95         the server shut down.  Unfortunately, if you call that function from
     96         a signal handler, the server will just hang, since the process is
     97         paused for the signal, causing a deadlock.  Thus we are reinventing the
     98         wheel with our own event loop.
     99 
    100         """
    101         self._keep_running = False
    102 
    103 
    104 def dbus_safe(default_return_value):
    105     """Catch all DBus exceptions and return a default value instead.
    106 
    107     Wrap a function with a try block that catches DBus exceptions and
    108     returns default values instead.  This is convenient for simple error
    109     handling since XMLRPC doesn't understand DBus exceptions.
    110 
    111     @param wrapped_function function to wrap.
    112     @param default_return_value value to return on exception (usually False).
    113 
    114     """
    115     def decorator(wrapped_function):
    116         """Call a function and catch DBus errors.
    117 
    118         @param wrapped_function function to call in dbus safe context.
    119         @return function return value or default_return_value on failure.
    120 
    121         """
    122         @functools.wraps(wrapped_function)
    123         def wrapper(*args, **kwargs):
    124             """Pass args and kwargs to a dbus safe function.
    125 
    126             @param args formal python arguments.
    127             @param kwargs keyword python arguments.
    128             @return function return value or default_return_value on failure.
    129 
    130             """
    131             logging.debug('%s()', wrapped_function.__name__)
    132             try:
    133                 return wrapped_function(*args, **kwargs)
    134 
    135             except dbus.exceptions.DBusException as e:
    136                 logging.error('Exception while performing operation %s: %s: %s',
    137                               wrapped_function.__name__,
    138                               e.get_dbus_name(),
    139                               e.get_dbus_message())
    140                 return default_return_value
    141 
    142         return wrapper
    143 
    144     return decorator
    145 
    146 
    147 class XmlRpcDelegate(object):
    148     """A super class for XmlRPC delegates used with XmlRpcServer.
    149 
    150     This doesn't add much helpful functionality except to implement the trivial
    151     status check method expected by autotest's host.xmlrpc_connect() method.
    152     Subclass this class to add more functionality.
    153 
    154     """
    155 
    156 
    157     def __enter__(self):
    158         logging.debug('Bringing up XmlRpcDelegate: %r.', self)
    159         pass
    160 
    161 
    162     def __exit__(self, exception, value, traceback):
    163         logging.debug('Tearing down XmlRpcDelegate: %r.', self)
    164         pass
    165 
    166 
    167     def ready(self):
    168         """Confirm that the XMLRPC server is up and ready to serve.
    169 
    170         @return True (always).
    171 
    172         """
    173         logging.debug('ready()')
    174         return True
    175