[Autotest] multi-dut support in factory/test_that/autoserv
BUG=b/180139845
TEST=test_that --board=hana 100.107.106.131 infra_CompanionDuts --companion_hosts=100.107.106.133,chromeos6-row6-rack22-host3.cros
Change-Id: I26f33ea7f77a10aeaee11f77f0395ffe179be926
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/autotest/+/2723411
Commit-Queue: Derek Beckett <dbeckett@chromium.org>
Tested-by: Derek Beckett <dbeckett@chromium.org>
Reviewed-by: Garry Wang <xianuowang@chromium.org>
Reviewed-by: Seewai Fu <seewaifu@google.com>
diff --git a/server/autoserv b/server/autoserv
index 929e46c..77440a7 100755
--- a/server/autoserv
+++ b/server/autoserv
@@ -79,6 +79,27 @@
sys.exit(1)
+def _get_companions(parser):
+ """Get a list of machine names from command line arg -m or a file.
+
+ @param parser: Parser for the command line arguments.
+
+ @return: A list of machine names from command line arg -m or the
+ machines file specified in the command line arg -M.
+ """
+ if parser.options.companion_hosts:
+ companions = parser.options.companion_hosts.replace(',', ' ').strip().split()
+ else:
+ companions = []
+
+ if companions:
+ for companion in companions:
+ if not companion or re.search('\s', companion):
+ parser.parser.error("Invalid companion: %s" % str(companion))
+ companions = list(set(companions))
+ companions.sort()
+ return companions
+
def _get_machines(parser):
"""Get a list of machine names from command line arg -m or a file.
@@ -466,6 +487,7 @@
ssh_options = parser.options.ssh_options
no_use_packaging = parser.options.no_use_packaging
in_lab = bool(parser.options.lab)
+ companion_hosts = _get_companions(parser)
# can't be both a client and a server side test
if client and server:
@@ -547,6 +569,13 @@
'in_lab': in_lab,
'use_client_trampoline': use_client_trampoline,
'sync_offload_dir': parser.options.sync_offload_dir,
+ 'companion_hosts': server_job.get_machine_dicts(
+ machine_names=companion_hosts,
+ store_dir=os.path.join(results,
+ parser.options.host_info_subdir),
+ in_lab=in_lab,
+ use_shadow_store=not parser.options.local_only_host_info,
+ host_attributes=parser.options.host_attributes)
}
if parser.options.parent_job_id:
job_kwargs['parent_job_id'] = int(parser.options.parent_job_id)
diff --git a/server/autoserv_parser.py b/server/autoserv_parser.py
index 66b7dfc..097ea2f 100644
--- a/server/autoserv_parser.py
+++ b/server/autoserv_parser.py
@@ -237,6 +237,11 @@
default='2',
type=str,
choices=['2', '3'])
+ self.parser.add_argument('-ch',
+ action='store',
+ type=str,
+ dest='companion_hosts',
+ help='list of companion hosts for the test.')
#
# Warning! Please read before adding any new arguments!
diff --git a/server/autoserv_utils.py b/server/autoserv_utils.py
index 069dd6e..b1cfe32 100644
--- a/server/autoserv_utils.py
+++ b/server/autoserv_utils.py
@@ -17,10 +17,15 @@
autoserv_path = os.path.join(autoserv_directory, 'autoserv')
-def autoserv_run_job_command(autoserv_directory, machines,
- results_directory=None, extra_args=[], job=None,
- queue_entry=None, verbose=True,
- write_pidfile=True, fast_mode=False,
+def autoserv_run_job_command(autoserv_directory,
+ machines,
+ results_directory=None,
+ extra_args=[],
+ job=None,
+ queue_entry=None,
+ verbose=True,
+ write_pidfile=True,
+ fast_mode=False,
ssh_verbosity=0,
no_console_prefix=False,
ssh_options=None,
@@ -28,7 +33,8 @@
in_lab=False,
host_attributes=None,
use_virtualenv=False,
- host_info_subdir=''):
+ host_info_subdir='',
+ companion_hosts=None):
"""
Construct an autoserv command from a job or host queue entry.
@@ -68,6 +74,10 @@
support everywhere. Default: False.
@param host_info_subdir: When set, a sub-directory of the results directory
where host info file(s) are stored.
+ @param companion_hosts: a str or list of hosts to be used as companions
+ for the and provided to test. NOTE: these are
+ different than machines, where each host is a host
+ that the test would be run on.
@returns The autoserv command line as a list of executable + parameters.
@@ -96,6 +106,11 @@
if machines:
command += ['-m', machines]
+ if companion_hosts:
+ if not isinstance(companion_hosts, list):
+ companion_hosts = [companion_hosts]
+ command += ['-ch', ",".join(companion_hosts)]
+
if ssh_verbosity:
command += ['--ssh_verbosity', str(ssh_verbosity)]
diff --git a/server/hosts/__init__.py b/server/hosts/__init__.py
index e58e28d..77a8912 100644
--- a/server/hosts/__init__.py
+++ b/server/hosts/__init__.py
@@ -22,6 +22,8 @@
# factory function
from autotest_lib.server.hosts.factory import create_host
from autotest_lib.server.hosts.factory import create_target_machine
+ from autotest_lib.server.hosts.factory import create_companion_hosts
+
except ImportError:
# host abstract classes
from base_classes import Host
@@ -37,3 +39,4 @@
# factory function
from factory import create_host
from factory import create_target_machine
+ from factory import create_companion_hosts
\ No newline at end of file
diff --git a/server/hosts/factory.py b/server/hosts/factory.py
index 4a421b9..a5b01d8 100644
--- a/server/hosts/factory.py
+++ b/server/hosts/factory.py
@@ -187,6 +187,20 @@
ignore_timeout=False)
+def create_companion_hosts(companion_hosts):
+ """Wrapped for create_hosts for making host objects on companion duts.
+
+ @param companion_hosts: str or list of extra_host hostnames
+
+ @returns: A list of host objects for each host in companion_hosts
+ """
+ if not isinstance(companion_hosts, list):
+ companion_hosts = [companion_hosts]
+ hosts = []
+ for host in companion_hosts:
+ hosts.append(create_host(host))
+ return hosts
+
# TODO(kevcheng): Update the creation method so it's not a research project
# determining the class inheritance model.
def create_host(machine, host_class=None, connectivity_class=None, **args):
diff --git a/server/server_job.py b/server/server_job.py
index 2bbda25..20eb9b5 100644
--- a/server/server_job.py
+++ b/server/server_job.py
@@ -255,7 +255,8 @@
control_filename=SERVER_CONTROL_FILENAME,
parent_job_id=None, in_lab=False,
use_client_trampoline=False,
- sync_offload_dir=''):
+ sync_offload_dir='',
+ companion_hosts=None):
"""
Create a server side job object.
@@ -295,6 +296,10 @@
control file.
@param sync_offload_dir: String; relative path to synchronous offload
dir, relative to the results directory. Ignored if empty.
+ @param companion_hosts: a str or list of hosts to be used as companions
+ for the and provided to test. NOTE: these are different than
+ machines, where each host is a host that the test would be run
+ on.
"""
super(server_job, self).__init__(resultdir=resultdir)
self.control = control
@@ -327,6 +332,7 @@
self._control_filename = control_filename
self._disable_sysinfo = disable_sysinfo
self._use_client_trampoline = use_client_trampoline
+ self._companion_hosts = companion_hosts
self.logging = logging_manager.get_logging_manager(
manage_stdout_and_stderr=True, redirect_fds=True)
@@ -844,6 +850,8 @@
logging.debug("Results dir is %s", self.resultdir)
logging.debug("Synchronous offload dir is %s", sync_dir)
logging.info("Processing control file")
+ if self._companion_hosts:
+ namespace['companion_hosts'] = self._companion_hosts
namespace['use_packaging'] = use_packaging
namespace['synchronous_offload_dir'] = sync_dir
namespace['extended_timeout'] = self.extended_timeout
diff --git a/server/site_tests/infra_CompanionDuts/control b/server/site_tests/infra_CompanionDuts/control
new file mode 100644
index 0000000..b4d1914
--- /dev/null
+++ b/server/site_tests/infra_CompanionDuts/control
@@ -0,0 +1,22 @@
+# Copyright 2021 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.
+
+AUTHOR = 'dbeckett'
+NAME = 'infra_CompanionDuts'
+TIME = 'SHORT'
+TEST_CATEGORY = 'General'
+TEST_CLASS = 'stub'
+TEST_TYPE = 'server'
+
+DOC = """
+Verify the companion dut flag reaches a test.
+"""
+
+
+def run(machine):
+ host = hosts.create_host(machine)
+ companions = hosts.create_companion_hosts(companion_hosts)
+ job.run_test('infra_CompanionDuts', host=host, companions=companions)
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/infra_CompanionDuts/infra_CompanionDuts.py b/server/site_tests/infra_CompanionDuts/infra_CompanionDuts.py
new file mode 100644
index 0000000..aa03c09
--- /dev/null
+++ b/server/site_tests/infra_CompanionDuts/infra_CompanionDuts.py
@@ -0,0 +1,28 @@
+# Copyright 2021 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.
+
+from autotest_lib.client.common_lib import error
+from autotest_lib.server import test
+
+
+class infra_CompanionDuts(test.test):
+ """
+ Verify the companion dut flag reaches a test.
+
+ """
+ version = 1
+
+ def run_once(self, host, companions):
+ """
+ Starting point of this test.
+
+ Note: base class sets host as self._host.
+
+ """
+ self.host = host
+ for c in companions:
+ dut_out = c.run('echo True').stdout.strip()
+ if dut_out != 'True':
+ raise error.TestError("Companion DUT stdout != True (got: %s)",
+ dut_out)
diff --git a/site_utils/test_runner_utils.py b/site_utils/test_runner_utils.py
index 94bce75..000a2c1 100755
--- a/site_utils/test_runner_utils.py
+++ b/site_utils/test_runner_utils.py
@@ -252,7 +252,7 @@
def run_job(job, host, info, autotest_path, results_directory, fast_mode,
id_digits=1, ssh_verbosity=0, ssh_options=None,
args=None, pretend=False,
- autoserv_verbose=False):
+ autoserv_verbose=False, companion_hosts=None):
"""
Shell out to autoserv to run an individual test job.
@@ -274,6 +274,7 @@
@param pretend: If True, will print out autoserv commands rather than
running them.
@param autoserv_verbose: If true, pass the --verbose flag to autoserv.
+ @param companion_hosts: Companion hosts for the test.
@returns: a tuple, return code of the job and absolute path of directory
where results were stored.
@@ -305,7 +306,8 @@
no_console_prefix=True,
use_packaging=False,
host_attributes=info.attributes,
- host_info_subdir=_HOST_INFO_SUBDIR)
+ host_info_subdir=_HOST_INFO_SUBDIR,
+ companion_hosts=companion_hosts)
code = _run_autoserv(command, pretend)
return code, results_directory
@@ -454,7 +456,8 @@
autoserv_verbose=False,
iterations=1,
host_attributes={},
- job_retry=True):
+ job_retry=True,
+ companion_hosts=None):
"""Perform local run of tests.
This method enforces satisfaction of test dependencies for tests that are
@@ -483,6 +486,7 @@
@param iterations: int number of times to schedule tests.
@param host_attributes: Dict of host attributes to pass into autoserv.
@param job_retry: If False, tests will not be retried at all.
+ @param companion_hosts: companion hosts for the test.
@returns: A list of return codes each job that has run. Or [1] if
provision failed prior to running any jobs.
@@ -544,6 +548,7 @@
args,
pretend,
autoserv_verbose,
+ companion_hosts
)
codes.append(code)
logging.debug("Code: %s, Results in %s", code, abs_dir)
@@ -696,7 +701,8 @@
debug=False,
allow_chrome_crashes=False,
host_attributes={},
- job_retry=True):
+ job_retry=True,
+ companion_hosts=None):
"""
Perform a test_that run, from the |autotest_path|.
@@ -730,6 +736,7 @@
@param allow_chrome_crashes: If True, allow chrome crashes.
@param host_attributes: Dict of host attributes to pass into autoserv.
@param job_retry: If False, tests will not be retried at all.
+ @param companion_hosts: companion hosts for the test.
@return: A return code that test_that should exit with.
"""
@@ -766,8 +773,8 @@
autoserv_verbose=debug,
iterations=iterations,
host_attributes=host_attributes,
- job_retry=job_retry)
-
+ job_retry=job_retry,
+ companion_hosts=companion_hosts)
if pretend:
logging.info('Finished pretend run. Exiting.')
return 0
diff --git a/site_utils/test_runner_utils_unittest.py b/site_utils/test_runner_utils_unittest.py
index 10c574b..daafa6c 100755
--- a/site_utils/test_runner_utils_unittest.py
+++ b/site_utils/test_runner_utils_unittest.py
@@ -216,6 +216,7 @@
mox.StrContains(self.args),
False,
False,
+ None,
).AndReturn((0, '/fake/dir'))
self.mox.ReplayAll()
diff --git a/site_utils/test_that.py b/site_utils/test_that.py
index 64a01a3..caf5e64 100755
--- a/site_utils/test_that.py
+++ b/site_utils/test_that.py
@@ -164,6 +164,10 @@
parser.add_argument('--ssh_private_key', action='store',
default=test_runner_utils.TEST_KEY_PATH,
help='Path to the private ssh key.')
+ parser.add_argument('--companion_hosts',
+ action='store',
+ default=None,
+ help='Companion duts for the test.')
return parser.parse_args(argv), remote_argv
@@ -329,7 +333,8 @@
debug=arguments.debug,
allow_chrome_crashes=arguments.allow_chrome_crashes,
pretend=arguments.pretend,
- job_retry=arguments.retry)
+ job_retry=arguments.retry,
+ companion_hosts=arguments.companion_hosts)
def _main_for_lab_run(argv, arguments):