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