• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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