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