You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.6 KiB
152 lines
5.6 KiB
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import httplib
|
|
import logging
|
|
import socket
|
|
import time
|
|
import xmlrpclib
|
|
|
|
from autotest_lib.client.cros.faft.config import Config as ClientConfig
|
|
from autotest_lib.server import autotest
|
|
|
|
|
|
class _Method(object):
|
|
"""Class to save the name of the RPC method instead of the real object.
|
|
|
|
It keeps the name of the RPC method locally first such that the RPC method
|
|
can be evalulated to a real object while it is called. Its purpose is to
|
|
refer to the latest RPC proxy as the original previous-saved RPC proxy may
|
|
be lost due to reboot.
|
|
|
|
The call_method is the method which does refer to the latest RPC proxy.
|
|
"""
|
|
def __init__(self, call_method, name):
|
|
self.__call_method = call_method
|
|
self.__name = name
|
|
|
|
def __getattr__(self, name):
|
|
# Support a nested method.
|
|
return _Method(self.__call_method, "%s.%s" % (self.__name, name))
|
|
|
|
def __call__(self, *args, **dargs):
|
|
return self.__call_method(self.__name, *args, **dargs)
|
|
|
|
def __repr__(self):
|
|
"""Return a description of the method object"""
|
|
return "%s('%s')" % (self.__class__.__name__, self.__name)
|
|
|
|
def __str__(self):
|
|
"""Return a description of the method object"""
|
|
return "<%s '%s'>" % (self.__class__.__name__, self.__name)
|
|
|
|
|
|
class RPCProxy(object):
|
|
"""Proxy to the FAFTClient RPC server on DUT.
|
|
|
|
It acts as a proxy to the FAFTClient on DUT. It is smart enough to:
|
|
- postpone the RPC connection to the first class method call;
|
|
- reconnect to the RPC server in case connection lost, e.g. reboot;
|
|
- always call the latest RPC proxy object.
|
|
|
|
@ivar _client: the ssh host object
|
|
@type host: autotest_lib.server.hosts.abstract_ssh.AbstractSSHHost
|
|
@ivar _faft_client: the real serverproxy to use for calls
|
|
@type _faft_client: xmlrpclib.ServerProxy | None
|
|
"""
|
|
_client_config = ClientConfig()
|
|
|
|
def __init__(self, host):
|
|
"""Constructor.
|
|
|
|
@param host: The host object, passed via the test control file.
|
|
"""
|
|
self._client = host
|
|
self._faft_client = None
|
|
|
|
def __del__(self):
|
|
self.disconnect()
|
|
|
|
def __getattr__(self, name):
|
|
"""Return a _Method object only, not its real object."""
|
|
return _Method(self.__call_faft_client, name)
|
|
|
|
def __call_faft_client(self, name, *args, **dargs):
|
|
"""Make the call on the latest RPC proxy object.
|
|
|
|
This method gets the internal method of the RPC proxy and calls it.
|
|
|
|
@param name: Name of the RPC method, a nested method supported.
|
|
@param args: The rest of arguments.
|
|
@param dargs: The rest of dict-type arguments.
|
|
@return: The return value of the FAFTClient RPC method.
|
|
"""
|
|
if self._faft_client is None:
|
|
self.connect()
|
|
try:
|
|
return getattr(self._faft_client, name)(*args, **dargs)
|
|
except (socket.error,
|
|
httplib.BadStatusLine,
|
|
xmlrpclib.ProtocolError):
|
|
# Reconnect the RPC server in case connection lost, e.g. reboot.
|
|
self.connect()
|
|
# Try again.
|
|
return getattr(self._faft_client, name)(*args, **dargs)
|
|
|
|
def connect(self):
|
|
"""Connect the RPC server."""
|
|
# Make sure Autotest dependency is there.
|
|
autotest.Autotest(self._client).install()
|
|
self._faft_client = self._client.rpc_server_tracker.xmlrpc_connect(
|
|
self._client_config.rpc_command,
|
|
self._client_config.rpc_port,
|
|
command_name=self._client_config.rpc_command_short,
|
|
ready_test_name=self._client_config.rpc_ready_call,
|
|
timeout_seconds=self._client_config.rpc_timeout,
|
|
logfile="%s.%s" % (self._client_config.rpc_logfile,
|
|
time.time()),
|
|
server_desc=str(self)
|
|
)
|
|
|
|
def disconnect(self):
|
|
"""Disconnect the RPC server."""
|
|
# The next start of the RPC server will terminate any leftovers,
|
|
# so no need to pkill upon disconnect.
|
|
if self._faft_client is not None:
|
|
logging.debug("Closing FAFT RPC server connection.")
|
|
self._client.rpc_server_tracker.disconnect(
|
|
self._client_config.rpc_port, pkill=False)
|
|
self._faft_client = None
|
|
|
|
def quit(self):
|
|
"""Tell the RPC server to quit, then disconnect from it."""
|
|
if self._faft_client is None:
|
|
return
|
|
logging.debug("Telling FAFT RPC server to quit.")
|
|
try:
|
|
remote_quit = getattr(
|
|
self._faft_client, self._client_config.rpc_quit_call)
|
|
remote_quit()
|
|
need_pkill = False
|
|
except (StandardError, httplib.BadStatusLine, xmlrpclib.Error) as e:
|
|
logging.warn("Error while telling FAFT RPC server to quit: %s", e)
|
|
# If we failed to tell the RPC server to quit for some reason,
|
|
# fall back to SIGTERM, because it may not have exited.
|
|
need_pkill = True
|
|
|
|
self._client.rpc_server_tracker.disconnect(
|
|
self._client_config.rpc_port, pkill=need_pkill)
|
|
self._faft_client = None
|
|
|
|
def __repr__(self):
|
|
"""Return a description of the proxy object"""
|
|
return '%s(%s)' % (self.__class__.__name__, self._client)
|
|
|
|
def __str__(self):
|
|
"""Return a description of the proxy object"""
|
|
return "<%s '%s:%s'>" % (
|
|
self.__class__.__name__,
|
|
self._client.hostname,
|
|
self._client_config.rpc_port)
|