remote_facade_factory.py revision e0b08e6170b57f90262726eb7f04e059cb47419c
1# Copyright 2014 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 5import httplib 6import logging 7import socket 8import xmlrpclib 9import pprint 10import sys 11 12from autotest_lib.client.common_lib.cros import retry 13from autotest_lib.client.cros import constants 14from autotest_lib.server import autotest 15from autotest_lib.server.cros.multimedia import audio_facade_adapter 16from autotest_lib.server.cros.multimedia import display_facade_adapter 17 18 19class _Method: 20 """Class to save the name of the RPC method instead of the real object. 21 22 It keeps the name of the RPC method locally first such that the RPC method 23 can be evalulated to a real object while it is called. Its purpose is to 24 refer to the latest RPC proxy as the original previous-saved RPC proxy may 25 be lost due to reboot. 26 27 The call_method is the method which does refer to the latest RPC proxy. 28 """ 29 def __init__(self, call_method, name): 30 self.__call_method = call_method 31 self.__name = name 32 33 def __getattr__(self, name): 34 # Support a nested method. 35 return _Method(self.__call_method, "%s.%s" % (self.__name, name)) 36 37 def __call__(self, *args, **dargs): 38 return self.__call_method(self.__name, *args, **dargs) 39 40 41class RemoteFacadeProxy(object): 42 """An abstraction of XML RPC proxy to the DUT multimedia server. 43 44 The traditional XML RPC server proxy is static. It is lost when DUT 45 reboots. This class reconnects the server again when it finds the 46 connection is lost. 47 48 """ 49 50 XMLRPC_CONNECT_TIMEOUT = 60 51 XMLRPC_RETRY_TIMEOUT = 180 52 XMLRPC_RETRY_DELAY = 10 53 54 def __init__(self, host): 55 """Construct a RemoteFacadeProxy. 56 57 @param host: Host object representing a remote host. 58 """ 59 self._client = host 60 self._xmlrpc_proxy = None 61 self.connect(reconnect=False) 62 63 64 def __getattr__(self, name): 65 """Return a _Method object only, not its real object.""" 66 return _Method(self.__call_proxy, name) 67 68 69 def __call_proxy(self, name, *args, **dargs): 70 """Make the call on the latest RPC proxy object. 71 72 This method gets the internal method of the RPC proxy and calls it. 73 74 @param name: Name of the RPC method, a nested method supported. 75 @param args: The rest of arguments. 76 @param dargs: The rest of dict-type arguments. 77 @return: The return value of the RPC method. 78 """ 79 try: 80 # TODO(ihf): This logs all traffic from server to client. Make the spew optional. 81 rpc = ( 82 '%s(%s, %s)' % 83 (pprint.pformat(name), pprint.pformat(args), 84 pprint.pformat(dargs))) 85 try: 86 value = getattr(self._xmlrpc_proxy, name)(*args, **dargs) 87 if type(value) is str and value.startswith('Traceback'): 88 raise Exception('RPC error: %s\n%s' % (name, value)) 89 logging.info('RPC %s returns %s.', rpc, pprint.pformat(value)) 90 return value 91 except (socket.error, 92 xmlrpclib.ProtocolError, 93 httplib.BadStatusLine): 94 # Reconnect the RPC server in case connection lost, e.g. reboot. 95 self.connect(reconnect=True) 96 # Try again. 97 logging.warning('Retrying RPC %s.', rpc) 98 value = getattr(self._xmlrpc_proxy, name)(*args, **dargs) 99 if type(value) is str and value.startswith('Traceback'): 100 raise Exception('RPC error: %s\n%s' % (name, value)) 101 logging.info('RPC %s returns %s.', rpc, pprint.pformat(value)) 102 return value 103 except: 104 logging.error( 105 'Failed RPC %s with status [%s].', rpc, sys.exc_info()[0]) 106 raise 107 108 109 def connect(self, reconnect): 110 """Connects the XML-RPC proxy on the client. 111 112 @param reconnect: True for reconnection, False for the first-time. 113 """ 114 @retry.retry((socket.error, 115 xmlrpclib.ProtocolError, 116 httplib.BadStatusLine), 117 timeout_min=self.XMLRPC_RETRY_TIMEOUT / 60.0, 118 delay_sec=self.XMLRPC_RETRY_DELAY) 119 def connect_with_retries(reconnect): 120 """Connects the XML-RPC proxy with retries. 121 122 @param reconnect: True for reconnection, False for the first-time. 123 """ 124 if reconnect: 125 command = constants.MULTIMEDIA_XMLRPC_SERVER_RESTART_COMMAND 126 else: 127 command = constants.MULTIMEDIA_XMLRPC_SERVER_COMMAND 128 129 self._xmlrpc_proxy = self._client.xmlrpc_connect( 130 command, 131 constants.MULTIMEDIA_XMLRPC_SERVER_PORT, 132 command_name=( 133 constants.MULTIMEDIA_XMLRPC_SERVER_CLEANUP_PATTERN 134 ), 135 ready_test_name=( 136 constants.MULTIMEDIA_XMLRPC_SERVER_READY_METHOD), 137 timeout_seconds=self.XMLRPC_CONNECT_TIMEOUT) 138 139 logging.info('Setup the connection to RPC server, with retries...') 140 connect_with_retries(reconnect) 141 142 143 def __del__(self): 144 """Destructor of RemoteFacadeFactory.""" 145 self._client.rpc_disconnect( 146 constants.MULTIMEDIA_XMLRPC_SERVER_PORT) 147 148 149class RemoteFacadeFactory(object): 150 """A factory to generate remote multimedia facades. 151 152 The facade objects are remote-wrappers to access the DUT multimedia 153 functionality, like display, video, and audio. 154 155 """ 156 157 def __init__(self, host): 158 """Construct a RemoteFacadeFactory. 159 160 @param host: Host object representing a remote host. 161 """ 162 self._client = host 163 # Make sure the client library is on the device so that the proxy code 164 # is there when we try to call it. 165 client_at = autotest.Autotest(self._client) 166 client_at.install() 167 self._proxy = RemoteFacadeProxy(self._client) 168 169 170 def ready(self): 171 """Returns the proxy ready status""" 172 return self._proxy.ready() 173 174 175 def create_audio_facade(self): 176 """Creates an audio facade object.""" 177 return audio_facade_adapter.AudioFacadeRemoteAdapter( 178 self._client, self._proxy) 179 180 181 def create_display_facade(self): 182 """Creates a display facade object.""" 183 return display_facade_adapter.DisplayFacadeRemoteAdapter( 184 self._client, self._proxy) 185