blob: 20e0995dc441d9a9e9acf6eba58b971fa2d7307f [file] [log] [blame]
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -07001# Copyright 2018 The Chromium 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
5"""Run the skylab staging test.
6
7This script runs a suite of autotest tests and some other sanity checks against
8a given Skylab instance. If all sanity checks and tests have the expected
9results, the script exits with success.
10
11This script is intended to be used for the Autotest staging lab's test_push.
12This script does not update any software before running the tests (i.e. caller
13is responsible for setting up the staging lab with the correct software
14beforehand), nor does it update any software refs on success (i.e. caller is
15responsible for blessing the newer version of software as needed).
16"""
17
18from __future__ import absolute_import
19from __future__ import division
20from __future__ import print_function
21
22import argparse
Alex Zamorzaevf0573b52019-04-05 12:07:59 -070023import collections
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -070024import logging
25import sys
26import time
27
28from lucifer import autotest
29from lucifer import loglib
30from skylab_staging import errors
31from skylab_staging import swarming
32
33_METRICS_PREFIX = 'chromeos/autotest/test_push/skylab'
34_POLLING_INTERVAL_S = 10
35_WAIT_FOR_DUTS_TIMEOUT_S = 20 * 60
36
Xixuan Wu51f41d82018-08-10 15:19:07 -070037# Dictionary of test results expected in suite:skylab_staging_test.
Alex Zamorzaevf0573b52019-04-05 12:07:59 -070038_EXPECTED_TEST_RESULTS = {'login_LoginSuccess.*': ['GOOD'],
39 'provision_AutoUpdate.double': ['GOOD'],
Alex Zamorzaev9033aca2019-04-18 14:08:19 -070040 'dummy_Pass$': ['GOOD'],
41 'dummy_Pass.actionable$': ['GOOD'],
Alex Zamorzaev0891e4e2019-04-23 16:42:19 -070042 'dummy_Pass.bluetooth$': ['GOOD'],
Alex Zamorzaev9033aca2019-04-18 14:08:19 -070043 # ssp and nossp.
44 'dummy_PassServer$': ['GOOD', 'GOOD'],
45 # The entire dummy_Fail test is retried.
Alex Zamorzaev0756d172019-04-15 17:29:35 -070046 'dummy_Fail.Fail$': ['FAIL', 'FAIL'],
47 'dummy_Fail.Error$': ['ERROR', 'ERROR'],
48 'dummy_Fail.Warn$': ['WARN', 'WARN'],
49 'dummy_Fail.NAError$': ['TEST_NA',
50 'TEST_NA'],
51 'dummy_Fail.Crash$': ['GOOD', 'GOOD'],
Alex Zamorzaev9033aca2019-04-18 14:08:19 -070052 'tast.*': ['GOOD'],
Alex Zamorzaevf0573b52019-04-05 12:07:59 -070053 }
Xixuan Wu51f41d82018-08-10 15:19:07 -070054
55# Some test could be missing from the test results for various reasons. Add
56# such test in this list and explain the reason.
57_IGNORED_TESTS = [
Xixuan Wu51f41d82018-08-10 15:19:07 -070058]
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -070059
60_logger = logging.getLogger(__name__)
61
62
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -070063def main():
64 """Entry point of test_push."""
65 autotest.monkeypatch()
66 metrics = autotest.chromite_load('metrics')
67 ts_mon_config = autotest.chromite_load('ts_mon_config')
68
69 parser = _get_parser()
70 loglib.add_logging_options(parser)
71 args = parser.parse_args()
72 loglib.configure_logging_with_args(parser, args)
73
74 with ts_mon_config.SetupTsMonGlobalState(service_name='skylab_test_push',
75 indirect=True):
76 success = False
77 try:
78 with metrics.SecondsTimer(_METRICS_PREFIX + '/durations/total',
79 add_exception_field=True):
80 _run_test_push(args)
81 success = True
82 finally:
83 metrics.Counter(_METRICS_PREFIX + '/tick').increment(
84 fields={'success': success})
85
86def _get_parser():
87 parser = argparse.ArgumentParser(
88 description='Run test_push against Skylab instance.')
89 parser.add_argument(
90 '--swarming-url',
91 required=True,
92 help='Full URL to the Swarming instance to use',
93 )
94 parser.add_argument(
95 '--swarming-cli',
96 required=True,
97 help='Path to the Swarming cli tool.',
98 )
99 # TODO(crbug.com/867969) Use model instead of board once skylab inventory has
100 # model information.
101 parser.add_argument(
102 '--dut-board',
103 required=True,
104 help='Label board of the DUTs to use for testing',
105 )
106 parser.add_argument(
107 '--dut-pool',
108 required=True,
109 choices=('DUT_POOL_CQ', 'DUT_POOL_BVT', 'DUT_POOL_SUITES'),
110 help='Label pool of the DUTs to use for testing',
111 )
112 parser.add_argument(
113 '--build',
114 required=True,
115 help='ChromeOS build to use for provisioning'
116 ' (e.g.: gandolf-release/R54-8743.25.0).',
117 )
118 parser.add_argument(
119 '--timeout-mins',
120 type=int,
121 required=True,
122 help='(Optional) Overall timeout for the test_push. On timeout, test_push'
123 ' attempts to abort any in-flight test suites before quitting.',
124 )
125 parser.add_argument(
126 '--num-min-duts',
127 type=int,
128 help='Minimum number of Ready DUTs required for test suite.',
129 )
130 parser.add_argument(
131 '--service-account-json',
132 default=None,
133 help='(Optional) Path to the service account credentials file to'
134 ' authenticate with Swarming service.',
135 )
136 return parser
137
138
139def _run_test_push(args):
140 """Meat of the test_push flow."""
141 metrics = autotest.chromite_load('metrics')
142
143 deadline = time.time() + (args.timeout_mins * 60)
144 swclient = swarming.Client(args.swarming_cli, args.swarming_url,
145 args.service_account_json)
146 if args.num_min_duts:
147 _ensure_duts_ready(
148 swclient,
149 args.dut_board,
150 args.dut_pool,
151 args.num_min_duts,
152 min(deadline - time.time(), _WAIT_FOR_DUTS_TIMEOUT_S),
153 )
154
155 # Just like the builders, first run a provision suite to provision required
156 # DUTs, then run the actual suite.
157 with metrics.SecondsTimer(_METRICS_PREFIX + '/durations/provision_suite',
158 add_exception_field=True):
159 task_id = swclient.trigger_suite(
160 args.dut_board,
161 args.dut_pool,
162 args.build,
163 'provision',
164 deadline - time.time(),
165 )
166 _logger.info('Triggered provision suite. Task id: %s', task_id)
167 swclient.wait_for_suite(
168 task_id,
169 args.dut_board,
170 args.dut_pool,
171 args.build,
172 'provision',
173 deadline - time.time(),
174 )
175 _logger.info('Finished provision suite.')
176
177 with metrics.SecondsTimer(_METRICS_PREFIX + '/durations/push_to_prod_suite',
178 add_exception_field=True):
179 task_id = swclient.trigger_suite(
180 args.dut_board,
181 args.dut_pool,
182 args.build,
Prathmesh Prabhu8bd18922018-08-16 11:08:04 -0700183 'skylab_staging_test',
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -0700184 deadline - time.time(),
185 )
Prathmesh Prabhu8bd18922018-08-16 11:08:04 -0700186 _logger.info('Triggered skylab_staging_test suite. Task id: %s', task_id)
Xixuan Wu5dcca502019-01-07 12:24:37 -0800187 _verify_suite_creation(swclient, task_id)
Xixuan Wu51f41d82018-08-10 15:19:07 -0700188 _logger.info('Check push_to_prod suite on: \n %s',
189 swclient.task_url(task_id))
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -0700190 swclient.wait_for_suite(
191 task_id,
192 args.dut_board,
193 args.dut_pool,
194 args.build,
Prathmesh Prabhu8bd18922018-08-16 11:08:04 -0700195 'skylab_staging_test',
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -0700196 deadline - time.time(),
197 )
Prathmesh Prabhu8bd18922018-08-16 11:08:04 -0700198 _logger.info('Finished skylab_staging_test suite.')
Xixuan Wu51f41d82018-08-10 15:19:07 -0700199
200 _verify_test_results(task_id, _EXPECTED_TEST_RESULTS)
201
202
Xixuan Wu5dcca502019-01-07 12:24:37 -0800203def _verify_suite_creation(swclient, task_id):
204 """Verify the suite is created successfully."""
205 result = swclient.query('task/%s/result' % task_id, [])
206 if result['state'] != 'COMPLETED' or result['failure']:
207 raise errors.TestPushError('Suite task %s is not successfully created.'
208 % task_id)
209
210
Xixuan Wu51f41d82018-08-10 15:19:07 -0700211def _verify_test_results(task_id, expected_results):
212 """Verify if test results are expected."""
213 _logger.info('Comparing test results...')
214 test_views = _get_test_views(task_id)
215 available_views = [v for v in test_views if _view_is_preserved(v)]
216 logging.debug('Test results:')
217 for v in available_views:
218 logging.debug('%s%s', v['test_name'].ljust(30), v['status'])
219
220 summary = _verify_and_summarize(available_views, expected_results)
221 if summary:
222 logging.error('\n'.join(summary))
223 raise errors.TestPushError('Test results are not consistent with '
224 'expected results')
225
226
227def _get_test_views(task_id):
228 """Retrieve test views from TKO for skylab task id."""
229 tko_db = autotest.load('tko.db')
230 db = tko_db.db()
231 return db.get_child_tests_by_parent_task_id(task_id)
232
233
234def _view_is_preserved(view):
235 """Detect whether to keep the test view for further comparison."""
236 job_status = autotest.load('server.cros.dynamic_suite.job_status')
237 return (job_status.view_is_relevant(view) and
238 (not job_status.view_is_for_suite_job(view)))
239
240
241def _verify_and_summarize(available_views, expected_results):
242 """Verify and generate summaries for test_push results."""
243 test_push_common = autotest.load('site_utils.test_push_common')
Alex Zamorzaevf0573b52019-04-05 12:07:59 -0700244 views = collections.defaultdict(list)
245 for view in available_views:
246 views[view['test_name']].append(view['status'])
Xixuan Wu51f41d82018-08-10 15:19:07 -0700247 return test_push_common.summarize_push(views, expected_results,
248 _IGNORED_TESTS)
Prathmesh Prabhudd9071f2018-07-25 12:36:07 -0700249
250
251def _ensure_duts_ready(swclient, board, pool, min_duts, timeout_s):
252 """Ensure that at least num_duts are in the ready dut_state."""
253 start_time = time.time()
254 while True:
255 _logger.debug('Checking whether %d DUTs are available', min_duts)
256 num_duts = swclient.num_ready_duts(board, pool)
257 if num_duts >= min_duts:
258 _logger.info(
259 '%d available DUTs satisfy the minimum requirement of %d DUTs',
260 num_duts, min_duts,
261 )
262 return
263 if time.time() - start_time > timeout_s:
264 raise errors.TestPushError(
265 'Could not find %d ready DUTs with (board:%s, pool:%s) within %d'
266 ' seconds' % (min_duts, board, pool, timeout_s)
267 )
268 time.sleep(_POLLING_INTERVAL_S)
269
270
271if __name__ == '__main__':
272 sys.exit(main())