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