• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 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 time
9import xmlrpclib
10
11from autotest_lib.client.cros.faft.config import Config as ClientConfig
12from autotest_lib.server import autotest
13
14
15class _Method(object):
16    """Class to save the name of the RPC method instead of the real object.
17
18    It keeps the name of the RPC method locally first such that the RPC method
19    can be evalulated to a real object while it is called. Its purpose is to
20    refer to the latest RPC proxy as the original previous-saved RPC proxy may
21    be lost due to reboot.
22
23    The call_method is the method which does refer to the latest RPC proxy.
24    """
25    def __init__(self, call_method, name):
26        self.__call_method = call_method
27        self.__name = name
28
29    def __getattr__(self, name):
30        # Support a nested method.
31        return _Method(self.__call_method, "%s.%s" % (self.__name, name))
32
33    def __call__(self, *args, **dargs):
34        return self.__call_method(self.__name, *args, **dargs)
35
36    def __repr__(self):
37        """Return a description of the method object"""
38        return "%s('%s')" % (self.__class__.__name__, self.__name)
39
40    def __str__(self):
41        """Return a description of the method object"""
42        return "<%s '%s'>" % (self.__class__.__name__, self.__name)
43
44
45class RPCProxy(object):
46    """Proxy to the FAFTClient RPC server on DUT.
47
48    It acts as a proxy to the FAFTClient on DUT. It is smart enough to:
49     - postpone the RPC connection to the first class method call;
50     - reconnect to the RPC server in case connection lost, e.g. reboot;
51     - always call the latest RPC proxy object.
52
53     @ivar _client: the ssh host object
54     @type host: autotest_lib.server.hosts.abstract_ssh.AbstractSSHHost
55     @ivar _faft_client: the real serverproxy to use for calls
56     @type _faft_client: xmlrpclib.ServerProxy | None
57    """
58    _client_config = ClientConfig()
59
60    def __init__(self, host):
61        """Constructor.
62
63        @param host: The host object, passed via the test control file.
64        """
65        self._client = host
66        self._faft_client = None
67
68    def __del__(self):
69        self.disconnect()
70
71    def __getattr__(self, name):
72        """Return a _Method object only, not its real object."""
73        return _Method(self.__call_faft_client, name)
74
75    def __call_faft_client(self, name, *args, **dargs):
76        """Make the call on the latest RPC proxy object.
77
78        This method gets the internal method of the RPC proxy and calls it.
79
80        @param name: Name of the RPC method, a nested method supported.
81        @param args: The rest of arguments.
82        @param dargs: The rest of dict-type arguments.
83        @return: The return value of the FAFTClient RPC method.
84        """
85        if self._faft_client is None:
86            self.connect()
87        try:
88            return getattr(self._faft_client, name)(*args, **dargs)
89        except (socket.error,
90                httplib.BadStatusLine,
91                xmlrpclib.ProtocolError):
92            # Reconnect the RPC server in case connection lost, e.g. reboot.
93            self.connect()
94            # Try again.
95            return getattr(self._faft_client, name)(*args, **dargs)
96
97    def connect(self):
98        """Connect the RPC server."""
99        # Make sure Autotest dependency is there.
100        autotest.Autotest(self._client).install()
101        self._faft_client = self._client.rpc_server_tracker.xmlrpc_connect(
102                self._client_config.rpc_command,
103                self._client_config.rpc_port,
104                command_name=self._client_config.rpc_command_short,
105                ready_test_name=self._client_config.rpc_ready_call,
106                timeout_seconds=self._client_config.rpc_timeout,
107                logfile="%s.%s" % (self._client_config.rpc_logfile,
108                                   time.time()),
109                server_desc=str(self)
110                )
111
112    def disconnect(self):
113        """Disconnect the RPC server."""
114        # The next start of the RPC server will terminate any leftovers,
115        # so no need to pkill upon disconnect.
116        if self._faft_client is not None:
117            logging.debug("Closing FAFT RPC server connection.")
118        self._client.rpc_server_tracker.disconnect(
119                self._client_config.rpc_port, pkill=False)
120        self._faft_client = None
121
122    def quit(self):
123        """Tell the RPC server to quit, then disconnect from it."""
124        if self._faft_client is None:
125            return
126        logging.debug("Telling FAFT RPC server to quit.")
127        try:
128            remote_quit = getattr(
129                    self._faft_client, self._client_config.rpc_quit_call)
130            remote_quit()
131            need_pkill = False
132        except (StandardError, httplib.BadStatusLine, xmlrpclib.Error) as e:
133            logging.warn("Error while telling FAFT RPC server to quit: %s", e)
134            # If we failed to tell the RPC server to quit for some reason,
135            # fall back to SIGTERM, because it may not have exited.
136            need_pkill = True
137
138        self._client.rpc_server_tracker.disconnect(
139                self._client_config.rpc_port, pkill=need_pkill)
140        self._faft_client = None
141
142    def __repr__(self):
143        """Return a description of the proxy object"""
144        return '%s(%s)' % (self.__class__.__name__, self._client)
145
146    def __str__(self):
147        """Return a description of the proxy object"""
148        return "<%s '%s:%s'>" % (
149                self.__class__.__name__,
150                self._client.hostname,
151                self._client_config.rpc_port)
152