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