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