1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li# contextlib.nested is deprecated and removed in python3 7*9c5db199SXin Li# Since the apis are quite different, keep track of whether to use nested or not 8*9c5db199SXin Li# based on the availability of contextlib.nested and take different code paths. 9*9c5db199SXin Litry: 10*9c5db199SXin Li from contextlib import nested 11*9c5db199SXin Li use_nested = True 12*9c5db199SXin Liexcept ImportError: 13*9c5db199SXin Li import contextlib 14*9c5db199SXin Li use_nested = False 15*9c5db199SXin Li 16*9c5db199SXin Liimport dbus 17*9c5db199SXin Liimport errno 18*9c5db199SXin Liimport functools 19*9c5db199SXin Liimport logging 20*9c5db199SXin Liimport os 21*9c5db199SXin Liimport select 22*9c5db199SXin Liimport signal 23*9c5db199SXin Liimport six.moves.xmlrpc_server 24*9c5db199SXin Liimport threading 25*9c5db199SXin Li 26*9c5db199SXin Li 27*9c5db199SXin Lidef terminate_old(script_name, sigterm_timeout=5, sigkill_timeout=3): 28*9c5db199SXin Li """ 29*9c5db199SXin Li Avoid "address already in use" errors by killing any leftover RPC server 30*9c5db199SXin Li processes, possibly from previous runs. 31*9c5db199SXin Li 32*9c5db199SXin Li A process is a match if it's Python and has the given script in the command 33*9c5db199SXin Li line. This should avoid including processes such as editors and 'tail' of 34*9c5db199SXin Li logs, which might match a simple pkill. 35*9c5db199SXin Li 36*9c5db199SXin Li exe=/usr/local/bin/python3 37*9c5db199SXin Li cmdline=['/usr/bin/python3', '-u', '/usr/local/autotest/.../rpc_server.py'] 38*9c5db199SXin Li 39*9c5db199SXin Li @param script_name: The filename of the main script, used to match processes 40*9c5db199SXin Li @param sigterm_timeout: Wait N seconds after SIGTERM before trying SIGKILL. 41*9c5db199SXin Li @param sigkill_timeout: Wait N seconds after SIGKILL before complaining. 42*9c5db199SXin Li """ 43*9c5db199SXin Li # import late, to avoid affecting servers that don't call the method 44*9c5db199SXin Li import psutil 45*9c5db199SXin Li 46*9c5db199SXin Li script_name_abs = os.path.abspath(script_name) 47*9c5db199SXin Li script_name_base = os.path.basename(script_name) 48*9c5db199SXin Li me = psutil.Process() 49*9c5db199SXin Li 50*9c5db199SXin Li logging.debug('This process: %s: %s, %s', me, me.exe(), me.cmdline()) 51*9c5db199SXin Li logging.debug('Checking for leftover processes...') 52*9c5db199SXin Li 53*9c5db199SXin Li running = [] 54*9c5db199SXin Li for proc in psutil.process_iter(attrs=['name', 'exe', 'cmdline']): 55*9c5db199SXin Li if proc == me: 56*9c5db199SXin Li continue 57*9c5db199SXin Li try: 58*9c5db199SXin Li name = proc.name() 59*9c5db199SXin Li if not name or 'py' not in name: 60*9c5db199SXin Li continue 61*9c5db199SXin Li exe = proc.exe() 62*9c5db199SXin Li args = proc.cmdline() 63*9c5db199SXin Li # Note: If we ever need multiple instances on different ports, 64*9c5db199SXin Li # add a check for listener ports, likely via proc.connections() 65*9c5db199SXin Li if '/python' in exe and (script_name in args 66*9c5db199SXin Li or script_name_abs in args 67*9c5db199SXin Li or script_name_base in args): 68*9c5db199SXin Li logging.debug('Found process: %s: %s', proc, args) 69*9c5db199SXin Li running.append(proc) 70*9c5db199SXin Li except psutil.Error as e: 71*9c5db199SXin Li logging.debug('%s: %s', e, proc) 72*9c5db199SXin Li continue 73*9c5db199SXin Li 74*9c5db199SXin Li if not running: 75*9c5db199SXin Li return 76*9c5db199SXin Li 77*9c5db199SXin Li logging.info('Trying SIGTERM: pids=%s', [p.pid for p in running]) 78*9c5db199SXin Li for proc in running: 79*9c5db199SXin Li try: 80*9c5db199SXin Li proc.send_signal(0) 81*9c5db199SXin Li proc.terminate() 82*9c5db199SXin Li except psutil.NoSuchProcess as e: 83*9c5db199SXin Li logging.debug('%s: %s', e, proc) 84*9c5db199SXin Li except psutil.Error as e: 85*9c5db199SXin Li logging.warning('%s: %s', e, proc) 86*9c5db199SXin Li 87*9c5db199SXin Li (terminated, running) = psutil.wait_procs(running, sigterm_timeout) 88*9c5db199SXin Li if not running: 89*9c5db199SXin Li return 90*9c5db199SXin Li 91*9c5db199SXin Li running.sort(key=lambda p: p.pid) 92*9c5db199SXin Li logging.info('Trying SIGKILL: pids=%s', [p.pid for p in running]) 93*9c5db199SXin Li for proc in running: 94*9c5db199SXin Li try: 95*9c5db199SXin Li proc.kill() 96*9c5db199SXin Li except psutil.NoSuchProcess as e: 97*9c5db199SXin Li logging.debug('%s: %s', e, proc) 98*9c5db199SXin Li except psutil.Error as e: 99*9c5db199SXin Li logging.warning('%s: %s', e, proc) 100*9c5db199SXin Li 101*9c5db199SXin Li (sigkilled, running) = psutil.wait_procs(running, sigkill_timeout) 102*9c5db199SXin Li if running: 103*9c5db199SXin Li running.sort(key=lambda p: p.pid) 104*9c5db199SXin Li logging.warning('Found leftover processes %s; address may be in use!', 105*9c5db199SXin Li [p.pid for p in running]) 106*9c5db199SXin Li else: 107*9c5db199SXin Li logging.debug('Leftover processes have exited.') 108*9c5db199SXin Li 109*9c5db199SXin Li 110*9c5db199SXin Liclass XmlRpcServer(threading.Thread): 111*9c5db199SXin Li """Simple XMLRPC server implementation. 112*9c5db199SXin Li 113*9c5db199SXin Li In theory, Python should provide a valid XMLRPC server implementation as 114*9c5db199SXin Li part of its standard library. In practice the provided implementation 115*9c5db199SXin Li doesn't handle signals, not even EINTR. As a result, we have this class. 116*9c5db199SXin Li 117*9c5db199SXin Li Usage: 118*9c5db199SXin Li 119*9c5db199SXin Li server = XmlRpcServer(('localhost', 43212)) 120*9c5db199SXin Li server.register_delegate(my_delegate_instance) 121*9c5db199SXin Li server.run() 122*9c5db199SXin Li 123*9c5db199SXin Li """ 124*9c5db199SXin Li 125*9c5db199SXin Li def __init__(self, host, port): 126*9c5db199SXin Li """Construct an XmlRpcServer. 127*9c5db199SXin Li 128*9c5db199SXin Li @param host string hostname to bind to. 129*9c5db199SXin Li @param port int port number to bind to. 130*9c5db199SXin Li 131*9c5db199SXin Li """ 132*9c5db199SXin Li super(XmlRpcServer, self).__init__() 133*9c5db199SXin Li logging.info('Binding server to %s:%d', host, port) 134*9c5db199SXin Li self._server = six.moves.xmlrpc_server.SimpleXMLRPCServer( 135*9c5db199SXin Li (host, port), allow_none=True) 136*9c5db199SXin Li self._server.register_introspection_functions() 137*9c5db199SXin Li # After python 2.7.10, BaseServer.handle_request automatically retries 138*9c5db199SXin Li # on EINTR, so handle_request will be blocked at select.select forever 139*9c5db199SXin Li # if timeout is None. Set a timeout so server can be shut down 140*9c5db199SXin Li # gracefully. Check issue crbug.com/571737 and 141*9c5db199SXin Li # https://bugs.python.org/issue7978 for the explanation. 142*9c5db199SXin Li self._server.timeout = 0.5 143*9c5db199SXin Li self._keep_running = True 144*9c5db199SXin Li self._delegates = [] 145*9c5db199SXin Li # Gracefully shut down on signals. This is how we expect to be shut 146*9c5db199SXin Li # down by autotest. 147*9c5db199SXin Li signal.signal(signal.SIGTERM, self._handle_signal) 148*9c5db199SXin Li signal.signal(signal.SIGINT, self._handle_signal) 149*9c5db199SXin Li 150*9c5db199SXin Li 151*9c5db199SXin Li def register_delegate(self, delegate): 152*9c5db199SXin Li """Register delegate objects with the server. 153*9c5db199SXin Li 154*9c5db199SXin Li The server will automagically look up all methods not prefixed with an 155*9c5db199SXin Li underscore and treat them as potential RPC calls. These methods may 156*9c5db199SXin Li only take basic Python objects as parameters, as noted by the 157*9c5db199SXin Li SimpleXMLRPCServer documentation. The state of the delegate is 158*9c5db199SXin Li persisted across calls. 159*9c5db199SXin Li 160*9c5db199SXin Li @param delegate object Python object to be exposed via RPC. 161*9c5db199SXin Li 162*9c5db199SXin Li """ 163*9c5db199SXin Li self._server.register_instance(delegate) 164*9c5db199SXin Li self._delegates.append(delegate) 165*9c5db199SXin Li 166*9c5db199SXin Li def run(self): 167*9c5db199SXin Li """Block and handle many XmlRpc requests.""" 168*9c5db199SXin Li logging.info('XmlRpcServer starting...') 169*9c5db199SXin Li 170*9c5db199SXin Li def stack_inner(): 171*9c5db199SXin Li """Handle requests to server until asked to stop running.""" 172*9c5db199SXin Li while self._keep_running: 173*9c5db199SXin Li try: 174*9c5db199SXin Li self._server.handle_request() 175*9c5db199SXin Li except select.error as v: 176*9c5db199SXin Li # In a cruel twist of fate, the python library doesn't 177*9c5db199SXin Li # handle this kind of error. 178*9c5db199SXin Li if v[0] != errno.EINTR: 179*9c5db199SXin Li raise 180*9c5db199SXin Li 181*9c5db199SXin Li if use_nested: 182*9c5db199SXin Li with nested(*self._delegates): 183*9c5db199SXin Li stack_inner() 184*9c5db199SXin Li else: 185*9c5db199SXin Li with contextlib.ExitStack() as stack: 186*9c5db199SXin Li delegates = [stack.enter_context(d) for d in self._delegates] 187*9c5db199SXin Li stack_inner() 188*9c5db199SXin Li 189*9c5db199SXin Li for delegate in self._delegates: 190*9c5db199SXin Li if hasattr(delegate, 'cleanup'): 191*9c5db199SXin Li delegate.cleanup() 192*9c5db199SXin Li 193*9c5db199SXin Li logging.info('XmlRpcServer exited.') 194*9c5db199SXin Li 195*9c5db199SXin Li 196*9c5db199SXin Li def _handle_signal(self, _signum, _frame): 197*9c5db199SXin Li """Handle a process signal by gracefully quitting. 198*9c5db199SXin Li 199*9c5db199SXin Li SimpleXMLRPCServer helpfully exposes a method called shutdown() which 200*9c5db199SXin Li clears a flag similar to _keep_running, and then blocks until it sees 201*9c5db199SXin Li the server shut down. Unfortunately, if you call that function from 202*9c5db199SXin Li a signal handler, the server will just hang, since the process is 203*9c5db199SXin Li paused for the signal, causing a deadlock. Thus we are reinventing the 204*9c5db199SXin Li wheel with our own event loop. 205*9c5db199SXin Li 206*9c5db199SXin Li """ 207*9c5db199SXin Li self._keep_running = False 208*9c5db199SXin Li 209*9c5db199SXin Li 210*9c5db199SXin Lidef dbus_safe(default_return_value): 211*9c5db199SXin Li """Catch all DBus exceptions and return a default value instead. 212*9c5db199SXin Li 213*9c5db199SXin Li Wrap a function with a try block that catches DBus exceptions and 214*9c5db199SXin Li returns default values instead. This is convenient for simple error 215*9c5db199SXin Li handling since XMLRPC doesn't understand DBus exceptions. 216*9c5db199SXin Li 217*9c5db199SXin Li @param wrapped_function function to wrap. 218*9c5db199SXin Li @param default_return_value value to return on exception (usually False). 219*9c5db199SXin Li 220*9c5db199SXin Li """ 221*9c5db199SXin Li def decorator(wrapped_function): 222*9c5db199SXin Li """Call a function and catch DBus errors. 223*9c5db199SXin Li 224*9c5db199SXin Li @param wrapped_function function to call in dbus safe context. 225*9c5db199SXin Li @return function return value or default_return_value on failure. 226*9c5db199SXin Li 227*9c5db199SXin Li """ 228*9c5db199SXin Li @functools.wraps(wrapped_function) 229*9c5db199SXin Li def wrapper(*args, **kwargs): 230*9c5db199SXin Li """Pass args and kwargs to a dbus safe function. 231*9c5db199SXin Li 232*9c5db199SXin Li @param args formal python arguments. 233*9c5db199SXin Li @param kwargs keyword python arguments. 234*9c5db199SXin Li @return function return value or default_return_value on failure. 235*9c5db199SXin Li 236*9c5db199SXin Li """ 237*9c5db199SXin Li logging.debug('%s()', wrapped_function.__name__) 238*9c5db199SXin Li try: 239*9c5db199SXin Li return wrapped_function(*args, **kwargs) 240*9c5db199SXin Li 241*9c5db199SXin Li except dbus.exceptions.DBusException as e: 242*9c5db199SXin Li logging.error('Exception while performing operation %s: %s: %s', 243*9c5db199SXin Li wrapped_function.__name__, 244*9c5db199SXin Li e.get_dbus_name(), 245*9c5db199SXin Li e.get_dbus_message()) 246*9c5db199SXin Li return default_return_value 247*9c5db199SXin Li 248*9c5db199SXin Li return wrapper 249*9c5db199SXin Li 250*9c5db199SXin Li return decorator 251*9c5db199SXin Li 252*9c5db199SXin Li 253*9c5db199SXin Liclass XmlRpcDelegate(object): 254*9c5db199SXin Li """A super class for XmlRPC delegates used with XmlRpcServer. 255*9c5db199SXin Li 256*9c5db199SXin Li This doesn't add much helpful functionality except to implement the trivial 257*9c5db199SXin Li status check method expected by autotest's host.xmlrpc_connect() method. 258*9c5db199SXin Li Subclass this class to add more functionality. 259*9c5db199SXin Li 260*9c5db199SXin Li """ 261*9c5db199SXin Li 262*9c5db199SXin Li 263*9c5db199SXin Li def __enter__(self): 264*9c5db199SXin Li logging.debug('Bringing up XmlRpcDelegate: %r.', self) 265*9c5db199SXin Li pass 266*9c5db199SXin Li 267*9c5db199SXin Li 268*9c5db199SXin Li def __exit__(self, exception, value, traceback): 269*9c5db199SXin Li logging.debug('Tearing down XmlRpcDelegate: %r.', self) 270*9c5db199SXin Li pass 271*9c5db199SXin Li 272*9c5db199SXin Li 273*9c5db199SXin Li def ready(self): 274*9c5db199SXin Li """Confirm that the XMLRPC server is up and ready to serve. 275*9c5db199SXin Li 276*9c5db199SXin Li @return True (always). 277*9c5db199SXin Li 278*9c5db199SXin Li """ 279*9c5db199SXin Li logging.debug('ready()') 280*9c5db199SXin Li return True 281