1# Copyright (c) 2014 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 logging
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.server.cros import moblab_test
9from autotest_lib.server.hosts import moblab_host
10from autotest_lib.utils import labellib
11
12
13_CLEANUP_TIME_M = 5
14_MOBLAB_IMAGE_STORAGE = '/mnt/moblab/static'
15
16class moblab_RunSuite(moblab_test.MoblabTest):
17    """
18    Moblab run suite test. Ensures that a Moblab can run a suite from start
19    to finish by kicking off a suite which will have the Moblab stage an
20    image, provision its DUTs and run the tests.
21    """
22    version = 1
23
24
25    def run_once(self, host, suite_name, moblab_suite_max_retries,
26                 target_build='', clear_devserver_cache=True,
27                 test_timeout_hint_m=None):
28        """Runs a suite on a Moblab Host against its test DUTS.
29
30        @param host: Moblab Host that will run the suite.
31        @param suite_name: Name of the suite to run.
32        @param moblab_suite_max_retries: The maximum number of test retries
33                allowed within the suite launched on moblab.
34        @param target_build: Optional build to be use in the run_suite
35                call on moblab. This argument is passed as is to run_suite. It
36                must be a sensible build target for the board of the sub-DUTs
37                attached to the moblab.
38        @param clear_devserver_cache: If True, image cache of the devserver
39                running on moblab is cleared before running the test to validate
40                devserver imaging staging flow.
41        @param test_timeout_hint_m: (int) Optional overall timeout for the test.
42                For this test, it is very important to collect post failure data
43                from the moblab device. If the overall timeout is provided, the
44                test will try to fail early to save some time for log collection
45                from the DUT.
46
47        @raises AutoservRunError if the suite does not complete successfully.
48        """
49        self._host = host
50
51        self._maybe_clear_devserver_cache(clear_devserver_cache)
52        # Fetch the board of the DUT's assigned to this Moblab. There should
53        # only be one type.
54        try:
55            dut = host.afe.get_hosts()[0]
56        except IndexError:
57            raise error.TestFail('All hosts for this MobLab are down. Please '
58                                 'request the lab admins to take a look.')
59
60        labels = labellib.LabelsMapping(dut.labels)
61        board = labels['board']
62
63        if not target_build:
64            stable_version_map = host.afe.get_stable_version_map(
65                    host.afe.CROS_IMAGE_TYPE)
66            target_build = stable_version_map.get_image_name(board)
67
68        logging.info('Running suite: %s.', suite_name)
69        cmd = ("%s/site_utils/run_suite.py --pool='' --board=%s --build=%s "
70               "--suite_name=%s --retry=True " "--max_retries=%d" %
71               (moblab_host.AUTOTEST_INSTALL_DIR, board, target_build,
72                suite_name, moblab_suite_max_retries))
73        cmd, run_suite_timeout_s = self._append_run_suite_timeout(
74                cmd,
75                test_timeout_hint_m,
76        )
77
78        logging.debug('Run suite command: %s', cmd)
79        try:
80            result = host.run_as_moblab(cmd, timeout=run_suite_timeout_s)
81        except error.AutoservRunError as e:
82            if _is_run_suite_error_critical(e.result_obj.exit_status):
83                raise
84        else:
85            logging.debug('Suite Run Output:\n%s', result.stdout)
86            # Cache directory can contain large binaries like CTS/CTS zip files
87            # no need to offload those in the results.
88            # The cache is owned by root user
89            host.run('rm -fR /mnt/moblab/results/shared/cache',
90                      timeout=600)
91
92    def _append_run_suite_timeout(self, cmd, test_timeout_hint_m):
93        """Modify given run_suite command with timeout.
94
95        @param cmd: run_suite command str.
96        @param test_timeout_hint_m: (int) timeout for the test, or None.
97        @return cmd, run_suite_timeout_s: cmd is the updated command str,
98                run_suite_timeout_s is the timeout to use for the run_suite
99                call, in seconds.
100        """
101        if test_timeout_hint_m is None:
102            return cmd, 10800
103
104        # Arguments passed in via test_args may be all str, depending on how
105        # they're passed in.
106        test_timeout_hint_m = int(test_timeout_hint_m)
107        elasped_m = self.elapsed.total_seconds() / 60
108        run_suite_timeout_m = (
109                test_timeout_hint_m - elasped_m - _CLEANUP_TIME_M)
110        logging.info('Overall test timeout hint provided (%d minutes)',
111                     test_timeout_hint_m)
112        logging.info('%d minutes have already elasped', elasped_m)
113        logging.info(
114                'Keeping %d minutes for cleanup, will allow %d minutes for '
115                'the suite to run.', _CLEANUP_TIME_M, run_suite_timeout_m)
116        cmd += ' --timeout_mins %d' % run_suite_timeout_m
117        return cmd, run_suite_timeout_m * 60
118
119    def _maybe_clear_devserver_cache(self, clear_devserver_cache):
120        # When passed in via test_args, all arguments are str
121        if not isinstance(clear_devserver_cache, bool):
122            clear_devserver_cache = (clear_devserver_cache.lower() == 'true')
123        if clear_devserver_cache:
124            self._host.run('rm -rf %s/*' % _MOBLAB_IMAGE_STORAGE)
125
126
127def _is_run_suite_error_critical(return_code):
128    # We can't actually import run_suite here because importing run_suite pulls
129    # in certain MySQLdb dependencies that fail to load in the context of a
130    # test.
131    # OTOH, these return codes are unlikely to change because external users /
132    # builders depend on them.
133    return return_code not in (
134            0,  # run_suite.RETURN_CODES.OK
135            2,  # run_suite.RETURN_CODES.WARNING
136    )
137