未验证 提交 12ecd91a 编写于 作者: L Lukáš Doktor

Merging pull request 1569

* https://github.com/avocado-framework/avocado:
  remote: don't copy files for remote executions
  remote: improve error handling from remote executions
  remote: create a `DummyLoader` and use it in 'remote'
  refactor the total number of tests probe
......@@ -420,15 +420,11 @@ class Job(object):
This is a public Job API as part of the documented Job phases
"""
if (getattr(self.args, 'remote_hostname', False) and
getattr(self.args, 'remote_no_copy', False)):
self.test_suite = [(None, {})]
else:
try:
self.test_suite = self._make_test_suite(self.urls)
except loader.LoaderError as details:
stacktrace.log_exc_info(sys.exc_info(), 'avocado.app.debug')
raise exceptions.OptionValidationError(details)
try:
self.test_suite = self._make_test_suite(self.urls)
except loader.LoaderError as details:
stacktrace.log_exc_info(sys.exc_info(), 'avocado.app.debug')
raise exceptions.OptionValidationError(details)
def pre_tests(self):
"""
......@@ -461,7 +457,6 @@ class Job(object):
except (IOError, ValueError) as details:
raise exceptions.OptionValidationError("Unable to parse mux: "
"%s" % details)
self.args.test_result_total = mux.get_number_of_tests(self.test_suite)
self._make_old_style_test_result()
self._make_test_runner()
......@@ -470,9 +465,10 @@ class Job(object):
self._log_job_debug_info(mux)
jobdata.record(self.args, self.logdir, mux, self.urls, sys.argv)
replay_map = getattr(self.args, 'replay_map', None)
summary = self.test_runner.run_suite(self.test_suite, mux, self.timeout,
replay_map,
self.args.test_result_total)
summary = self.test_runner.run_suite(self.test_suite,
mux,
self.timeout,
replay_map)
# If it's all good so far, set job status to 'PASS'
if self.status == 'RUNNING':
self.status = 'PASS'
......
......@@ -264,6 +264,9 @@ class TestLoaderProxy(object):
return test_instance
def clear_plugins(self):
self.registered_plugins = []
class TestLoader(object):
......@@ -796,4 +799,26 @@ class ExternalLoader(TestLoader):
return {test.ExternalRunnerTest: output.TERM_SUPPORT.healthy_str}
class DummyLoader(TestLoader):
"""
Dummy-runner loader class
"""
name = 'dummy'
def __init__(self, args, extra_params):
super(DummyLoader, self).__init__(args, extra_params)
def discover(self, url, which_tests=DEFAULT):
return [(test.SkipTest, {'name': url})]
@staticmethod
def get_type_label_mapping():
return {test.SkipTest: 'DUMMY'}
@staticmethod
def get_decorator_mapping():
return {test.SkipTest: output.TERM_SUPPORT.healthy_str}
loader = TestLoaderProxy()
......@@ -22,7 +22,6 @@ import logging
from fabric.exceptions import CommandTimeout
from .test import RemoteTest
from .. import data_dir
from .. import output
from .. import remoter
from .. import virt
......@@ -50,41 +49,6 @@ class RemoteTestRunner(TestRunner):
#: remoter connection to the remote machine
self.remote = None
def _copy_files(self):
"""
Gather test directories and copy them recursively to
$remote_test_dir + $test_absolute_path.
:note: Default tests execution is translated into absolute paths too
"""
if self.job.args.remote_no_copy: # Leave everything as is
return
# TODO: Use `avocado.core.loader.TestLoader` instead
self.remote.makedir(self.remote_test_dir)
paths = set()
for i in xrange(len(self.job.urls)):
url = self.job.urls[i]
if not os.path.exists(url): # use test_dir path + py
url = os.path.join(data_dir.get_test_dir(), url)
if not os.path.exists(url):
raise exceptions.JobError("Unable to map test id '%s' to file"
% self.job.urls[i])
url = os.path.abspath(url) # always use abspath; avoid clashes
# modify url to remote_path + abspath
paths.add(url)
self.job.urls[i] = self.remote_test_dir + url
for path in sorted(paths):
rpath = self.remote_test_dir + path
self.remote.makedir(os.path.dirname(rpath))
self.remote.send_files(path, os.path.dirname(rpath))
test_data = path + '.data'
if os.path.isdir(test_data):
self.remote.send_files(test_data, os.path.dirname(rpath))
for mux_file in getattr(self.job.args, 'mux_yaml') or []:
rpath = os.path.join(self.remote_test_dir, mux_file)
self.remote.makedir(os.path.dirname(rpath))
self.remote.send_files(mux_file, rpath)
def setup(self):
""" Setup remote environment and copy test directories """
self.job.log.info("LOGIN : %s@%s:%d (TIMEOUT: %s seconds)",
......@@ -179,23 +143,13 @@ class RemoteTestRunner(TestRunner):
:return: a dictionary with test results.
"""
extra_params = []
mux_files = [os.path.join(self.remote_test_dir, mux_file)
for mux_file in getattr(self.job.args,
'mux_yaml') or []]
mux_files = getattr(self.job.args, 'mux_yaml') or []
if mux_files:
extra_params.append("-m %s" % " ".join(mux_files))
if getattr(self.job.args, "dry_run", False):
extra_params.append("--dry-run")
urls_str = " ".join(urls)
avocado_check_urls_cmd = ('cd %s; avocado list %s '
'--paginator=off' % (self.remote_test_dir,
urls_str))
check_urls_result = self.remote.run(avocado_check_urls_cmd,
ignore_status=True,
timeout=60)
if check_urls_result.exit_status != 0:
raise exceptions.JobError(check_urls_result.stdout)
avocado_cmd = ('cd %s; avocado run --force-job-id %s --json - '
'--archive %s %s' % (self.remote_test_dir,
......@@ -209,7 +163,12 @@ class RemoteTestRunner(TestRunner):
"specified timeout (%s). Interrupting."
% (timeout))
json_result = self._parse_json_response(result.stdout)
try:
json_result = self._parse_json_response(result.stdout)
except:
stacktrace.log_exc_info(sys.exc_info(), logger='avocado.debug')
raise exceptions.JobError(result.stdout)
for t_dict in json_result['tests']:
logdir = os.path.join(self.job.logdir, 'test-results')
relative_path = astring.string_to_safe_path(t_dict['test'])
......@@ -219,8 +178,7 @@ class RemoteTestRunner(TestRunner):
return json_result
def run_suite(self, test_suite, mux, timeout=0, replay_map=None,
test_result_total=0):
def run_suite(self, test_suite, mux, timeout=0, replay_map=None):
"""
Run one or more tests and report with test result.
......@@ -231,7 +189,6 @@ class RemoteTestRunner(TestRunner):
"""
del test_suite # using self.job.urls instead
del mux # we're not using multiplexation here
del test_result_total # evaluated by the remote avocado
if not timeout: # avoid timeout = 0
timeout = None
summary = set()
......@@ -264,12 +221,12 @@ class RemoteTestRunner(TestRunner):
if not avocado_installed:
raise exceptions.JobError('Remote machine does not seem to'
' have avocado installed')
self._copy_files()
except Exception as details:
stacktrace.log_exc_info(sys.exc_info(), logger='avocado.test')
raise exceptions.JobError(details)
results = self.run_test(self.job.urls, timeout)
remote_log_dir = os.path.dirname(results['debuglog'])
self.result_proxy.set_tests_total(results['total'])
self.result_proxy.start_tests()
for tst in results['tests']:
name = tst['test'].split('-', 1)
......@@ -365,7 +322,6 @@ class VMTestRunner(RemoteTestRunner):
self.job.args.remote_username = self.job.args.vm_username
self.job.args.remote_password = self.job.args.vm_password
self.job.args.remote_key_file = self.job.args.vm_key_file
self.job.args.remote_no_copy = self.job.args.vm_no_copy
self.job.args.remote_timeout = self.job.args.vm_timeout
super(VMTestRunner, self).setup()
......
......@@ -81,6 +81,10 @@ class ResultProxy(object):
for output_plugin in self.output_plugins:
output_plugin.check_test(state)
def set_tests_total(self, tests_total):
for output_plugin in self.output_plugins:
output_plugin.tests_total = tests_total
class Result(object):
......@@ -96,7 +100,7 @@ class Result(object):
"""
self.job_unique_id = getattr(job, "unique_id", None)
self.logfile = getattr(job, "logfile", None)
self.tests_total = getattr(job.args, 'test_result_total', 1)
self.tests_total = 0
self.tests_run = 0
self.tests_total_time = 0.0
self.passed = 0
......
......@@ -481,8 +481,7 @@ class TestRunner(object):
factory = template
yield factory, variant
def run_suite(self, test_suite, mux, timeout=0, replay_map=None,
test_result_total=0):
def run_suite(self, test_suite, mux, timeout=0, replay_map=None):
"""
Run one or more tests and report with test result.
......@@ -494,7 +493,6 @@ class TestRunner(object):
summary = set()
if self.job.sysinfo is not None:
self.job.sysinfo.start_job_hook()
self.result_proxy.start_tests()
queue = queues.SimpleQueue()
if timeout > 0:
......@@ -502,7 +500,10 @@ class TestRunner(object):
else:
deadline = None
test_result_total = mux.get_number_of_tests(test_suite)
no_digits = len(str(test_result_total))
self.result_proxy.set_tests_total(test_result_total)
self.result_proxy.start_tests()
index = -1
try:
......
......@@ -61,29 +61,6 @@ class DockerRemoter(object):
""" Return this remoter's container ID """
return self._docker_id
def makedir(self, remote_path):
"""
Create a directory on the container
:warning: No other process must be running on foreground
:param remote_path: the remote path to create.
"""
self._docker.cmd("mkdir -p %s" % remote_path)
def send_files(self, local_path, remote_path):
"""
Send files to the container
"""
process.run("%s cp %s %s:%s" % (self._dkrcmd, local_path,
self._docker_id, remote_path))
def receive_files(self, local_path, remote_path):
"""
Receive files from the container
"""
process.run("%s cp %s:%s %s" % (self._dkrcmd, self._docker_id,
remote_path, local_path))
def run(self, command, ignore_status=False, quiet=None, timeout=60):
"""
Run command inside the container
......@@ -132,8 +109,6 @@ class DockerTestRunner(RemoteTestRunner):
Test runner which runs the job inside a docker container
"""
remote_test_dir = "/avocado_remote_test_dir" # Absolute path only
def __init__(self, job, test_result):
super(DockerTestRunner, self).__init__(job, test_result)
self.remote = None # Will be set in `setup`
......@@ -143,12 +118,9 @@ class DockerTestRunner(RemoteTestRunner):
dkr_opt = self.job.args.docker_options
dkr_name = os.path.basename(self.job.logdir) + '.' + 'avocado'
self.remote = DockerRemoter(dkrcmd, self.job.args.docker, dkr_opt, dkr_name)
# We need to create the base dir, otherwise docker creates it as root
self.remote.makedir(self.remote_test_dir)
self.job.log.info("DOCKER : Container id '%s'"
% self.remote.get_cid())
self.job.log.debug("DOCKER : Container name '%s'" % dkr_name)
self.job.args.remote_no_copy = self.job.args.docker_no_copy
def tear_down(self):
try:
......@@ -186,10 +158,6 @@ class Docker(CLI):
cmd_parser.add_argument("--docker-options", default="",
help="Extra options for docker run cmd."
" (see: man docker-run)", metavar="OPT")
cmd_parser.add_argument("--docker-no-copy", action="store_true",
help="Assume tests are already in the "
"container")
cmd_parser.add_argument("--docker-no-cleanup", action="store_true",
help="Preserve container after test")
......
......@@ -19,6 +19,7 @@ import logging
import sys
from avocado.core import exit_codes
from avocado.core import loader
from avocado.core import remoter
from avocado.core.plugin_interfaces import CLI
from avocado.core.remote import RemoteResult
......@@ -68,11 +69,6 @@ class Remote(CLI):
help='Specify an identity file with '
'a private key instead of a password '
'(Example: .pem files from Amazon EC2)')
self.remote_parser.add_argument('--remote-no-copy',
dest='remote_no_copy',
action='store_true',
help="Don't copy tests and use the "
"exact uri on guest machine.")
self.remote_parser.add_argument('--remote-timeout', metavar='SECONDS',
help=("Amount of time (in seconds) to "
"wait for a successful connection"
......@@ -106,6 +102,8 @@ class Remote(CLI):
def run(self, args):
if self._check_required_args(args, 'remote_hostname',
('remote_hostname',)):
loader.loader.clear_plugins()
loader.loader.register_plugin(loader.DummyLoader)
register_test_result_class(args, RemoteResult)
args.test_runner = RemoteTestRunner
setattr(args, 'stdout_claimed_by', '--remote-hostname')
......@@ -74,9 +74,6 @@ class VM(CLI):
action='store_true', default=False,
help='Restore VM to a previous state, '
'before running tests')
self.vm_parser.add_argument('--vm-no-copy', action='store_true',
help="Don't copy tests and use the "
"exact uri on VM machine.")
self.vm_parser.add_argument('--vm-timeout', metavar='SECONDS',
help=("Amount of time (in seconds) to "
"wait for a successful connection"
......
......@@ -62,9 +62,8 @@ Once the remote machine is properly setup, you may run your test. Example::
RESULTS : PASS 1 | ERROR 0 | FAIL 1 | SKIP 0 | WARN 0 | INTERRUPT 0
TESTS TIME : 1.01 s
As you can see, Avocado will copy the tests you have to your remote machine and
execute them. A bit of extra logging information is added to your job summary,
mainly to distinguish the regular execution from the remote one. Note here that
A bit of extra logging information is added to your job summary, mainly
to distinguish the regular execution from the remote one. Note here that
we did not need `--remote-password` because an SSH key was already setup.
Running Tests on a Virtual Machine
......@@ -141,9 +140,8 @@ Once the virtual machine is properly setup, you may run your test. Example::
RESULTS : PASS 1 | ERROR 0 | FAIL 1 | SKIP 0 | WARN 0 | INTERRUPT 0
TESTS TIME : 1.01 s
As you can see, Avocado will copy the tests you have to your libvirt domain and
execute them. A bit of extra logging information is added to your job summary,
mainly to distinguish the regular execution from the remote one. Note here that
A bit of extra logging information is added to your job summary, mainly
to distinguish the regular execution from the remote one. Note here that
we did not need `--vm-password` because the SSH key is already setup.
Running Tests on a Docker container
......@@ -233,3 +231,18 @@ below::
By doing that, both `MYVAR1` and `MYVAR2` will be available in remote
environment.
Known Issues
============
Given the modular architecture of Avocado, the fact that the ``remote``
feature is a plugin and also the fact that the plugins are engaged in no
particular order, other plugins will not have the information that we
are in a remote execution. As consequence, plugins that look for local
resources that are available only remotely can fail. That's the case of
the so called ``multiplex`` plugin. If you're using the multiplex plugin
(``-m`` or ``--mux-yaml``) options in addition to the remote plugin (or
any derived plugin, like ``vm`` or ``docker``), the multiplex files must
exist locally in the provided path. Notice the multiplex files must be
also available remotely in the provided path, since we don't copy files
for remote executions.
......@@ -32,6 +32,7 @@ class JSONResultTest(unittest.TestCase):
self.job = job.Job(args)
self.test_result = Result(FakeJob(args))
self.test_result.filename = self.tmpfile[1]
self.test_result.tests_total = 1
self.test_result.start_tests()
self.test1 = SimpleTest(job=self.job, base_logdir=self.tmpdir)
self.test1.status = 'PASS'
......
......@@ -35,10 +35,10 @@ class RemoteTestRunnerTest(unittest.TestCase):
remote_port=22,
remote_password='password',
remote_key_file=None,
remote_no_copy=False,
remote_timeout=60,
show_job_log=False,
mux_yaml=['foo.yaml', 'bar/baz.yaml'],
mux_yaml=['~/avocado/tests/foo.yaml',
'~/avocado/tests/bar/baz.yaml'],
dry_run=True,
env_keep=None)
log = flexmock()
......@@ -51,7 +51,6 @@ class RemoteTestRunnerTest(unittest.TestCase):
flexmock(remote.RemoteTestRunner).should_receive('__init__')
self.runner = remote.RemoteTestRunner(job, None)
self.runner.job = job
self.runner._copy_files = lambda: True # Skip _copy_files
filehandler = logging.StreamHandler()
flexmock(logging).should_receive("FileHandler").and_return(filehandler)
......@@ -96,13 +95,6 @@ _=/usr/bin/env''', exit_status=0)
.with_args(args_version, ignore_status=True, timeout=60)
.once().and_return(version_result))
args = ('cd ~/avocado/tests; avocado list /tests/sleeptest '
'/tests/other/test passtest --paginator=off')
urls_result = flexmock(exit_status=0)
(Remote.should_receive('run')
.with_args(args, ignore_status=True, timeout=60)
.once().and_return(urls_result))
args = ("cd ~/avocado/tests; avocado run --force-job-id 1-sleeptest;0 "
"--json - --archive /tests/sleeptest /tests/other/test "
"passtest -m ~/avocado/tests/foo.yaml "
......@@ -113,8 +105,10 @@ _=/usr/bin/env''', exit_status=0)
Results = flexmock(remote=Remote, urls=['sleeptest'],
stream=stream, timeout=None,
args=flexmock(show_job_log=False,
mux_yaml=['foo.yaml', 'bar/baz.yaml'],
mux_yaml=['~/avocado/tests/foo.yaml',
'~/avocado/tests/bar/baz.yaml'],
dry_run=True))
Results.should_receive('set_tests_total').once().with_args(1).ordered()
Results.should_receive('start_tests').once().ordered()
args = {'status': u'PASS', 'whiteboard': '', 'time_start': 0,
'name': '1-sleeptest;0', 'class_name': 'RemoteTest',
......@@ -169,7 +163,6 @@ class RemoteTestRunnerSetup(unittest.TestCase):
remote_port=22,
remote_password='password',
remote_key_file=None,
remote_no_copy=False,
remote_timeout=60,
show_job_log=False,
env_keep=None)
......
......@@ -38,6 +38,7 @@ class xUnitSucceedTest(unittest.TestCase):
args.xunit_output = self.tmpfile[1]
self.job = job.Job(args)
self.test_result = Result(FakeJob(args))
self.test_result.tests_total = 1
self.test_result.start_tests()
self.test1 = SimpleTest(job=self.job, base_logdir=self.tmpdir)
self.test1.status = 'PASS'
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册