Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 1 | #!/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 | |
| 9 | The script runs push_to_prod suite to verify code in prod branch is ready to be |
| 10 | pushed. Link to design document: |
| 11 | https://docs.google.com/a/google.com/document/d/1JMz0xS3fZRSHMpFkkKAL_rxsdbNZomhHbC3B8L71uuI/edit |
| 12 | |
| 13 | To verify if prod branch can be pushed to lab, run following command in |
| 14 | chromeos-autotest.cbf server: |
Michael Liang | 52d9f1f | 2014-06-17 15:01:24 -0700 | [diff] [blame] | 15 | /usr/local/autotest/site_utils/test_push.py -e someone@company.com |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 16 | |
Shuqian Zhao | f3a114c | 2016-09-21 11:02:15 -0700 | [diff] [blame] | 17 | The script uses latest gandof stable build as test build by default. |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 18 | |
| 19 | """ |
| 20 | |
| 21 | import argparse |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 22 | import ast |
Shuqian Zhao | 7b2daea | 2016-10-25 13:31:06 -0700 | [diff] [blame] | 23 | from contextlib import contextmanager |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 24 | import getpass |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 25 | import multiprocessing |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 26 | import os |
| 27 | import re |
| 28 | import subprocess |
| 29 | import sys |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 30 | import time |
| 31 | import traceback |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 32 | import urllib2 |
| 33 | |
| 34 | import common |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 35 | try: |
| 36 | from autotest_lib.frontend import setup_django_environment |
| 37 | from autotest_lib.frontend.afe import models |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 38 | from autotest_lib.frontend.afe import rpc_utils |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 39 | except ImportError: |
| 40 | # Unittest may not have Django database configured and will fail to import. |
| 41 | pass |
Dan Shi | 5fa602c | 2015-03-26 17:54:13 -0700 | [diff] [blame] | 42 | from autotest_lib.client.common_lib import global_config |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 43 | from autotest_lib.client.common_lib import priorities |
Shuqian Zhao | 6fc7bf4 | 2016-12-11 19:10:36 -0800 | [diff] [blame] | 44 | from autotest_lib.client.common_lib.cros import retry |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 45 | from autotest_lib.server import site_utils |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 46 | from autotest_lib.server import utils |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 47 | from autotest_lib.server.cros import provision |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 48 | from autotest_lib.server.cros.dynamic_suite import frontend_wrappers |
Dan Shi | 5fa602c | 2015-03-26 17:54:13 -0700 | [diff] [blame] | 49 | from autotest_lib.site_utils import gmail_lib |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 50 | from autotest_lib.site_utils.suite_scheduler import constants |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 51 | |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 52 | try: |
| 53 | from chromite.lib import metrics |
| 54 | from chromite.lib import ts_mon_config |
| 55 | except ImportError: |
| 56 | metrics = site_utils.metrics_mock |
| 57 | ts_mon_config = site_utils.metrics_mock |
| 58 | |
Shuqian Zhao | 7b2daea | 2016-10-25 13:31:06 -0700 | [diff] [blame] | 59 | AUTOTEST_DIR=common.autotest_dir |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 60 | CONFIG = global_config.global_config |
| 61 | |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 62 | AFE = frontend_wrappers.RetryingAFE(timeout_min=0.5, delay_sec=2) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 63 | TKO = frontend_wrappers.RetryingTKO(timeout_min=0.1, delay_sec=10) |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 64 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 65 | MAIL_FROM = 'chromeos-test@google.com' |
Shuqian Zhao | 1286166 | 2016-08-31 19:23:17 -0700 | [diff] [blame] | 66 | BUILD_REGEX = 'R[\d]+-[\d]+\.[\d]+\.[\d]+' |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 67 | RUN_SUITE_COMMAND = 'run_suite.py' |
| 68 | PUSH_TO_PROD_SUITE = 'push_to_prod' |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 69 | DUMMY_SUITE = 'dummy' |
Shuqian Zhao | 8ac22e8 | 2016-09-22 14:26:18 -0700 | [diff] [blame] | 70 | # TODO(shuqianz): Dynamically get android build after crbug.com/646068 fixed |
xixuan | 2d66858 | 2016-06-10 14:02:32 -0700 | [diff] [blame] | 71 | DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB = 30 |
Shuqian Zhao | 1286166 | 2016-08-31 19:23:17 -0700 | [diff] [blame] | 72 | IMAGE_BUCKET = CONFIG.get_config_value('CROS', 'image_storage_server') |
Shuqian Zhao | 8ac22e8 | 2016-09-22 14:26:18 -0700 | [diff] [blame] | 73 | DEFAULT_EMAIL = CONFIG.get_config_value( |
xixuan | 9307e62 | 2017-02-03 20:01:01 -0800 | [diff] [blame] | 74 | 'SCHEDULER', 'notify_email', type=list, default=[]) |
Prathmesh Prabhu | 5e28851 | 2017-08-14 10:47:29 -0700 | [diff] [blame] | 75 | # TODO(crbug.com/743077): Bump up tesbed requirement back to 1 when we re-enable |
| 76 | # testbed tests. |
| 77 | DEFAULT_NUM_DUTS = "{'gandof': 4, 'quawks': 2, 'testbed': 0}" |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 78 | |
Fang Deng | 6dddf60 | 2014-04-17 17:01:47 -0700 | [diff] [blame] | 79 | SUITE_JOB_START_INFO_REGEX = ('^.*Created suite job:.*' |
| 80 | 'tab_id=view_job&object_id=(\d+)$') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 81 | |
| 82 | # Dictionary of test results keyed by test name regular expression. |
| 83 | EXPECTED_TEST_RESULTS = {'^SERVER_JOB$': 'GOOD', |
| 84 | # This is related to dummy_Fail/control.dependency. |
| 85 | 'dummy_Fail.dependency$': 'TEST_NA', |
Dan Shi | dc9eb17 | 2014-12-09 16:05:02 -0800 | [diff] [blame] | 86 | 'login_LoginSuccess.*': 'GOOD', |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 87 | 'provision_AutoUpdate.double': 'GOOD', |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 88 | 'dummy_Pass.*': 'GOOD', |
| 89 | 'dummy_Fail.Fail$': 'FAIL', |
| 90 | 'dummy_Fail.RetryFail$': 'FAIL', |
| 91 | 'dummy_Fail.RetrySuccess': 'GOOD', |
| 92 | 'dummy_Fail.Error$': 'ERROR', |
| 93 | 'dummy_Fail.Warn$': 'WARN', |
| 94 | 'dummy_Fail.NAError$': 'TEST_NA', |
| 95 | 'dummy_Fail.Crash$': 'GOOD', |
| 96 | } |
| 97 | |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 98 | EXPECTED_TEST_RESULTS_DUMMY = {'^SERVER_JOB$': 'GOOD', |
| 99 | 'dummy_Pass.*': 'GOOD', |
| 100 | 'dummy_Fail.Fail': 'FAIL', |
| 101 | 'dummy_Fail.Warn': 'WARN', |
| 102 | 'dummy_Fail.Crash': 'GOOD', |
| 103 | 'dummy_Fail.Error': 'ERROR', |
| 104 | 'dummy_Fail.NAError': 'TEST_NA',} |
| 105 | |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 106 | EXPECTED_TEST_RESULTS_TESTBED = {'^SERVER_JOB$': 'GOOD', |
| 107 | 'testbed_DummyTest': 'GOOD',} |
| 108 | |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 109 | EXPECTED_TEST_RESULTS_POWERWASH = {'platform_Powerwash': 'GOOD', |
| 110 | 'SERVER_JOB': 'GOOD'} |
| 111 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 112 | URL_HOST = CONFIG.get_config_value('SERVER', 'hostname', type=str) |
| 113 | URL_PATTERN = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str) |
| 114 | |
Dan Shi | dc9eb17 | 2014-12-09 16:05:02 -0800 | [diff] [blame] | 115 | # Some test could be missing from the test results for various reasons. Add |
| 116 | # such test in this list and explain the reason. |
| 117 | IGNORE_MISSING_TESTS = [ |
| 118 | # For latest build, npo_test_delta does not exist. |
| 119 | 'autoupdate_EndToEndTest.npo_test_delta.*', |
| 120 | # For trybot build, nmo_test_delta does not exist. |
| 121 | 'autoupdate_EndToEndTest.nmo_test_delta.*', |
| 122 | # Older build does not have login_LoginSuccess test in push_to_prod suite. |
| 123 | # TODO(dshi): Remove following lines after R41 is stable. |
| 124 | 'login_LoginSuccess'] |
| 125 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 126 | # Save all run_suite command output. |
Shuqian Zhao | 7b68219 | 2016-09-16 14:38:41 -0700 | [diff] [blame] | 127 | manager = multiprocessing.Manager() |
| 128 | run_suite_output = manager.list() |
Shuqian Zhao | 1b4ca27 | 2016-09-18 14:58:19 -0700 | [diff] [blame] | 129 | all_suite_ids = manager.list() |
Shuqian Zhao | 7b2daea | 2016-10-25 13:31:06 -0700 | [diff] [blame] | 130 | # A dict maps the name of the updated repos and the path of them. |
| 131 | UPDATED_REPOS = {'autotest': AUTOTEST_DIR, |
| 132 | 'chromite': '%s/site-packages/chromite/' % AUTOTEST_DIR} |
Shuqian Zhao | 80d3271 | 2016-11-11 16:37:36 -0800 | [diff] [blame] | 133 | PUSH_USER = 'chromeos-test-lab' |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 134 | |
| 135 | class TestPushException(Exception): |
| 136 | """Exception to be raised when the test to push to prod failed.""" |
| 137 | pass |
| 138 | |
Dan Shi | 5ba5d2e | 2014-05-09 13:47:00 -0700 | [diff] [blame] | 139 | |
Shuqian Zhao | 6fc7bf4 | 2016-12-11 19:10:36 -0800 | [diff] [blame] | 140 | @retry.retry(TestPushException, timeout_min=5, delay_sec=30) |
Shuqian Zhao | a6cf66b | 2017-03-03 12:08:57 -0800 | [diff] [blame] | 141 | def check_dut_inventory(required_num_duts, pool): |
| 142 | """Check DUT inventory for each board in the pool specified.. |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 143 | |
Shuqian Zhao | a7fa5b6 | 2016-11-18 11:13:16 -0800 | [diff] [blame] | 144 | @param required_num_duts: a dict specifying the number of DUT each platform |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 145 | requires in order to finish push tests. |
Shuqian Zhao | a6cf66b | 2017-03-03 12:08:57 -0800 | [diff] [blame] | 146 | @param pool: the pool used by test_push. |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 147 | @raise TestPushException: if number of DUTs are less than the requirement. |
| 148 | """ |
Shuqian Zhao | 6fc7bf4 | 2016-12-11 19:10:36 -0800 | [diff] [blame] | 149 | print 'Checking DUT inventory...' |
Shuqian Zhao | a6cf66b | 2017-03-03 12:08:57 -0800 | [diff] [blame] | 150 | pool_label = constants.Labels.POOL_PREFIX + pool |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 151 | hosts = AFE.run('get_hosts', status='Ready', locked=False) |
Shuqian Zhao | a6cf66b | 2017-03-03 12:08:57 -0800 | [diff] [blame] | 152 | hosts = [h for h in hosts if pool_label in h.get('labels', [])] |
Shuqian Zhao | a7fa5b6 | 2016-11-18 11:13:16 -0800 | [diff] [blame] | 153 | platforms = [host['platform'] for host in hosts] |
| 154 | current_inventory = {p : platforms.count(p) for p in platforms} |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 155 | error_msg = '' |
Shuqian Zhao | a7fa5b6 | 2016-11-18 11:13:16 -0800 | [diff] [blame] | 156 | for platform, req_num in required_num_duts.items(): |
| 157 | curr_num = current_inventory.get(platform, 0) |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 158 | if curr_num < req_num: |
Shuqian Zhao | a6cf66b | 2017-03-03 12:08:57 -0800 | [diff] [blame] | 159 | error_msg += ('\nRequire %d %s DUTs in pool: %s, only %d are Ready' |
| 160 | ' now' % (req_num, platform, pool, curr_num)) |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 161 | if error_msg: |
| 162 | raise TestPushException('Not enough DUTs to run push tests. %s' % |
| 163 | error_msg) |
| 164 | |
| 165 | |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 166 | def powerwash_dut_to_test_repair(hostname, timeout): |
| 167 | """Powerwash dut to test repair workflow. |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 168 | |
| 169 | @param hostname: hostname of the dut. |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 170 | @param timeout: seconds of the powerwash test to hit timeout. |
| 171 | @raise TestPushException: if DUT fail to run the test. |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 172 | """ |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 173 | t = models.Test.objects.get(name='platform_Powerwash') |
| 174 | c = utils.read_file(os.path.join(common.autotest_dir, t.path)) |
| 175 | job_id = rpc_utils.create_job_common( |
| 176 | 'powerwash', priority=priorities.Priority.SUPER, |
| 177 | control_type='Server', control_file=c, hosts=[hostname]) |
| 178 | |
Shuqian Zhao | e83a78c | 2016-09-16 15:01:25 -0700 | [diff] [blame] | 179 | end = time.time() + timeout |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 180 | while not TKO.get_job_test_statuses_from_db(job_id): |
Shuqian Zhao | e83a78c | 2016-09-16 15:01:25 -0700 | [diff] [blame] | 181 | if time.time() >= end: |
| 182 | AFE.run('abort_host_queue_entries', job=job_id) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 183 | raise TestPushException( |
Shuqian Zhao | e83a78c | 2016-09-16 15:01:25 -0700 | [diff] [blame] | 184 | 'Powerwash test on %s timeout after %ds, abort it.' % |
| 185 | (hostname, timeout)) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 186 | time.sleep(10) |
| 187 | verify_test_results(job_id, EXPECTED_TEST_RESULTS_POWERWASH) |
| 188 | # Kick off verify, verify will fail and a repair should be triggered. |
| 189 | AFE.reverify_hosts(hostnames=[hostname]) |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 190 | |
| 191 | |
Shuqian Zhao | 06deae0 | 2017-02-28 09:55:59 -0800 | [diff] [blame] | 192 | def reverify_all_push_duts(): |
| 193 | """Reverify all the push DUTs.""" |
| 194 | print 'Reverifying all DUTs.' |
| 195 | hosts = [h.hostname for h in AFE.get_hosts()] |
Shuqian Zhao | d2a99f0 | 2016-09-22 13:31:30 -0700 | [diff] [blame] | 196 | AFE.reverify_hosts(hostnames=hosts) |
| 197 | |
| 198 | |
Kevin Cheng | e691ce9 | 2016-12-15 12:17:13 -0800 | [diff] [blame] | 199 | def get_default_build(board='gandof', server='chromeos-autotest.hot'): |
Dan Shi | 5ba5d2e | 2014-05-09 13:47:00 -0700 | [diff] [blame] | 200 | """Get the default build to be used for test. |
| 201 | |
Dan Shi | 8df9c00 | 2016-03-08 15:37:39 -0800 | [diff] [blame] | 202 | @param board: Name of board to be tested, default is gandof. |
| 203 | @return: Build to be tested, e.g., gandof-release/R36-5881.0.0 |
Dan Shi | 5ba5d2e | 2014-05-09 13:47:00 -0700 | [diff] [blame] | 204 | """ |
Dan Shi | 5ba5d2e | 2014-05-09 13:47:00 -0700 | [diff] [blame] | 205 | build = None |
Kevin Cheng | e691ce9 | 2016-12-15 12:17:13 -0800 | [diff] [blame] | 206 | cmd = ('%s/cli/atest stable_version list --board=%s -w %s' % |
| 207 | (AUTOTEST_DIR, board, server)) |
Shuqian Zhao | 1286166 | 2016-08-31 19:23:17 -0700 | [diff] [blame] | 208 | result = subprocess.check_output(cmd, shell=True).strip() |
| 209 | build = re.search(BUILD_REGEX, result) |
| 210 | if build: |
| 211 | return '%s-release/%s' % (board, build.group(0)) |
Dan Shi | 5ba5d2e | 2014-05-09 13:47:00 -0700 | [diff] [blame] | 212 | |
Shuqian Zhao | 1286166 | 2016-08-31 19:23:17 -0700 | [diff] [blame] | 213 | # If fail to get stable version from cautotest, use that defined in config |
Dan Shi | 5ba5d2e | 2014-05-09 13:47:00 -0700 | [diff] [blame] | 214 | build = CONFIG.get_config_value('CROS', 'stable_cros_version') |
| 215 | return '%s-release/%s' % (board, build) |
| 216 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 217 | def parse_arguments(): |
| 218 | """Parse arguments for test_push tool. |
| 219 | |
| 220 | @return: Parsed arguments. |
| 221 | |
| 222 | """ |
| 223 | parser = argparse.ArgumentParser() |
Dan Shi | 8df9c00 | 2016-03-08 15:37:39 -0800 | [diff] [blame] | 224 | parser.add_argument('-b', '--board', dest='board', default='gandof', |
| 225 | help='Default is gandof.') |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 226 | parser.add_argument('-sb', '--shard_board', dest='shard_board', |
| 227 | default='quawks', |
| 228 | help='Default is quawks.') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 229 | parser.add_argument('-i', '--build', dest='build', default=None, |
Shuqian Zhao | f3a114c | 2016-09-21 11:02:15 -0700 | [diff] [blame] | 230 | help='Default is the latest stale build of given ' |
| 231 | 'board. Must be a stable build, otherwise AU test ' |
| 232 | 'will fail. (ex: gandolf-release/R54-8743.25.0)') |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 233 | parser.add_argument('-si', '--shard_build', dest='shard_build', default=None, |
Shuqian Zhao | f3a114c | 2016-09-21 11:02:15 -0700 | [diff] [blame] | 234 | help='Default is the latest stable build of given ' |
| 235 | 'board. Must be a stable build, otherwise AU test ' |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 236 | 'will fail.') |
Kevin Cheng | e691ce9 | 2016-12-15 12:17:13 -0800 | [diff] [blame] | 237 | parser.add_argument('-w', '--web', default='chromeos-autotest.hot', |
| 238 | help='Specify web server to grab stable version from.') |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 239 | parser.add_argument('-ab', '--android_board', dest='android_board', |
Shuqian Zhao | 8ac22e8 | 2016-09-22 14:26:18 -0700 | [diff] [blame] | 240 | default='shamu-2', help='Android board to test.') |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 241 | parser.add_argument('-ai', '--android_build', dest='android_build', |
| 242 | help='Android build to test.') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 243 | parser.add_argument('-p', '--pool', dest='pool', default='bvt') |
| 244 | parser.add_argument('-u', '--num', dest='num', type=int, default=3, |
| 245 | help='Run on at most NUM machines.') |
xixuan | 9307e62 | 2017-02-03 20:01:01 -0800 | [diff] [blame] | 246 | parser.add_argument('-e', '--email', nargs='+', dest='email', |
| 247 | default=DEFAULT_EMAIL, |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 248 | help='Email address for the notification to be sent to ' |
| 249 | 'after the script finished running.') |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 250 | parser.add_argument('-t', '--timeout_min', dest='timeout_min', type=int, |
xixuan | 2d66858 | 2016-06-10 14:02:32 -0700 | [diff] [blame] | 251 | default=DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB, |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 252 | help='Time in mins to wait before abort the jobs we ' |
| 253 | 'are waiting on. Only for the asynchronous suites ' |
| 254 | 'triggered by create_and_return flag.') |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 255 | parser.add_argument('-ud', '--num_duts', dest='num_duts', |
| 256 | default=DEFAULT_NUM_DUTS, |
| 257 | help="String of dict that indicates the required number" |
| 258 | " of DUTs for each board. E.g {'gandof':4}") |
Shuqian Zhao | 676ed6f | 2016-09-21 14:20:50 -0700 | [diff] [blame] | 259 | parser.add_argument('-c', '--continue_on_failure', action='store_true', |
| 260 | dest='continue_on_failure', |
| 261 | help='All tests continue to run when there is failure') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 262 | |
| 263 | arguments = parser.parse_args(sys.argv[1:]) |
| 264 | |
Shuqian Zhao | f3a114c | 2016-09-21 11:02:15 -0700 | [diff] [blame] | 265 | # Get latest stable build as default build. |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 266 | if not arguments.build: |
Kevin Cheng | e691ce9 | 2016-12-15 12:17:13 -0800 | [diff] [blame] | 267 | arguments.build = get_default_build(arguments.board, arguments.web) |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 268 | if not arguments.shard_build: |
Kevin Cheng | e691ce9 | 2016-12-15 12:17:13 -0800 | [diff] [blame] | 269 | arguments.shard_build = get_default_build(arguments.shard_board, |
| 270 | arguments.web) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 271 | |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 272 | arguments.num_duts = ast.literal_eval(arguments.num_duts) |
| 273 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 274 | return arguments |
| 275 | |
| 276 | |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 277 | def do_run_suite(suite_name, arguments, use_shard=False, |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 278 | create_and_return=False, testbed_test=False): |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 279 | """Call run_suite to run a suite job, and return the suite job id. |
| 280 | |
| 281 | The script waits the suite job to finish before returning the suite job id. |
| 282 | Also it will echo the run_suite output to stdout. |
| 283 | |
| 284 | @param suite_name: Name of a suite, e.g., dummy. |
| 285 | @param arguments: Arguments for run_suite command. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 286 | @param use_shard: If true, suite is scheduled for shard board. |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 287 | @param create_and_return: If True, run_suite just creates the suite, print |
| 288 | the job id, then finish immediately. |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 289 | @param testbed_test: True to run testbed test. Default is False. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 290 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 291 | @return: Suite job ID. |
| 292 | |
| 293 | """ |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 294 | if use_shard and not testbed_test: |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 295 | board = arguments.shard_board |
| 296 | build = arguments.shard_build |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 297 | elif testbed_test: |
| 298 | board = arguments.android_board |
| 299 | build = arguments.android_build |
| 300 | else: |
| 301 | board = arguments.board |
| 302 | build = arguments.build |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 303 | |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 304 | # Remove cros-version label to force provision. |
Shuqian Zhao | 7a49f1b | 2016-10-24 16:48:04 -0700 | [diff] [blame] | 305 | hosts = AFE.get_hosts(label=constants.Labels.BOARD_PREFIX+board, |
| 306 | locked=False) |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 307 | for host in hosts: |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 308 | labels_to_remove = [ |
| 309 | l for l in host.labels |
| 310 | if (l.startswith(provision.CROS_VERSION_PREFIX) or |
| 311 | l.startswith(provision.TESTBED_BUILD_VERSION_PREFIX))] |
| 312 | if labels_to_remove: |
| 313 | AFE.run('host_remove_labels', id=host.id, labels=labels_to_remove) |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 314 | |
Shuqian Zhao | d01fad0 | 2016-11-18 10:00:22 -0800 | [diff] [blame] | 315 | # Test repair work flow on shards, powerwash test will timeout after 7m. |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 316 | if use_shard and not create_and_return: |
Shuqian Zhao | d01fad0 | 2016-11-18 10:00:22 -0800 | [diff] [blame] | 317 | powerwash_dut_to_test_repair(host.hostname, timeout=420) |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 318 | |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 319 | current_dir = os.path.dirname(os.path.realpath(__file__)) |
| 320 | cmd = [os.path.join(current_dir, RUN_SUITE_COMMAND), |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 321 | '-s', suite_name, |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 322 | '-b', board, |
| 323 | '-i', build, |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 324 | '-p', arguments.pool, |
Shuqian Zhao | 178ac01 | 2016-06-03 15:08:52 -0700 | [diff] [blame] | 325 | '-u', str(arguments.num)] |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 326 | if create_and_return: |
| 327 | cmd += ['-c'] |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 328 | if testbed_test: |
| 329 | cmd += ['--run_prod_code'] |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 330 | |
| 331 | suite_job_id = None |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 332 | |
| 333 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
| 334 | stderr=subprocess.STDOUT) |
| 335 | |
| 336 | while True: |
| 337 | line = proc.stdout.readline() |
| 338 | |
| 339 | # Break when run_suite process completed. |
| 340 | if not line and proc.poll() != None: |
| 341 | break |
| 342 | print line.rstrip() |
| 343 | run_suite_output.append(line.rstrip()) |
| 344 | |
| 345 | if not suite_job_id: |
| 346 | m = re.match(SUITE_JOB_START_INFO_REGEX, line) |
| 347 | if m and m.group(1): |
| 348 | suite_job_id = int(m.group(1)) |
Shuqian Zhao | 1b4ca27 | 2016-09-18 14:58:19 -0700 | [diff] [blame] | 349 | all_suite_ids.append(suite_job_id) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 350 | |
| 351 | if not suite_job_id: |
| 352 | raise TestPushException('Failed to retrieve suite job ID.') |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 353 | |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 354 | # If create_and_return specified, wait for the suite to finish. |
| 355 | if create_and_return: |
| 356 | end = time.time() + arguments.timeout_min * 60 |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 357 | while not AFE.get_jobs(id=suite_job_id, finished=True): |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 358 | if time.time() < end: |
| 359 | time.sleep(10) |
| 360 | else: |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 361 | AFE.run('abort_host_queue_entries', job=suite_job_id) |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 362 | raise TestPushException( |
| 363 | 'Asynchronous suite triggered by create_and_return ' |
| 364 | 'flag has timed out after %d mins. Aborting it.' % |
| 365 | arguments.timeout_min) |
| 366 | |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 367 | print 'Suite job %s is completed.' % suite_job_id |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 368 | return suite_job_id |
| 369 | |
| 370 | |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 371 | def check_dut_image(build, suite_job_id): |
| 372 | """Confirm all DUTs used for the suite are imaged to expected build. |
| 373 | |
| 374 | @param build: Expected build to be imaged. |
| 375 | @param suite_job_id: job ID of the suite job. |
| 376 | @raise TestPushException: If a DUT does not have expected build imaged. |
| 377 | """ |
| 378 | print 'Checking image installed in DUTs...' |
| 379 | job_ids = [job.id for job in |
| 380 | models.Job.objects.filter(parent_job_id=suite_job_id)] |
| 381 | hqes = [models.HostQueueEntry.objects.filter(job_id=job_id)[0] |
| 382 | for job_id in job_ids] |
| 383 | hostnames = set([hqe.host.hostname for hqe in hqes]) |
| 384 | for hostname in hostnames: |
Prathmesh Prabhu | f10f41a | 2017-04-21 11:52:16 -0700 | [diff] [blame] | 385 | found_build = site_utils.get_build_from_afe(hostname, AFE) |
| 386 | if found_build != build: |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 387 | raise TestPushException('DUT is not imaged properly. Host %s has ' |
| 388 | 'build %s, while build %s is expected.' % |
Prathmesh Prabhu | f10f41a | 2017-04-21 11:52:16 -0700 | [diff] [blame] | 389 | (hostname, found_build, build)) |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 390 | |
| 391 | |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 392 | def test_suite(suite_name, expected_results, arguments, use_shard=False, |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 393 | create_and_return=False, testbed_test=False): |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 394 | """Call run_suite to start a suite job and verify results. |
| 395 | |
| 396 | @param suite_name: Name of a suite, e.g., dummy |
| 397 | @param expected_results: A dictionary of test name to test result. |
| 398 | @param arguments: Arguments for run_suite command. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 399 | @param use_shard: If true, suite is scheduled for shard board. |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 400 | @param create_and_return: If True, run_suite just creates the suite, print |
| 401 | the job id, then finish immediately. |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 402 | @param testbed_test: True to run testbed test. Default is False. |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 403 | """ |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 404 | suite_job_id = do_run_suite(suite_name, arguments, use_shard, |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 405 | create_and_return, testbed_test) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 406 | |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 407 | # Confirm all DUTs used for the suite are imaged to expected build. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 408 | # hqe.host_id for jobs running in shard is not synced back to master db, |
| 409 | # therefore, skip verifying dut build for jobs running in shard. |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 410 | build_expected = (arguments.android_build if testbed_test |
| 411 | else arguments.build) |
Aviv Keshet | d235912 | 2017-05-03 22:50:10 -0700 | [diff] [blame] | 412 | if not use_shard and not testbed_test: |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 413 | check_dut_image(build_expected, suite_job_id) |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 414 | |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 415 | # Verify test results are the expected results. |
| 416 | verify_test_results(suite_job_id, expected_results) |
| 417 | |
| 418 | |
| 419 | def verify_test_results(job_id, expected_results): |
| 420 | """Verify the test results with the expected results. |
| 421 | |
| 422 | @param job_id: id of the running jobs. For suite job, it is suite_job_id. |
| 423 | @param expected_results: A dictionary of test name to test result. |
| 424 | @raise TestPushException: If verify fails. |
| 425 | """ |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 426 | print 'Comparing test results...' |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 427 | test_views = site_utils.get_test_views_from_tko(job_id, TKO) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 428 | |
| 429 | mismatch_errors = [] |
| 430 | extra_test_errors = [] |
| 431 | |
| 432 | found_keys = set() |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 433 | for test_name, test_status in test_views.items(): |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 434 | print "%s%s" % (test_name.ljust(30), test_status) |
Dan Shi | 80b6ec0 | 2016-07-21 15:49:18 -0700 | [diff] [blame] | 435 | # platform_InstallTestImage test may exist in old builds. |
| 436 | if re.search('platform_InstallTestImage_SERVER_JOB$', test_name): |
| 437 | continue |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 438 | test_found = False |
| 439 | for key,val in expected_results.items(): |
| 440 | if re.search(key, test_name): |
| 441 | test_found = True |
| 442 | found_keys.add(key) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 443 | if val != test_status: |
| 444 | error = ('%s Expected: [%s], Actual: [%s]' % |
| 445 | (test_name, val, test_status)) |
| 446 | mismatch_errors.append(error) |
| 447 | if not test_found: |
| 448 | extra_test_errors.append(test_name) |
| 449 | |
| 450 | missing_test_errors = set(expected_results.keys()) - found_keys |
Dan Shi | dc9eb17 | 2014-12-09 16:05:02 -0800 | [diff] [blame] | 451 | for exception in IGNORE_MISSING_TESTS: |
| 452 | try: |
| 453 | missing_test_errors.remove(exception) |
| 454 | except KeyError: |
| 455 | pass |
| 456 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 457 | summary = [] |
| 458 | if mismatch_errors: |
| 459 | summary.append(('Results of %d test(s) do not match expected ' |
| 460 | 'values:') % len(mismatch_errors)) |
| 461 | summary.extend(mismatch_errors) |
| 462 | summary.append('\n') |
| 463 | |
| 464 | if extra_test_errors: |
| 465 | summary.append('%d test(s) are not expected to be run:' % |
| 466 | len(extra_test_errors)) |
| 467 | summary.extend(extra_test_errors) |
| 468 | summary.append('\n') |
| 469 | |
| 470 | if missing_test_errors: |
| 471 | summary.append('%d test(s) are missing from the results:' % |
| 472 | len(missing_test_errors)) |
| 473 | summary.extend(missing_test_errors) |
| 474 | summary.append('\n') |
| 475 | |
| 476 | # Test link to log can be loaded. |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 477 | job_name = '%s-%s' % (job_id, getpass.getuser()) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 478 | log_link = URL_PATTERN % (URL_HOST, job_name) |
| 479 | try: |
| 480 | urllib2.urlopen(log_link).read() |
| 481 | except urllib2.URLError: |
| 482 | summary.append('Failed to load page for link to log: %s.' % log_link) |
| 483 | |
| 484 | if summary: |
| 485 | raise TestPushException('\n'.join(summary)) |
| 486 | |
| 487 | |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 488 | def test_suite_wrapper(queue, suite_name, expected_results, arguments, |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 489 | use_shard=False, create_and_return=False, |
| 490 | testbed_test=False): |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 491 | """Wrapper to call test_suite. Handle exception and pipe it to parent |
| 492 | process. |
| 493 | |
| 494 | @param queue: Queue to save exception to be accessed by parent process. |
| 495 | @param suite_name: Name of a suite, e.g., dummy |
| 496 | @param expected_results: A dictionary of test name to test result. |
| 497 | @param arguments: Arguments for run_suite command. |
| 498 | @param use_shard: If true, suite is scheduled for shard board. |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 499 | @param create_and_return: If True, run_suite just creates the suite, print |
| 500 | the job id, then finish immediately. |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 501 | @param testbed_test: True to run testbed test. Default is False. |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 502 | """ |
| 503 | try: |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 504 | test_suite(suite_name, expected_results, arguments, use_shard, |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 505 | create_and_return, testbed_test) |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 506 | except: |
| 507 | # Store the whole exc_info leads to a PicklingError. |
| 508 | except_type, except_value, tb = sys.exc_info() |
| 509 | queue.put((except_type, except_value, traceback.extract_tb(tb))) |
| 510 | |
| 511 | |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 512 | def check_queue(queue): |
| 513 | """Check the queue for any exception being raised. |
| 514 | |
| 515 | @param queue: Queue used to store exception for parent process to access. |
| 516 | @raise: Any exception found in the queue. |
| 517 | """ |
| 518 | if queue.empty(): |
| 519 | return |
| 520 | exc_info = queue.get() |
| 521 | # Raise the exception with original backtrace. |
| 522 | print 'Original stack trace of the exception:\n%s' % exc_info[2] |
| 523 | raise exc_info[0](exc_info[1]) |
| 524 | |
| 525 | |
Shuqian Zhao | 7b2daea | 2016-10-25 13:31:06 -0700 | [diff] [blame] | 526 | def get_head_of_repos(repos): |
| 527 | """Get HEAD of updated repos, currently are autotest and chromite repos |
| 528 | |
| 529 | @param repos: a map of repo name to the path of the repo. E.g. |
| 530 | {'autotest': '/usr/local/autotest'} |
| 531 | @return: a map of repo names to the current HEAD of that repo. |
| 532 | """ |
| 533 | @contextmanager |
| 534 | def cd(new_wd): |
| 535 | """Helper function to change working directory. |
| 536 | |
| 537 | @param new_wd: new working directory that switch to. |
| 538 | """ |
| 539 | prev_wd = os.getcwd() |
| 540 | os.chdir(os.path.expanduser(new_wd)) |
| 541 | try: |
| 542 | yield |
| 543 | finally: |
| 544 | os.chdir(prev_wd) |
| 545 | |
| 546 | updated_repo_heads = {} |
| 547 | for repo_name, path_to_repo in repos.iteritems(): |
| 548 | with cd(path_to_repo): |
| 549 | head = subprocess.check_output('git rev-parse HEAD', |
| 550 | shell=True).strip() |
| 551 | updated_repo_heads[repo_name] = head |
| 552 | return updated_repo_heads |
| 553 | |
| 554 | |
Shuqian Zhao | 80d3271 | 2016-11-11 16:37:36 -0800 | [diff] [blame] | 555 | def push_prod_next_branch(updated_repo_heads): |
| 556 | """push prod-next branch to the tested HEAD after all tests pass. |
| 557 | |
| 558 | The push command must be ran as PUSH_USER, since only PUSH_USER has the |
| 559 | right to push branches. |
| 560 | |
| 561 | @param updated_repo_heads: a map of repo names to tested HEAD of that repo. |
| 562 | """ |
| 563 | # prod-next branch for every repo is downloaded under PUSH_USER home dir. |
Shuqian Zhao | aa0301c | 2016-11-21 09:46:41 -0800 | [diff] [blame] | 564 | cmd = ('cd ~/{repo}; git pull; git rebase {hash} prod-next;' |
| 565 | 'git push origin prod-next') |
Shuqian Zhao | 80d3271 | 2016-11-11 16:37:36 -0800 | [diff] [blame] | 566 | run_push_as_push_user = "sudo su - %s -c '%s'" % (PUSH_USER, cmd) |
| 567 | |
| 568 | for repo_name, test_hash in updated_repo_heads.iteritems(): |
| 569 | push_cmd = run_push_as_push_user.format(hash=test_hash, repo=repo_name) |
| 570 | print 'Pushing %s prod-next branch to %s' % (repo_name, test_hash) |
| 571 | print subprocess.check_output(push_cmd, stderr=subprocess.STDOUT, |
| 572 | shell=True) |
| 573 | |
| 574 | |
xixuan | 9307e62 | 2017-02-03 20:01:01 -0800 | [diff] [blame] | 575 | def send_notification_email(email_list, title, msg): |
| 576 | """Send notification to all email addresses in email list. |
| 577 | |
| 578 | @param email_list: a email address list which receives notification email, |
| 579 | whose format is like: |
| 580 | [xxx@google.com, xxx@google.com, xxx@google.com,...] |
| 581 | so that users could also specify multiple email addresses by using |
| 582 | config '--email' or '-e'. |
| 583 | @param title: the title of the email to be sent. |
| 584 | @param msg: the content of the email to be sent. |
| 585 | """ |
| 586 | gmail_lib.send_email(','.join(email_list), title, msg) |
| 587 | |
| 588 | |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 589 | def _main(arguments): |
| 590 | """Running tests. |
| 591 | |
| 592 | @param arguments: command line arguments. |
| 593 | """ |
Shuqian Zhao | 80d3271 | 2016-11-11 16:37:36 -0800 | [diff] [blame] | 594 | updated_repo_heads = get_head_of_repos(UPDATED_REPOS) |
| 595 | updated_repo_msg = '\n'.join( |
| 596 | ['%s: %s' % (k, v) for k, v in updated_repo_heads.iteritems()]) |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 597 | test_push_success = False |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 598 | |
| 599 | try: |
Shuqian Zhao | 676ed6f | 2016-09-21 14:20:50 -0700 | [diff] [blame] | 600 | # Use daemon flag will kill child processes when parent process fails. |
| 601 | use_daemon = not arguments.continue_on_failure |
Shuqian Zhao | 6fc7bf4 | 2016-12-11 19:10:36 -0800 | [diff] [blame] | 602 | # Verify all the DUTs at the beginning of testing push. |
Shuqian Zhao | 06deae0 | 2017-02-28 09:55:59 -0800 | [diff] [blame] | 603 | reverify_all_push_duts() |
Shuqian Zhao | 6fc7bf4 | 2016-12-11 19:10:36 -0800 | [diff] [blame] | 604 | time.sleep(15) # Wait 15 secs for the verify test to start. |
Shuqian Zhao | a6cf66b | 2017-03-03 12:08:57 -0800 | [diff] [blame] | 605 | check_dut_inventory(arguments.num_duts, arguments.pool) |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 606 | queue = multiprocessing.Queue() |
| 607 | |
| 608 | push_to_prod_suite = multiprocessing.Process( |
| 609 | target=test_suite_wrapper, |
| 610 | args=(queue, PUSH_TO_PROD_SUITE, EXPECTED_TEST_RESULTS, |
| 611 | arguments)) |
Shuqian Zhao | 676ed6f | 2016-09-21 14:20:50 -0700 | [diff] [blame] | 612 | push_to_prod_suite.daemon = use_daemon |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 613 | push_to_prod_suite.start() |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 614 | |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 615 | # suite test with --create_and_return flag |
| 616 | asynchronous_suite = multiprocessing.Process( |
| 617 | target=test_suite_wrapper, |
| 618 | args=(queue, DUMMY_SUITE, EXPECTED_TEST_RESULTS_DUMMY, |
Aviv Keshet | d235912 | 2017-05-03 22:50:10 -0700 | [diff] [blame] | 619 | arguments, True, True)) |
Shuqian Zhao | 1b4ca27 | 2016-09-18 14:58:19 -0700 | [diff] [blame] | 620 | asynchronous_suite.daemon = True |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 621 | asynchronous_suite.start() |
| 622 | |
Aviv Keshet | d235912 | 2017-05-03 22:50:10 -0700 | [diff] [blame] | 623 | while (push_to_prod_suite.is_alive() |
Aviv Keshet | ac36b85 | 2017-07-14 09:58:05 -0700 | [diff] [blame] | 624 | or asynchronous_suite.is_alive()): |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 625 | check_queue(queue) |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 626 | time.sleep(5) |
| 627 | |
| 628 | check_queue(queue) |
| 629 | |
| 630 | push_to_prod_suite.join() |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 631 | asynchronous_suite.join() |
Shuqian Zhao | 80d3271 | 2016-11-11 16:37:36 -0800 | [diff] [blame] | 632 | |
| 633 | # All tests pass, push prod-next branch for UPDATED_REPOS. |
Shuqian Zhao | aa0301c | 2016-11-21 09:46:41 -0800 | [diff] [blame] | 634 | push_prod_next_branch(updated_repo_heads) |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 635 | test_push_success = True |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 636 | except Exception as e: |
| 637 | print 'Test for pushing to prod failed:\n' |
| 638 | print str(e) |
Shuqian Zhao | 676ed6f | 2016-09-21 14:20:50 -0700 | [diff] [blame] | 639 | # Abort running jobs when choose not to continue when there is failure. |
| 640 | if not arguments.continue_on_failure: |
| 641 | for suite_id in all_suite_ids: |
| 642 | if AFE.get_jobs(id=suite_id, finished=False): |
| 643 | AFE.run('abort_host_queue_entries', job=suite_id) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 644 | # Send out email about the test failure. |
| 645 | if arguments.email: |
xixuan | 9307e62 | 2017-02-03 20:01:01 -0800 | [diff] [blame] | 646 | send_notification_email( |
Dan Shi | 5fa602c | 2015-03-26 17:54:13 -0700 | [diff] [blame] | 647 | arguments.email, |
| 648 | 'Test for pushing to prod failed. Do NOT push!', |
Shuqian Zhao | 7b2daea | 2016-10-25 13:31:06 -0700 | [diff] [blame] | 649 | ('Test CLs of the following repos failed. Below are the ' |
| 650 | 'repos and the corresponding test HEAD.\n\n%s\n\n.' |
Shuqian Zhao | 0566ee7 | 2017-02-22 10:05:56 -0800 | [diff] [blame] | 651 | 'Error occurred during test:\n\n%s\n\n' |
Prathmesh Prabhu | 2877a00 | 2017-03-14 14:49:42 -0700 | [diff] [blame] | 652 | 'All logs have been saved to ' |
Aviv Keshet | 43bbf05 | 2017-07-10 11:39:45 -0700 | [diff] [blame] | 653 | '/var/log/test_push/test_push.log on push master. ' |
| 654 | 'Stats on recent success rate can be found at ' |
| 655 | 'go/test-push-stats . Detailed ' |
Prathmesh Prabhu | 2877a00 | 2017-03-14 14:49:42 -0700 | [diff] [blame] | 656 | 'debugging info can be found at go/push-to-prod' % |
Shuqian Zhao | 7b2daea | 2016-10-25 13:31:06 -0700 | [diff] [blame] | 657 | (updated_repo_msg, str(e)) + '\n'.join(run_suite_output))) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 658 | raise |
Shuqian Zhao | f794c49 | 2017-01-06 16:27:23 -0800 | [diff] [blame] | 659 | finally: |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 660 | metrics.Counter('chromeos/autotest/test_push/completed').increment( |
| 661 | fields={'success': test_push_success}) |
Shuqian Zhao | d2a99f0 | 2016-09-22 13:31:30 -0700 | [diff] [blame] | 662 | # Reverify all the hosts |
Shuqian Zhao | 06deae0 | 2017-02-28 09:55:59 -0800 | [diff] [blame] | 663 | reverify_all_push_duts() |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 664 | |
Prathmesh Prabhu | 39bf0a6 | 2017-08-29 22:03:19 -0700 | [diff] [blame^] | 665 | message = ('\nAll tests completed successfully, the prod branch of the ' |
| 666 | 'following repos is ready to be pushed to the hash list below.\n' |
Aviv Keshet | 51172b2 | 2017-01-30 16:28:57 -0800 | [diff] [blame] | 667 | '%s\n\n\nInstructions for pushing to prod are available at ' |
Shuqian Zhao | 3002e6e | 2017-05-02 18:56:14 -0700 | [diff] [blame] | 668 | 'https://goto.google.com/autotest-to-prod ' % updated_repo_msg) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 669 | print message |
| 670 | # Send out email about test completed successfully. |
| 671 | if arguments.email: |
xixuan | 9307e62 | 2017-02-03 20:01:01 -0800 | [diff] [blame] | 672 | send_notification_email( |
Dan Shi | 5fa602c | 2015-03-26 17:54:13 -0700 | [diff] [blame] | 673 | arguments.email, |
| 674 | 'Test for pushing to prod completed successfully', |
| 675 | message) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 676 | |
| 677 | |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 678 | def main(): |
| 679 | """Entry point.""" |
| 680 | arguments = parse_arguments() |
Shuqian Zhao | 034d85e | 2017-06-01 11:57:39 -0700 | [diff] [blame] | 681 | with ts_mon_config.SetupTsMonGlobalState(service_name='test_push', |
| 682 | indirect=True): |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 683 | return _main(arguments) |
| 684 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 685 | if __name__ == '__main__': |
| 686 | sys.exit(main()) |