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