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