blob: af579fbc95d60c9588f9087a05cae4f7a480ad96 [file] [log] [blame]
Dan Shi7e04fa82013-07-25 15:08:48 -07001#!/usr/bin/python
2#
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Tool to validate code in prod branch before pushing to lab.
8
9The script runs push_to_prod suite to verify code in prod branch is ready to be
10pushed. Link to design document:
11https://docs.google.com/a/google.com/document/d/1JMz0xS3fZRSHMpFkkKAL_rxsdbNZomhHbC3B8L71uuI/edit
12
13To verify if prod branch can be pushed to lab, run following command in
14chromeos-autotest.cbf server:
Michael Liang52d9f1f2014-06-17 15:01:24 -070015/usr/local/autotest/site_utils/test_push.py -e someone@company.com
Dan Shi7e04fa82013-07-25 15:08:48 -070016
Shuqian Zhaof3a114c2016-09-21 11:02:15 -070017The script uses latest gandof stable build as test build by default.
Dan Shi7e04fa82013-07-25 15:08:48 -070018
19"""
20
21import argparse
Shuqian Zhao1f311c02016-09-01 19:30:54 -070022import ast
Shuqian Zhao7b2daea2016-10-25 13:31:06 -070023from contextlib import contextmanager
Dan Shi7e04fa82013-07-25 15:08:48 -070024import getpass
Dan Shief1a5c02015-04-07 17:37:09 -070025import multiprocessing
Dan Shi7e04fa82013-07-25 15:08:48 -070026import os
27import re
28import subprocess
29import sys
Dan Shief1a5c02015-04-07 17:37:09 -070030import time
31import traceback
Dan Shi7e04fa82013-07-25 15:08:48 -070032import urllib2
33
34import common
Dan Shia8da7602014-05-09 15:18:15 -070035try:
36 from autotest_lib.frontend import setup_django_environment
37 from autotest_lib.frontend.afe import models
Shuqian Zhao327b6952016-09-12 10:42:03 -070038 from autotest_lib.frontend.afe import rpc_utils
Dan Shia8da7602014-05-09 15:18:15 -070039except ImportError:
40 # Unittest may not have Django database configured and will fail to import.
41 pass
Dan Shi5fa602c2015-03-26 17:54:13 -070042from autotest_lib.client.common_lib import global_config
Shuqian Zhao327b6952016-09-12 10:42:03 -070043from autotest_lib.client.common_lib import priorities
Shuqian Zhao6fc7bf42016-12-11 19:10:36 -080044from autotest_lib.client.common_lib.cros import retry
Dan Shi7e04fa82013-07-25 15:08:48 -070045from autotest_lib.server import site_utils
Shuqian Zhao327b6952016-09-12 10:42:03 -070046from autotest_lib.server import utils
Dan Shi47d32882014-12-22 16:25:05 -080047from autotest_lib.server.cros import provision
Dan Shi7e04fa82013-07-25 15:08:48 -070048from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Dan Shi5fa602c2015-03-26 17:54:13 -070049from autotest_lib.site_utils import gmail_lib
Dan Shi47d32882014-12-22 16:25:05 -080050from autotest_lib.site_utils.suite_scheduler import constants
Dan Shi7e04fa82013-07-25 15:08:48 -070051
Shuqian Zhao56969542017-05-30 12:56:57 -070052try:
53 from chromite.lib import metrics
54 from chromite.lib import ts_mon_config
55except ImportError:
56 metrics = site_utils.metrics_mock
57 ts_mon_config = site_utils.metrics_mock
58
Shuqian Zhao7b2daea2016-10-25 13:31:06 -070059AUTOTEST_DIR=common.autotest_dir
Dan Shi7e04fa82013-07-25 15:08:48 -070060CONFIG = global_config.global_config
61
Dan Shiefd403e2016-02-03 11:37:02 -080062AFE = frontend_wrappers.RetryingAFE(timeout_min=0.5, delay_sec=2)
Shuqian Zhao327b6952016-09-12 10:42:03 -070063TKO = frontend_wrappers.RetryingTKO(timeout_min=0.1, delay_sec=10)
Dan Shiefd403e2016-02-03 11:37:02 -080064
Dan Shi7e04fa82013-07-25 15:08:48 -070065MAIL_FROM = 'chromeos-test@google.com'
Shuqian Zhao12861662016-08-31 19:23:17 -070066BUILD_REGEX = 'R[\d]+-[\d]+\.[\d]+\.[\d]+'
Dan Shi7e04fa82013-07-25 15:08:48 -070067RUN_SUITE_COMMAND = 'run_suite.py'
68PUSH_TO_PROD_SUITE = 'push_to_prod'
Jakob Juelich8f143912014-10-10 14:08:05 -070069DUMMY_SUITE = 'dummy'
Dan Shi81ddc422016-09-09 13:58:31 -070070TESTBED_SUITE = 'testbed_push'
Shuqian Zhao8ac22e82016-09-22 14:26:18 -070071# TODO(shuqianz): Dynamically get android build after crbug.com/646068 fixed
xixuan2d668582016-06-10 14:02:32 -070072DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB = 30
Shuqian Zhao12861662016-08-31 19:23:17 -070073IMAGE_BUCKET = CONFIG.get_config_value('CROS', 'image_storage_server')
Shuqian Zhao8ac22e82016-09-22 14:26:18 -070074DEFAULT_EMAIL = CONFIG.get_config_value(
xixuan9307e622017-02-03 20:01:01 -080075 'SCHEDULER', 'notify_email', type=list, default=[])
Dan Shid3778372017-03-17 18:01:59 +000076DEFAULT_NUM_DUTS = "{'gandof': 4, 'quawks': 2, 'testbed': 1}"
Dan Shi7e04fa82013-07-25 15:08:48 -070077
Fang Deng6dddf602014-04-17 17:01:47 -070078SUITE_JOB_START_INFO_REGEX = ('^.*Created suite job:.*'
79 'tab_id=view_job&object_id=(\d+)$')
Dan Shi7e04fa82013-07-25 15:08:48 -070080
81# Dictionary of test results keyed by test name regular expression.
82EXPECTED_TEST_RESULTS = {'^SERVER_JOB$': 'GOOD',
83 # This is related to dummy_Fail/control.dependency.
84 'dummy_Fail.dependency$': 'TEST_NA',
Dan Shidc9eb172014-12-09 16:05:02 -080085 'login_LoginSuccess.*': 'GOOD',
Dan Shi47d32882014-12-22 16:25:05 -080086 'provision_AutoUpdate.double': 'GOOD',
Dan Shi7e04fa82013-07-25 15:08:48 -070087 'dummy_Pass.*': 'GOOD',
88 'dummy_Fail.Fail$': 'FAIL',
89 'dummy_Fail.RetryFail$': 'FAIL',
90 'dummy_Fail.RetrySuccess': 'GOOD',
91 'dummy_Fail.Error$': 'ERROR',
92 'dummy_Fail.Warn$': 'WARN',
93 'dummy_Fail.NAError$': 'TEST_NA',
94 'dummy_Fail.Crash$': 'GOOD',
95 }
96
Jakob Juelich8f143912014-10-10 14:08:05 -070097EXPECTED_TEST_RESULTS_DUMMY = {'^SERVER_JOB$': 'GOOD',
98 'dummy_Pass.*': 'GOOD',
99 'dummy_Fail.Fail': 'FAIL',
100 'dummy_Fail.Warn': 'WARN',
101 'dummy_Fail.Crash': 'GOOD',
102 'dummy_Fail.Error': 'ERROR',
103 'dummy_Fail.NAError': 'TEST_NA',}
104
Dan Shi81ddc422016-09-09 13:58:31 -0700105EXPECTED_TEST_RESULTS_TESTBED = {'^SERVER_JOB$': 'GOOD',
106 'testbed_DummyTest': 'GOOD',}
107
Shuqian Zhao327b6952016-09-12 10:42:03 -0700108EXPECTED_TEST_RESULTS_POWERWASH = {'platform_Powerwash': 'GOOD',
109 'SERVER_JOB': 'GOOD'}
110
Dan Shi7e04fa82013-07-25 15:08:48 -0700111URL_HOST = CONFIG.get_config_value('SERVER', 'hostname', type=str)
112URL_PATTERN = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str)
113
Dan Shidc9eb172014-12-09 16:05:02 -0800114# Some test could be missing from the test results for various reasons. Add
115# such test in this list and explain the reason.
116IGNORE_MISSING_TESTS = [
117 # For latest build, npo_test_delta does not exist.
118 'autoupdate_EndToEndTest.npo_test_delta.*',
119 # For trybot build, nmo_test_delta does not exist.
120 'autoupdate_EndToEndTest.nmo_test_delta.*',
121 # Older build does not have login_LoginSuccess test in push_to_prod suite.
122 # TODO(dshi): Remove following lines after R41 is stable.
123 'login_LoginSuccess']
124
Dan Shi7e04fa82013-07-25 15:08:48 -0700125# Save all run_suite command output.
Shuqian Zhao7b682192016-09-16 14:38:41 -0700126manager = multiprocessing.Manager()
127run_suite_output = manager.list()
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700128all_suite_ids = manager.list()
Shuqian Zhao7b2daea2016-10-25 13:31:06 -0700129# A dict maps the name of the updated repos and the path of them.
130UPDATED_REPOS = {'autotest': AUTOTEST_DIR,
131 'chromite': '%s/site-packages/chromite/' % AUTOTEST_DIR}
Shuqian Zhao80d32712016-11-11 16:37:36 -0800132PUSH_USER = 'chromeos-test-lab'
Dan Shi7e04fa82013-07-25 15:08:48 -0700133
134class TestPushException(Exception):
135 """Exception to be raised when the test to push to prod failed."""
136 pass
137
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700138
Shuqian Zhao6fc7bf42016-12-11 19:10:36 -0800139@retry.retry(TestPushException, timeout_min=5, delay_sec=30)
Shuqian Zhaoa6cf66b2017-03-03 12:08:57 -0800140def check_dut_inventory(required_num_duts, pool):
141 """Check DUT inventory for each board in the pool specified..
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700142
Shuqian Zhaoa7fa5b62016-11-18 11:13:16 -0800143 @param required_num_duts: a dict specifying the number of DUT each platform
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700144 requires in order to finish push tests.
Shuqian Zhaoa6cf66b2017-03-03 12:08:57 -0800145 @param pool: the pool used by test_push.
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700146 @raise TestPushException: if number of DUTs are less than the requirement.
147 """
Shuqian Zhao6fc7bf42016-12-11 19:10:36 -0800148 print 'Checking DUT inventory...'
Shuqian Zhaoa6cf66b2017-03-03 12:08:57 -0800149 pool_label = constants.Labels.POOL_PREFIX + pool
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700150 hosts = AFE.run('get_hosts', status='Ready', locked=False)
Shuqian Zhaoa6cf66b2017-03-03 12:08:57 -0800151 hosts = [h for h in hosts if pool_label in h.get('labels', [])]
Shuqian Zhaoa7fa5b62016-11-18 11:13:16 -0800152 platforms = [host['platform'] for host in hosts]
153 current_inventory = {p : platforms.count(p) for p in platforms}
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700154 error_msg = ''
Shuqian Zhaoa7fa5b62016-11-18 11:13:16 -0800155 for platform, req_num in required_num_duts.items():
156 curr_num = current_inventory.get(platform, 0)
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700157 if curr_num < req_num:
Shuqian Zhaoa6cf66b2017-03-03 12:08:57 -0800158 error_msg += ('\nRequire %d %s DUTs in pool: %s, only %d are Ready'
159 ' now' % (req_num, platform, pool, curr_num))
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700160 if error_msg:
161 raise TestPushException('Not enough DUTs to run push tests. %s' %
162 error_msg)
163
164
Shuqian Zhao327b6952016-09-12 10:42:03 -0700165def powerwash_dut_to_test_repair(hostname, timeout):
166 """Powerwash dut to test repair workflow.
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800167
168 @param hostname: hostname of the dut.
Shuqian Zhao327b6952016-09-12 10:42:03 -0700169 @param timeout: seconds of the powerwash test to hit timeout.
170 @raise TestPushException: if DUT fail to run the test.
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800171 """
Shuqian Zhao327b6952016-09-12 10:42:03 -0700172 t = models.Test.objects.get(name='platform_Powerwash')
173 c = utils.read_file(os.path.join(common.autotest_dir, t.path))
174 job_id = rpc_utils.create_job_common(
175 'powerwash', priority=priorities.Priority.SUPER,
176 control_type='Server', control_file=c, hosts=[hostname])
177
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700178 end = time.time() + timeout
Shuqian Zhao327b6952016-09-12 10:42:03 -0700179 while not TKO.get_job_test_statuses_from_db(job_id):
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700180 if time.time() >= end:
181 AFE.run('abort_host_queue_entries', job=job_id)
Shuqian Zhao327b6952016-09-12 10:42:03 -0700182 raise TestPushException(
Shuqian Zhaoe83a78c2016-09-16 15:01:25 -0700183 'Powerwash test on %s timeout after %ds, abort it.' %
184 (hostname, timeout))
Shuqian Zhao327b6952016-09-12 10:42:03 -0700185 time.sleep(10)
186 verify_test_results(job_id, EXPECTED_TEST_RESULTS_POWERWASH)
187 # Kick off verify, verify will fail and a repair should be triggered.
188 AFE.reverify_hosts(hostnames=[hostname])
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800189
190
Shuqian Zhao06deae02017-02-28 09:55:59 -0800191def reverify_all_push_duts():
192 """Reverify all the push DUTs."""
193 print 'Reverifying all DUTs.'
194 hosts = [h.hostname for h in AFE.get_hosts()]
Shuqian Zhaod2a99f02016-09-22 13:31:30 -0700195 AFE.reverify_hosts(hostnames=hosts)
196
197
Kevin Chenge691ce92016-12-15 12:17:13 -0800198def get_default_build(board='gandof', server='chromeos-autotest.hot'):
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700199 """Get the default build to be used for test.
200
Dan Shi8df9c002016-03-08 15:37:39 -0800201 @param board: Name of board to be tested, default is gandof.
202 @return: Build to be tested, e.g., gandof-release/R36-5881.0.0
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700203 """
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700204 build = None
Kevin Chenge691ce92016-12-15 12:17:13 -0800205 cmd = ('%s/cli/atest stable_version list --board=%s -w %s' %
206 (AUTOTEST_DIR, board, server))
Shuqian Zhao12861662016-08-31 19:23:17 -0700207 result = subprocess.check_output(cmd, shell=True).strip()
208 build = re.search(BUILD_REGEX, result)
209 if build:
210 return '%s-release/%s' % (board, build.group(0))
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700211
Shuqian Zhao12861662016-08-31 19:23:17 -0700212 # If fail to get stable version from cautotest, use that defined in config
Dan Shi5ba5d2e2014-05-09 13:47:00 -0700213 build = CONFIG.get_config_value('CROS', 'stable_cros_version')
214 return '%s-release/%s' % (board, build)
215
Dan Shi7e04fa82013-07-25 15:08:48 -0700216def parse_arguments():
217 """Parse arguments for test_push tool.
218
219 @return: Parsed arguments.
220
221 """
222 parser = argparse.ArgumentParser()
Dan Shi8df9c002016-03-08 15:37:39 -0800223 parser.add_argument('-b', '--board', dest='board', default='gandof',
224 help='Default is gandof.')
Jakob Juelich8f143912014-10-10 14:08:05 -0700225 parser.add_argument('-sb', '--shard_board', dest='shard_board',
226 default='quawks',
227 help='Default is quawks.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700228 parser.add_argument('-i', '--build', dest='build', default=None,
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700229 help='Default is the latest stale build of given '
230 'board. Must be a stable build, otherwise AU test '
231 'will fail. (ex: gandolf-release/R54-8743.25.0)')
Jakob Juelich8f143912014-10-10 14:08:05 -0700232 parser.add_argument('-si', '--shard_build', dest='shard_build', default=None,
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700233 help='Default is the latest stable build of given '
234 'board. Must be a stable build, otherwise AU test '
Jakob Juelich8f143912014-10-10 14:08:05 -0700235 'will fail.')
Kevin Chenge691ce92016-12-15 12:17:13 -0800236 parser.add_argument('-w', '--web', default='chromeos-autotest.hot',
237 help='Specify web server to grab stable version from.')
Dan Shi81ddc422016-09-09 13:58:31 -0700238 parser.add_argument('-ab', '--android_board', dest='android_board',
Shuqian Zhao8ac22e82016-09-22 14:26:18 -0700239 default='shamu-2', help='Android board to test.')
Dan Shi81ddc422016-09-09 13:58:31 -0700240 parser.add_argument('-ai', '--android_build', dest='android_build',
241 help='Android build to test.')
Dan Shi7e04fa82013-07-25 15:08:48 -0700242 parser.add_argument('-p', '--pool', dest='pool', default='bvt')
243 parser.add_argument('-u', '--num', dest='num', type=int, default=3,
244 help='Run on at most NUM machines.')
xixuan9307e622017-02-03 20:01:01 -0800245 parser.add_argument('-e', '--email', nargs='+', dest='email',
246 default=DEFAULT_EMAIL,
Dan Shi7e04fa82013-07-25 15:08:48 -0700247 help='Email address for the notification to be sent to '
248 'after the script finished running.')
Shuqian Zhaod4864772015-08-06 09:46:22 -0700249 parser.add_argument('-t', '--timeout_min', dest='timeout_min', type=int,
xixuan2d668582016-06-10 14:02:32 -0700250 default=DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB,
Shuqian Zhaod4864772015-08-06 09:46:22 -0700251 help='Time in mins to wait before abort the jobs we '
252 'are waiting on. Only for the asynchronous suites '
253 'triggered by create_and_return flag.')
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700254 parser.add_argument('-ud', '--num_duts', dest='num_duts',
255 default=DEFAULT_NUM_DUTS,
256 help="String of dict that indicates the required number"
257 " of DUTs for each board. E.g {'gandof':4}")
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700258 parser.add_argument('-c', '--continue_on_failure', action='store_true',
259 dest='continue_on_failure',
260 help='All tests continue to run when there is failure')
Dan Shi7e04fa82013-07-25 15:08:48 -0700261
262 arguments = parser.parse_args(sys.argv[1:])
263
Shuqian Zhaof3a114c2016-09-21 11:02:15 -0700264 # Get latest stable build as default build.
Dan Shi7e04fa82013-07-25 15:08:48 -0700265 if not arguments.build:
Kevin Chenge691ce92016-12-15 12:17:13 -0800266 arguments.build = get_default_build(arguments.board, arguments.web)
Jakob Juelich8f143912014-10-10 14:08:05 -0700267 if not arguments.shard_build:
Kevin Chenge691ce92016-12-15 12:17:13 -0800268 arguments.shard_build = get_default_build(arguments.shard_board,
269 arguments.web)
Dan Shi7e04fa82013-07-25 15:08:48 -0700270
Shuqian Zhao1f311c02016-09-01 19:30:54 -0700271 arguments.num_duts = ast.literal_eval(arguments.num_duts)
272
Dan Shi7e04fa82013-07-25 15:08:48 -0700273 return arguments
274
275
Shuqian Zhaod4864772015-08-06 09:46:22 -0700276def do_run_suite(suite_name, arguments, use_shard=False,
Dan Shi81ddc422016-09-09 13:58:31 -0700277 create_and_return=False, testbed_test=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700278 """Call run_suite to run a suite job, and return the suite job id.
279
280 The script waits the suite job to finish before returning the suite job id.
281 Also it will echo the run_suite output to stdout.
282
283 @param suite_name: Name of a suite, e.g., dummy.
284 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700285 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700286 @param create_and_return: If True, run_suite just creates the suite, print
287 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700288 @param testbed_test: True to run testbed test. Default is False.
Jakob Juelich8f143912014-10-10 14:08:05 -0700289
Dan Shi7e04fa82013-07-25 15:08:48 -0700290 @return: Suite job ID.
291
292 """
Dan Shi81ddc422016-09-09 13:58:31 -0700293 if use_shard and not testbed_test:
Jakob Juelich8f143912014-10-10 14:08:05 -0700294 board = arguments.shard_board
295 build = arguments.shard_build
Dan Shi81ddc422016-09-09 13:58:31 -0700296 elif testbed_test:
297 board = arguments.android_board
298 build = arguments.android_build
299 else:
300 board = arguments.board
301 build = arguments.build
Jakob Juelich8f143912014-10-10 14:08:05 -0700302
Dan Shi47d32882014-12-22 16:25:05 -0800303 # Remove cros-version label to force provision.
Shuqian Zhao7a49f1b2016-10-24 16:48:04 -0700304 hosts = AFE.get_hosts(label=constants.Labels.BOARD_PREFIX+board,
305 locked=False)
Dan Shi47d32882014-12-22 16:25:05 -0800306 for host in hosts:
Dan Shi81ddc422016-09-09 13:58:31 -0700307 labels_to_remove = [
308 l for l in host.labels
309 if (l.startswith(provision.CROS_VERSION_PREFIX) or
310 l.startswith(provision.TESTBED_BUILD_VERSION_PREFIX))]
311 if labels_to_remove:
312 AFE.run('host_remove_labels', id=host.id, labels=labels_to_remove)
Dan Shi47d32882014-12-22 16:25:05 -0800313
Shuqian Zhaod01fad02016-11-18 10:00:22 -0800314 # Test repair work flow on shards, powerwash test will timeout after 7m.
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800315 if use_shard and not create_and_return:
Shuqian Zhaod01fad02016-11-18 10:00:22 -0800316 powerwash_dut_to_test_repair(host.hostname, timeout=420)
Kevin Cheng6e4c2642015-12-11 09:45:57 -0800317
Dan Shief1a5c02015-04-07 17:37:09 -0700318 current_dir = os.path.dirname(os.path.realpath(__file__))
319 cmd = [os.path.join(current_dir, RUN_SUITE_COMMAND),
Dan Shi7e04fa82013-07-25 15:08:48 -0700320 '-s', suite_name,
Jakob Juelich8f143912014-10-10 14:08:05 -0700321 '-b', board,
322 '-i', build,
Dan Shi7e04fa82013-07-25 15:08:48 -0700323 '-p', arguments.pool,
Shuqian Zhao178ac012016-06-03 15:08:52 -0700324 '-u', str(arguments.num)]
Shuqian Zhaod4864772015-08-06 09:46:22 -0700325 if create_and_return:
326 cmd += ['-c']
Dan Shi81ddc422016-09-09 13:58:31 -0700327 if testbed_test:
328 cmd += ['--run_prod_code']
Dan Shi7e04fa82013-07-25 15:08:48 -0700329
330 suite_job_id = None
Dan Shi7e04fa82013-07-25 15:08:48 -0700331
332 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
333 stderr=subprocess.STDOUT)
334
335 while True:
336 line = proc.stdout.readline()
337
338 # Break when run_suite process completed.
339 if not line and proc.poll() != None:
340 break
341 print line.rstrip()
342 run_suite_output.append(line.rstrip())
343
344 if not suite_job_id:
345 m = re.match(SUITE_JOB_START_INFO_REGEX, line)
346 if m and m.group(1):
347 suite_job_id = int(m.group(1))
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700348 all_suite_ids.append(suite_job_id)
Dan Shi7e04fa82013-07-25 15:08:48 -0700349
350 if not suite_job_id:
351 raise TestPushException('Failed to retrieve suite job ID.')
Dan Shia8da7602014-05-09 15:18:15 -0700352
Shuqian Zhaod4864772015-08-06 09:46:22 -0700353 # If create_and_return specified, wait for the suite to finish.
354 if create_and_return:
355 end = time.time() + arguments.timeout_min * 60
Dan Shiefd403e2016-02-03 11:37:02 -0800356 while not AFE.get_jobs(id=suite_job_id, finished=True):
Shuqian Zhaod4864772015-08-06 09:46:22 -0700357 if time.time() < end:
358 time.sleep(10)
359 else:
Dan Shiefd403e2016-02-03 11:37:02 -0800360 AFE.run('abort_host_queue_entries', job=suite_job_id)
Shuqian Zhaod4864772015-08-06 09:46:22 -0700361 raise TestPushException(
362 'Asynchronous suite triggered by create_and_return '
363 'flag has timed out after %d mins. Aborting it.' %
364 arguments.timeout_min)
365
Dan Shia8da7602014-05-09 15:18:15 -0700366 print 'Suite job %s is completed.' % suite_job_id
Dan Shi7e04fa82013-07-25 15:08:48 -0700367 return suite_job_id
368
369
Dan Shia8da7602014-05-09 15:18:15 -0700370def check_dut_image(build, suite_job_id):
371 """Confirm all DUTs used for the suite are imaged to expected build.
372
373 @param build: Expected build to be imaged.
374 @param suite_job_id: job ID of the suite job.
375 @raise TestPushException: If a DUT does not have expected build imaged.
376 """
377 print 'Checking image installed in DUTs...'
378 job_ids = [job.id for job in
379 models.Job.objects.filter(parent_job_id=suite_job_id)]
380 hqes = [models.HostQueueEntry.objects.filter(job_id=job_id)[0]
381 for job_id in job_ids]
382 hostnames = set([hqe.host.hostname for hqe in hqes])
383 for hostname in hostnames:
Prathmesh Prabhuf10f41a2017-04-21 11:52:16 -0700384 found_build = site_utils.get_build_from_afe(hostname, AFE)
385 if found_build != build:
Dan Shia8da7602014-05-09 15:18:15 -0700386 raise TestPushException('DUT is not imaged properly. Host %s has '
387 'build %s, while build %s is expected.' %
Prathmesh Prabhuf10f41a2017-04-21 11:52:16 -0700388 (hostname, found_build, build))
Dan Shia8da7602014-05-09 15:18:15 -0700389
390
Shuqian Zhaod4864772015-08-06 09:46:22 -0700391def test_suite(suite_name, expected_results, arguments, use_shard=False,
Dan Shi81ddc422016-09-09 13:58:31 -0700392 create_and_return=False, testbed_test=False):
Dan Shi7e04fa82013-07-25 15:08:48 -0700393 """Call run_suite to start a suite job and verify results.
394
395 @param suite_name: Name of a suite, e.g., dummy
396 @param expected_results: A dictionary of test name to test result.
397 @param arguments: Arguments for run_suite command.
Jakob Juelich8f143912014-10-10 14:08:05 -0700398 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700399 @param create_and_return: If True, run_suite just creates the suite, print
400 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700401 @param testbed_test: True to run testbed test. Default is False.
Dan Shi7e04fa82013-07-25 15:08:48 -0700402 """
Shuqian Zhaod4864772015-08-06 09:46:22 -0700403 suite_job_id = do_run_suite(suite_name, arguments, use_shard,
Dan Shi81ddc422016-09-09 13:58:31 -0700404 create_and_return, testbed_test)
Dan Shi7e04fa82013-07-25 15:08:48 -0700405
Dan Shia8da7602014-05-09 15:18:15 -0700406 # Confirm all DUTs used for the suite are imaged to expected build.
Jakob Juelich8f143912014-10-10 14:08:05 -0700407 # hqe.host_id for jobs running in shard is not synced back to master db,
408 # therefore, skip verifying dut build for jobs running in shard.
Dan Shi81ddc422016-09-09 13:58:31 -0700409 build_expected = (arguments.android_build if testbed_test
410 else arguments.build)
Aviv Keshetd2359122017-05-03 22:50:10 -0700411 if not use_shard and not testbed_test:
Dan Shi81ddc422016-09-09 13:58:31 -0700412 check_dut_image(build_expected, suite_job_id)
Dan Shia8da7602014-05-09 15:18:15 -0700413
Shuqian Zhao327b6952016-09-12 10:42:03 -0700414 # Verify test results are the expected results.
415 verify_test_results(suite_job_id, expected_results)
416
417
418def verify_test_results(job_id, expected_results):
419 """Verify the test results with the expected results.
420
421 @param job_id: id of the running jobs. For suite job, it is suite_job_id.
422 @param expected_results: A dictionary of test name to test result.
423 @raise TestPushException: If verify fails.
424 """
Dan Shia8da7602014-05-09 15:18:15 -0700425 print 'Comparing test results...'
Shuqian Zhao327b6952016-09-12 10:42:03 -0700426 test_views = site_utils.get_test_views_from_tko(job_id, TKO)
Dan Shi7e04fa82013-07-25 15:08:48 -0700427
428 mismatch_errors = []
429 extra_test_errors = []
430
431 found_keys = set()
Shuqian Zhao327b6952016-09-12 10:42:03 -0700432 for test_name, test_status in test_views.items():
Dan Shi7e04fa82013-07-25 15:08:48 -0700433 print "%s%s" % (test_name.ljust(30), test_status)
Dan Shi80b6ec02016-07-21 15:49:18 -0700434 # platform_InstallTestImage test may exist in old builds.
435 if re.search('platform_InstallTestImage_SERVER_JOB$', test_name):
436 continue
Dan Shi7e04fa82013-07-25 15:08:48 -0700437 test_found = False
438 for key,val in expected_results.items():
439 if re.search(key, test_name):
440 test_found = True
441 found_keys.add(key)
Dan Shi7e04fa82013-07-25 15:08:48 -0700442 if val != test_status:
443 error = ('%s Expected: [%s], Actual: [%s]' %
444 (test_name, val, test_status))
445 mismatch_errors.append(error)
446 if not test_found:
447 extra_test_errors.append(test_name)
448
449 missing_test_errors = set(expected_results.keys()) - found_keys
Dan Shidc9eb172014-12-09 16:05:02 -0800450 for exception in IGNORE_MISSING_TESTS:
451 try:
452 missing_test_errors.remove(exception)
453 except KeyError:
454 pass
455
Dan Shi7e04fa82013-07-25 15:08:48 -0700456 summary = []
457 if mismatch_errors:
458 summary.append(('Results of %d test(s) do not match expected '
459 'values:') % len(mismatch_errors))
460 summary.extend(mismatch_errors)
461 summary.append('\n')
462
463 if extra_test_errors:
464 summary.append('%d test(s) are not expected to be run:' %
465 len(extra_test_errors))
466 summary.extend(extra_test_errors)
467 summary.append('\n')
468
469 if missing_test_errors:
470 summary.append('%d test(s) are missing from the results:' %
471 len(missing_test_errors))
472 summary.extend(missing_test_errors)
473 summary.append('\n')
474
475 # Test link to log can be loaded.
Shuqian Zhao327b6952016-09-12 10:42:03 -0700476 job_name = '%s-%s' % (job_id, getpass.getuser())
Dan Shi7e04fa82013-07-25 15:08:48 -0700477 log_link = URL_PATTERN % (URL_HOST, job_name)
478 try:
479 urllib2.urlopen(log_link).read()
480 except urllib2.URLError:
481 summary.append('Failed to load page for link to log: %s.' % log_link)
482
483 if summary:
484 raise TestPushException('\n'.join(summary))
485
486
Dan Shief1a5c02015-04-07 17:37:09 -0700487def test_suite_wrapper(queue, suite_name, expected_results, arguments,
Dan Shi81ddc422016-09-09 13:58:31 -0700488 use_shard=False, create_and_return=False,
489 testbed_test=False):
Dan Shief1a5c02015-04-07 17:37:09 -0700490 """Wrapper to call test_suite. Handle exception and pipe it to parent
491 process.
492
493 @param queue: Queue to save exception to be accessed by parent process.
494 @param suite_name: Name of a suite, e.g., dummy
495 @param expected_results: A dictionary of test name to test result.
496 @param arguments: Arguments for run_suite command.
497 @param use_shard: If true, suite is scheduled for shard board.
Shuqian Zhaod4864772015-08-06 09:46:22 -0700498 @param create_and_return: If True, run_suite just creates the suite, print
499 the job id, then finish immediately.
Dan Shi81ddc422016-09-09 13:58:31 -0700500 @param testbed_test: True to run testbed test. Default is False.
Dan Shief1a5c02015-04-07 17:37:09 -0700501 """
502 try:
Shuqian Zhaod4864772015-08-06 09:46:22 -0700503 test_suite(suite_name, expected_results, arguments, use_shard,
Dan Shi81ddc422016-09-09 13:58:31 -0700504 create_and_return, testbed_test)
Dan Shief1a5c02015-04-07 17:37:09 -0700505 except:
506 # Store the whole exc_info leads to a PicklingError.
507 except_type, except_value, tb = sys.exc_info()
508 queue.put((except_type, except_value, traceback.extract_tb(tb)))
509
510
Dan Shief1a5c02015-04-07 17:37:09 -0700511def check_queue(queue):
512 """Check the queue for any exception being raised.
513
514 @param queue: Queue used to store exception for parent process to access.
515 @raise: Any exception found in the queue.
516 """
517 if queue.empty():
518 return
519 exc_info = queue.get()
520 # Raise the exception with original backtrace.
521 print 'Original stack trace of the exception:\n%s' % exc_info[2]
522 raise exc_info[0](exc_info[1])
523
524
Shuqian Zhao7b2daea2016-10-25 13:31:06 -0700525def get_head_of_repos(repos):
526 """Get HEAD of updated repos, currently are autotest and chromite repos
527
528 @param repos: a map of repo name to the path of the repo. E.g.
529 {'autotest': '/usr/local/autotest'}
530 @return: a map of repo names to the current HEAD of that repo.
531 """
532 @contextmanager
533 def cd(new_wd):
534 """Helper function to change working directory.
535
536 @param new_wd: new working directory that switch to.
537 """
538 prev_wd = os.getcwd()
539 os.chdir(os.path.expanduser(new_wd))
540 try:
541 yield
542 finally:
543 os.chdir(prev_wd)
544
545 updated_repo_heads = {}
546 for repo_name, path_to_repo in repos.iteritems():
547 with cd(path_to_repo):
548 head = subprocess.check_output('git rev-parse HEAD',
549 shell=True).strip()
550 updated_repo_heads[repo_name] = head
551 return updated_repo_heads
552
553
Shuqian Zhao80d32712016-11-11 16:37:36 -0800554def push_prod_next_branch(updated_repo_heads):
555 """push prod-next branch to the tested HEAD after all tests pass.
556
557 The push command must be ran as PUSH_USER, since only PUSH_USER has the
558 right to push branches.
559
560 @param updated_repo_heads: a map of repo names to tested HEAD of that repo.
561 """
562 # prod-next branch for every repo is downloaded under PUSH_USER home dir.
Shuqian Zhaoaa0301c2016-11-21 09:46:41 -0800563 cmd = ('cd ~/{repo}; git pull; git rebase {hash} prod-next;'
564 'git push origin prod-next')
Shuqian Zhao80d32712016-11-11 16:37:36 -0800565 run_push_as_push_user = "sudo su - %s -c '%s'" % (PUSH_USER, cmd)
566
567 for repo_name, test_hash in updated_repo_heads.iteritems():
568 push_cmd = run_push_as_push_user.format(hash=test_hash, repo=repo_name)
569 print 'Pushing %s prod-next branch to %s' % (repo_name, test_hash)
570 print subprocess.check_output(push_cmd, stderr=subprocess.STDOUT,
571 shell=True)
572
573
xixuan9307e622017-02-03 20:01:01 -0800574def send_notification_email(email_list, title, msg):
575 """Send notification to all email addresses in email list.
576
577 @param email_list: a email address list which receives notification email,
578 whose format is like:
579 [xxx@google.com, xxx@google.com, xxx@google.com,...]
580 so that users could also specify multiple email addresses by using
581 config '--email' or '-e'.
582 @param title: the title of the email to be sent.
583 @param msg: the content of the email to be sent.
584 """
585 gmail_lib.send_email(','.join(email_list), title, msg)
586
587
Shuqian Zhao56969542017-05-30 12:56:57 -0700588def _main(arguments):
589 """Running tests.
590
591 @param arguments: command line arguments.
592 """
Shuqian Zhao80d32712016-11-11 16:37:36 -0800593 updated_repo_heads = get_head_of_repos(UPDATED_REPOS)
594 updated_repo_msg = '\n'.join(
595 ['%s: %s' % (k, v) for k, v in updated_repo_heads.iteritems()])
Shuqian Zhao56969542017-05-30 12:56:57 -0700596 test_push_success = False
Dan Shi7e04fa82013-07-25 15:08:48 -0700597
598 try:
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700599 # Use daemon flag will kill child processes when parent process fails.
600 use_daemon = not arguments.continue_on_failure
Shuqian Zhao6fc7bf42016-12-11 19:10:36 -0800601 # Verify all the DUTs at the beginning of testing push.
Shuqian Zhao06deae02017-02-28 09:55:59 -0800602 reverify_all_push_duts()
Shuqian Zhao6fc7bf42016-12-11 19:10:36 -0800603 time.sleep(15) # Wait 15 secs for the verify test to start.
Shuqian Zhaoa6cf66b2017-03-03 12:08:57 -0800604 check_dut_inventory(arguments.num_duts, arguments.pool)
Dan Shief1a5c02015-04-07 17:37:09 -0700605 queue = multiprocessing.Queue()
606
607 push_to_prod_suite = multiprocessing.Process(
608 target=test_suite_wrapper,
609 args=(queue, PUSH_TO_PROD_SUITE, EXPECTED_TEST_RESULTS,
610 arguments))
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700611 push_to_prod_suite.daemon = use_daemon
Dan Shief1a5c02015-04-07 17:37:09 -0700612 push_to_prod_suite.start()
Jakob Juelich8f143912014-10-10 14:08:05 -0700613
Shuqian Zhaod4864772015-08-06 09:46:22 -0700614 # suite test with --create_and_return flag
615 asynchronous_suite = multiprocessing.Process(
616 target=test_suite_wrapper,
617 args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY,
Aviv Keshetd2359122017-05-03 22:50:10 -0700618 arguments, True, True))
Shuqian Zhao1b4ca272016-09-18 14:58:19 -0700619 asynchronous_suite.daemon = True
Shuqian Zhaod4864772015-08-06 09:46:22 -0700620 asynchronous_suite.start()
621
Dan Shi6262f282017-03-17 18:02:45 +0000622 # Test suite for testbed
623 testbed_suite = multiprocessing.Process(
624 target=test_suite_wrapper,
625 args=(queue, TESTBED_SUITE, EXPECTED_TEST_RESULTS_TESTBED,
626 arguments, False, False, True))
627 testbed_suite.daemon = use_daemon
628 testbed_suite.start()
629
Aviv Keshetd2359122017-05-03 22:50:10 -0700630 while (push_to_prod_suite.is_alive()
631 or asynchronous_suite.is_alive()
632 or testbed_suite.is_alive()):
Dan Shief1a5c02015-04-07 17:37:09 -0700633 check_queue(queue)
Dan Shief1a5c02015-04-07 17:37:09 -0700634 time.sleep(5)
635
636 check_queue(queue)
637
638 push_to_prod_suite.join()
Shuqian Zhaod4864772015-08-06 09:46:22 -0700639 asynchronous_suite.join()
Dan Shi6262f282017-03-17 18:02:45 +0000640 testbed_suite.join()
Shuqian Zhao80d32712016-11-11 16:37:36 -0800641
642 # All tests pass, push prod-next branch for UPDATED_REPOS.
Shuqian Zhaoaa0301c2016-11-21 09:46:41 -0800643 push_prod_next_branch(updated_repo_heads)
Shuqian Zhao56969542017-05-30 12:56:57 -0700644 test_push_success = True
Dan Shi7e04fa82013-07-25 15:08:48 -0700645 except Exception as e:
646 print 'Test for pushing to prod failed:\n'
647 print str(e)
Shuqian Zhao676ed6f2016-09-21 14:20:50 -0700648 # Abort running jobs when choose not to continue when there is failure.
649 if not arguments.continue_on_failure:
650 for suite_id in all_suite_ids:
651 if AFE.get_jobs(id=suite_id, finished=False):
652 AFE.run('abort_host_queue_entries', job=suite_id)
Dan Shi7e04fa82013-07-25 15:08:48 -0700653 # Send out email about the test failure.
654 if arguments.email:
xixuan9307e622017-02-03 20:01:01 -0800655 send_notification_email(
Dan Shi5fa602c2015-03-26 17:54:13 -0700656 arguments.email,
657 'Test for pushing to prod failed. Do NOT push!',
Shuqian Zhao7b2daea2016-10-25 13:31:06 -0700658 ('Test CLs of the following repos failed. Below are the '
659 'repos and the corresponding test HEAD.\n\n%s\n\n.'
Shuqian Zhao0566ee72017-02-22 10:05:56 -0800660 'Error occurred during test:\n\n%s\n\n'
Prathmesh Prabhu2877a002017-03-14 14:49:42 -0700661 'All logs have been saved to '
Aviv Keshet43bbf052017-07-10 11:39:45 -0700662 '/var/log/test_push/test_push.log on push master. '
663 'Stats on recent success rate can be found at '
664 'go/test-push-stats . Detailed '
Prathmesh Prabhu2877a002017-03-14 14:49:42 -0700665 'debugging info can be found at go/push-to-prod' %
Shuqian Zhao7b2daea2016-10-25 13:31:06 -0700666 (updated_repo_msg, str(e)) + '\n'.join(run_suite_output)))
Dan Shi7e04fa82013-07-25 15:08:48 -0700667 raise
Shuqian Zhaof794c492017-01-06 16:27:23 -0800668 finally:
Shuqian Zhao56969542017-05-30 12:56:57 -0700669 metrics.Counter('chromeos/autotest/test_push/completed').increment(
670 fields={'success': test_push_success})
Shuqian Zhaod2a99f02016-09-22 13:31:30 -0700671 # Reverify all the hosts
Shuqian Zhao06deae02017-02-28 09:55:59 -0800672 reverify_all_push_duts()
Dan Shi7e04fa82013-07-25 15:08:48 -0700673
Shuqian Zhao7b2daea2016-10-25 13:31:06 -0700674 message = ('\nAll tests are completed successfully, the prod branch of the '
675 'following repos ready to be pushed to the hash list below.\n'
Aviv Keshet51172b22017-01-30 16:28:57 -0800676 '%s\n\n\nInstructions for pushing to prod are available at '
Shuqian Zhao3002e6e2017-05-02 18:56:14 -0700677 'https://goto.google.com/autotest-to-prod ' % updated_repo_msg)
Dan Shi7e04fa82013-07-25 15:08:48 -0700678 print message
679 # Send out email about test completed successfully.
680 if arguments.email:
xixuan9307e622017-02-03 20:01:01 -0800681 send_notification_email(
Dan Shi5fa602c2015-03-26 17:54:13 -0700682 arguments.email,
683 'Test for pushing to prod completed successfully',
684 message)
Dan Shi7e04fa82013-07-25 15:08:48 -0700685
686
Shuqian Zhao56969542017-05-30 12:56:57 -0700687def main():
688 """Entry point."""
689 arguments = parse_arguments()
Shuqian Zhao034d85e2017-06-01 11:57:39 -0700690 with ts_mon_config.SetupTsMonGlobalState(service_name='test_push',
691 indirect=True):
Shuqian Zhao56969542017-05-30 12:56:57 -0700692 return _main(arguments)
693
Dan Shi7e04fa82013-07-25 15:08:48 -0700694if __name__ == '__main__':
695 sys.exit(main())