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 os 8import socket 9import xmlrpclib 10import pprint 11import sys 12 13from autotest_lib.client.bin import utils 14from autotest_lib.client.common_lib import error 15from autotest_lib.client.common_lib.cros import retry 16from autotest_lib.client.cros import constants 17from autotest_lib.server import autotest 18from autotest_lib.server.cros.multimedia import audio_facade_adapter 19from autotest_lib.server.cros.multimedia import bluetooth_hid_facade_adapter 20from autotest_lib.server.cros.multimedia import browser_facade_adapter 21from autotest_lib.server.cros.multimedia import cfm_facade_adapter 22from autotest_lib.server.cros.multimedia import display_facade_adapter 23from autotest_lib.server.cros.multimedia import input_facade_adapter 24from autotest_lib.server.cros.multimedia import kiosk_facade_adapter 25from autotest_lib.server.cros.multimedia import system_facade_adapter 26from autotest_lib.server.cros.multimedia import usb_facade_adapter 27from autotest_lib.server.cros.multimedia import video_facade_adapter 28 29 30class _Method: 31 """Class to save the name of the RPC method instead of the real object. 32 33 It keeps the name of the RPC method locally first such that the RPC method 34 can be evalulated to a real object while it is called. Its purpose is to 35 refer to the latest RPC proxy as the original previous-saved RPC proxy may 36 be lost due to reboot. 37 38 The call_method is the method which does refer to the latest RPC proxy. 39 """ 40 41 def __init__(self, call_method, name): 42 self.__call_method = call_method 43 self.__name = name 44 45 46 def __getattr__(self, name): 47 # Support a nested method. 48 return _Method(self.__call_method, "%s.%s" % (self.__name, name)) 49 50 51 def __call__(self, *args, **dargs): 52 return self.__call_method(self.__name, *args, **dargs) 53 54 55class RemoteFacadeProxy(object): 56 """An abstraction of XML RPC proxy to the DUT multimedia server. 57 58 The traditional XML RPC server proxy is static. It is lost when DUT 59 reboots. This class reconnects the server again when it finds the 60 connection is lost. 61 62 """ 63 64 XMLRPC_CONNECT_TIMEOUT = 90 65 XMLRPC_RETRY_TIMEOUT = 180 66 XMLRPC_RETRY_DELAY = 10 67 REBOOT_TIMEOUT = 60 68 69 def __init__(self, host, no_chrome, extra_browser_args=None): 70 """Construct a RemoteFacadeProxy. 71 72 @param host: Host object representing a remote host. 73 @param no_chrome: Don't start Chrome by default. 74 @param extra_browser_args: A list containing extra browser args passed 75 to Chrome in addition to default ones. 76 77 """ 78 self._client = host 79 self._xmlrpc_proxy = None 80 self._no_chrome = no_chrome 81 self._extra_browser_args = extra_browser_args 82 self.connect() 83 if not no_chrome: 84 self._start_chrome(reconnect=False, retry=True, 85 extra_browser_args=self._extra_browser_args) 86 87 88 def __getattr__(self, name): 89 """Return a _Method object only, not its real object.""" 90 return _Method(self.__call_proxy, name) 91 92 93 def __call_proxy(self, name, *args, **dargs): 94 """Make the call on the latest RPC proxy object. 95 96 This method gets the internal method of the RPC proxy and calls it. 97 98 @param name: Name of the RPC method, a nested method supported. 99 @param args: The rest of arguments. 100 @param dargs: The rest of dict-type arguments. 101 @return: The return value of the RPC method. 102 """ 103 try: 104 # TODO(ihf): This logs all traffic from server to client. Make 105 # the spew optional. 106 rpc = ( 107 '%s(%s, %s)' % 108 (pprint.pformat(name), pprint.pformat(args), 109 pprint.pformat(dargs))) 110 try: 111 value = getattr(self._xmlrpc_proxy, name)(*args, **dargs) 112 if type(value) is str and value.startswith('Traceback'): 113 raise Exception('RPC error: %s\n%s' % (name, value)) 114 logging.debug('RPC %s returns %s.', rpc, pprint.pformat(value)) 115 return value 116 except (socket.error, 117 xmlrpclib.ProtocolError, 118 httplib.BadStatusLine): 119 # Reconnect the RPC server in case connection lost, e.g. reboot. 120 self.connect() 121 if not self._no_chrome: 122 self._start_chrome( 123 reconnect=True, retry=False, 124 extra_browser_args=self._extra_browser_args) 125 # Try again. 126 logging.warning('Retrying RPC %s.', rpc) 127 value = getattr(self._xmlrpc_proxy, name)(*args, **dargs) 128 if type(value) is str and value.startswith('Traceback'): 129 raise Exception('RPC error: %s\n%s' % (name, value)) 130 logging.debug('RPC %s returns %s.', rpc, pprint.pformat(value)) 131 return value 132 except: 133 logging.error( 134 'Failed RPC %s with status [%s].', rpc, sys.exc_info()[0]) 135 raise 136 137 138 def connect(self): 139 """Connects the XML-RPC proxy on the client. 140 141 @return: True on success. Note that if autotest server fails to 142 connect to XMLRPC server on Cros host after timeout, 143 error.TimeoutException will be raised by retry.retry 144 decorator. 145 146 """ 147 @retry.retry((socket.error, 148 xmlrpclib.ProtocolError, 149 httplib.BadStatusLine), 150 timeout_min=self.XMLRPC_RETRY_TIMEOUT / 60.0, 151 delay_sec=self.XMLRPC_RETRY_DELAY) 152 def connect_with_retries(): 153 """Connects the XML-RPC proxy with retries.""" 154 self._xmlrpc_proxy = self._client.rpc_server_tracker.xmlrpc_connect( 155 constants.MULTIMEDIA_XMLRPC_SERVER_COMMAND, 156 constants.MULTIMEDIA_XMLRPC_SERVER_PORT, 157 command_name=( 158 constants.MULTIMEDIA_XMLRPC_SERVER_CLEANUP_PATTERN 159 ), 160 ready_test_name=( 161 constants.MULTIMEDIA_XMLRPC_SERVER_READY_METHOD), 162 timeout_seconds=self.XMLRPC_CONNECT_TIMEOUT, 163 logfile=constants.MULTIMEDIA_XMLRPC_SERVER_LOG_FILE, 164 request_timeout_seconds= 165 constants.MULTIMEDIA_XMLRPC_SERVER_REQUEST_TIMEOUT) 166 167 logging.info('Setup the connection to RPC server, with retries...') 168 connect_with_retries() 169 return True 170 171 172 def _start_chrome(self, reconnect, retry=False, extra_browser_args=None): 173 """Starts Chrome using browser facade on Cros host. 174 175 @param reconnect: True for reconnection, False for the first-time. 176 @param retry: True to retry using a reboot on host. 177 @param extra_browser_args: A list containing extra browser args passed 178 to Chrome in addition to default ones. 179 180 @raise: error.TestError: if fail to start Chrome after retry. 181 182 """ 183 logging.info( 184 'Start Chrome with default arguments and extra browser args %s...', 185 extra_browser_args) 186 success = self._xmlrpc_proxy.browser.start_default_chrome( 187 reconnect, extra_browser_args) 188 if not success and retry: 189 logging.warning('Can not start Chrome. Reboot host and try again') 190 # Reboot host and try again. 191 self._client.reboot() 192 # Wait until XMLRPC server can be reconnected. 193 utils.poll_for_condition(condition=self.connect, 194 timeout=self.REBOOT_TIMEOUT) 195 logging.info( 196 'Retry starting Chrome with default arguments and ' 197 'extra browser args %s...', extra_browser_args) 198 success = self._xmlrpc_proxy.browser.start_default_chrome( 199 reconnect, extra_browser_args) 200 201 if not success: 202 raise error.TestError( 203 'Failed to start Chrome on DUT. ' 204 'Check multimedia_xmlrpc_server.log in result folder.') 205 206 207 def __del__(self): 208 """Destructor of RemoteFacadeFactory.""" 209 self._client.rpc_server_tracker.disconnect( 210 constants.MULTIMEDIA_XMLRPC_SERVER_PORT) 211 212 213class RemoteFacadeFactory(object): 214 """A factory to generate remote multimedia facades. 215 216 The facade objects are remote-wrappers to access the DUT multimedia 217 functionality, like display, video, and audio. 218 219 """ 220 221 def __init__(self, host, no_chrome=False, install_autotest=True, 222 results_dir=None, extra_browser_args=None): 223 """Construct a RemoteFacadeFactory. 224 225 @param host: Host object representing a remote host. 226 @param no_chrome: Don't start Chrome by default. 227 @param install_autotest: Install autotest on host. 228 @param results_dir: A directory to store multimedia server init log. 229 @param extra_browser_args: A list containing extra browser args passed 230 to Chrome in addition to default ones. 231 If it is not None, we will get multimedia init log to the results_dir. 232 233 """ 234 self._client = host 235 if install_autotest: 236 # Make sure the client library is on the device so that 237 # the proxy code is there when we try to call it. 238 client_at = autotest.Autotest(self._client) 239 client_at.install() 240 try: 241 self._proxy = RemoteFacadeProxy( 242 host=self._client, 243 no_chrome=no_chrome, 244 extra_browser_args=extra_browser_args) 245 finally: 246 if results_dir: 247 host.get_file(constants.MULTIMEDIA_XMLRPC_SERVER_LOG_FILE, 248 os.path.join(results_dir, 249 'multimedia_xmlrpc_server.log.init')) 250 251 252 def ready(self): 253 """Returns the proxy ready status""" 254 return self._proxy.ready() 255 256 257 def create_audio_facade(self): 258 """Creates an audio facade object.""" 259 return audio_facade_adapter.AudioFacadeRemoteAdapter( 260 self._client, self._proxy) 261 262 263 def create_video_facade(self): 264 """Creates a video facade object.""" 265 return video_facade_adapter.VideoFacadeRemoteAdapter( 266 self._client, self._proxy) 267 268 269 def create_display_facade(self): 270 """Creates a display facade object.""" 271 return display_facade_adapter.DisplayFacadeRemoteAdapter( 272 self._client, self._proxy) 273 274 275 def create_system_facade(self): 276 """Creates a system facade object.""" 277 return system_facade_adapter.SystemFacadeRemoteAdapter( 278 self._client, self._proxy) 279 280 281 def create_usb_facade(self): 282 """"Creates a USB facade object.""" 283 return usb_facade_adapter.USBFacadeRemoteAdapter(self._proxy) 284 285 286 def create_browser_facade(self): 287 """"Creates a browser facade object.""" 288 return browser_facade_adapter.BrowserFacadeRemoteAdapter(self._proxy) 289 290 291 def create_bluetooth_hid_facade(self): 292 """"Creates a bluetooth hid facade object.""" 293 return bluetooth_hid_facade_adapter.BluetoothHIDFacadeRemoteAdapter( 294 self._client, self._proxy) 295 296 297 def create_input_facade(self): 298 """"Creates an input facade object.""" 299 return input_facade_adapter.InputFacadeRemoteAdapter(self._proxy) 300 301 302 def create_cfm_facade(self): 303 """"Creates a cfm facade object.""" 304 return cfm_facade_adapter.CFMFacadeRemoteAdapter( 305 self._client, self._proxy) 306 307 308 def create_kiosk_facade(self): 309 """"Creates a kiosk facade object.""" 310 return kiosk_facade_adapter.KioskFacadeRemoteAdapter( 311 self._client, self._proxy) 312