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