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 |
Shuqian Zhao | bb030ff | 2017-09-21 17:36:13 -0700 | [diff] [blame] | 14 | chromeos-staging-master2.hot 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 | 0de876d | 2018-01-31 11:53:34 -0800 | [diff] [blame] | 23 | import datetime |
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 | f239b31 | 2017-12-05 16:45:02 -0800 | [diff] [blame] | 44 | from autotest_lib.client.common_lib.cros import retry |
Prathmesh Prabhu | cd246f5 | 2018-01-03 13:45:48 -0800 | [diff] [blame] | 45 | from autotest_lib.frontend.afe import rpc_client_lib |
Xixuan Wu | 93e646c | 2017-12-07 18:36:10 -0800 | [diff] [blame] | 46 | from autotest_lib.server import constants |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 47 | from autotest_lib.server import site_utils |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 48 | from autotest_lib.server import utils |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 49 | from autotest_lib.server.cros import provision |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 50 | from autotest_lib.server.cros.dynamic_suite import frontend_wrappers |
Xixuan Wu | 665cfad | 2018-08-10 10:08:14 -0700 | [diff] [blame] | 51 | from autotest_lib.site_utils import test_push_common |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 52 | |
Shuqian Zhao | 7b2daea | 2016-10-25 13:31:06 -0700 | [diff] [blame] | 53 | AUTOTEST_DIR=common.autotest_dir |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 54 | CONFIG = global_config.global_config |
| 55 | |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 56 | AFE = frontend_wrappers.RetryingAFE(timeout_min=0.5, delay_sec=2) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 57 | TKO = frontend_wrappers.RetryingTKO(timeout_min=0.1, delay_sec=10) |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 58 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 59 | MAIL_FROM = 'chromeos-test@google.com' |
Shuqian Zhao | 1286166 | 2016-08-31 19:23:17 -0700 | [diff] [blame] | 60 | BUILD_REGEX = 'R[\d]+-[\d]+\.[\d]+\.[\d]+' |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 61 | RUN_SUITE_COMMAND = 'run_suite.py' |
| 62 | PUSH_TO_PROD_SUITE = 'push_to_prod' |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 63 | DUMMY_SUITE = 'dummy' |
xixuan | 2d66858 | 2016-06-10 14:02:32 -0700 | [diff] [blame] | 64 | DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB = 30 |
Shuqian Zhao | 1286166 | 2016-08-31 19:23:17 -0700 | [diff] [blame] | 65 | IMAGE_BUCKET = CONFIG.get_config_value('CROS', 'image_storage_server') |
Allen Li | 64edf06 | 2017-11-27 15:33:54 -0800 | [diff] [blame] | 66 | DEFAULT_NUM_DUTS = ( |
| 67 | ('gandof', 4), |
Aviv Keshet | cc0be07 | 2018-09-20 21:32:09 +0000 | [diff] [blame] | 68 | ('quawks', 2), |
Allen Li | 64edf06 | 2017-11-27 15:33:54 -0800 | [diff] [blame] | 69 | ) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 70 | |
Fang Deng | 6dddf60 | 2014-04-17 17:01:47 -0700 | [diff] [blame] | 71 | SUITE_JOB_START_INFO_REGEX = ('^.*Created suite job:.*' |
| 72 | 'tab_id=view_job&object_id=(\d+)$') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 73 | |
| 74 | # Dictionary of test results keyed by test name regular expression. |
| 75 | EXPECTED_TEST_RESULTS = {'^SERVER_JOB$': 'GOOD', |
| 76 | # This is related to dummy_Fail/control.dependency. |
| 77 | 'dummy_Fail.dependency$': 'TEST_NA', |
Dan Shi | dc9eb17 | 2014-12-09 16:05:02 -0800 | [diff] [blame] | 78 | 'login_LoginSuccess.*': 'GOOD', |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 79 | 'provision_AutoUpdate.double': 'GOOD', |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 80 | 'dummy_Pass.*': 'GOOD', |
| 81 | 'dummy_Fail.Fail$': 'FAIL', |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 82 | 'dummy_Fail.Error$': 'ERROR', |
| 83 | 'dummy_Fail.Warn$': 'WARN', |
| 84 | 'dummy_Fail.NAError$': 'TEST_NA', |
| 85 | 'dummy_Fail.Crash$': 'GOOD', |
Aviv Keshet | ff024f9 | 2017-09-26 13:43:14 -0700 | [diff] [blame] | 86 | 'autotest_SyncCount$': 'GOOD', |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 87 | } |
| 88 | |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 89 | EXPECTED_TEST_RESULTS_DUMMY = {'^SERVER_JOB$': 'GOOD', |
| 90 | 'dummy_Pass.*': 'GOOD', |
| 91 | 'dummy_Fail.Fail': 'FAIL', |
| 92 | 'dummy_Fail.Warn': 'WARN', |
| 93 | 'dummy_Fail.Crash': 'GOOD', |
| 94 | 'dummy_Fail.Error': 'ERROR', |
| 95 | 'dummy_Fail.NAError': 'TEST_NA',} |
| 96 | |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 97 | EXPECTED_TEST_RESULTS_POWERWASH = {'platform_Powerwash': 'GOOD', |
| 98 | 'SERVER_JOB': 'GOOD'} |
| 99 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 100 | URL_HOST = CONFIG.get_config_value('SERVER', 'hostname', type=str) |
| 101 | URL_PATTERN = CONFIG.get_config_value('CROS', 'log_url_pattern', type=str) |
| 102 | |
Prathmesh Prabhu | 9b00d2d | 2018-08-17 11:30:16 -0700 | [diff] [blame] | 103 | # Some test could be extra / missing or have mismatched results for various |
| 104 | # reasons. Add such test in this list and explain the reason. |
Xixuan Wu | 84a834f | 2018-08-10 15:22:26 -0700 | [diff] [blame] | 105 | _IGNORED_TESTS = [ |
Prathmesh Prabhu | c457798 | 2018-08-17 11:23:36 -0700 | [diff] [blame] | 106 | # test_push uses a stable image build to test, which is quite behind ToT. |
| 107 | # The following expectations are correct at ToT, but need to be ignored |
| 108 | # until stable image is recent enough. |
| 109 | |
Prathmesh Prabhu | c457798 | 2018-08-17 11:23:36 -0700 | [diff] [blame] | 110 | # TODO(pprabhu): Remove once R70 is stable. |
| 111 | 'dummy_Fail.RetrySuccess', |
| 112 | 'dummy_Fail.RetryFail', |
Xixuan Wu | 84a834f | 2018-08-10 15:22:26 -0700 | [diff] [blame] | 113 | ] |
Dan Shi | dc9eb17 | 2014-12-09 16:05:02 -0800 | [diff] [blame] | 114 | |
Aviv Keshet | 0d679eb | 2017-11-08 13:25:01 -0800 | [diff] [blame] | 115 | # Multiprocessing proxy objects that are used to share data between background |
| 116 | # suite-running processes and main process. The multiprocessing-compatible |
| 117 | # versions are initialized in _main. |
| 118 | _run_suite_output = [] |
| 119 | _all_suite_ids = [] |
| 120 | |
Shuqian Zhao | 0de876d | 2018-01-31 11:53:34 -0800 | [diff] [blame] | 121 | DEFAULT_SERVICE_RESPAWN_LIMIT = 2 |
| 122 | |
| 123 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 124 | class TestPushException(Exception): |
| 125 | """Exception to be raised when the test to push to prod failed.""" |
| 126 | pass |
| 127 | |
Shuqian Zhao | f239b31 | 2017-12-05 16:45:02 -0800 | [diff] [blame] | 128 | @retry.retry(TestPushException, timeout_min=5, delay_sec=30) |
| 129 | def check_dut_inventory(required_num_duts, pool): |
| 130 | """Check DUT inventory for each board in the pool specified.. |
| 131 | |
| 132 | @param required_num_duts: a dict specifying the number of DUT each platform |
| 133 | requires in order to finish push tests. |
| 134 | @param pool: the pool used by test_push. |
| 135 | @raise TestPushException: if number of DUTs are less than the requirement. |
| 136 | """ |
| 137 | print 'Checking DUT inventory...' |
| 138 | pool_label = constants.Labels.POOL_PREFIX + pool |
| 139 | hosts = AFE.run('get_hosts', status='Ready', locked=False) |
| 140 | hosts = [h for h in hosts if pool_label in h.get('labels', [])] |
| 141 | platforms = [host['platform'] for host in hosts] |
| 142 | current_inventory = {p : platforms.count(p) for p in platforms} |
| 143 | error_msg = '' |
| 144 | for platform, req_num in required_num_duts.items(): |
| 145 | curr_num = current_inventory.get(platform, 0) |
| 146 | if curr_num < req_num: |
| 147 | error_msg += ('\nRequire %d %s DUTs in pool: %s, only %d are Ready' |
| 148 | ' now' % (req_num, platform, pool, curr_num)) |
| 149 | if error_msg: |
| 150 | raise TestPushException('Not enough DUTs to run push tests. %s' % |
| 151 | error_msg) |
| 152 | |
Dan Shi | 5ba5d2e | 2014-05-09 13:47:00 -0700 | [diff] [blame] | 153 | |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 154 | def powerwash_dut_to_test_repair(hostname, timeout): |
| 155 | """Powerwash dut to test repair workflow. |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 156 | |
| 157 | @param hostname: hostname of the dut. |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 158 | @param timeout: seconds of the powerwash test to hit timeout. |
| 159 | @raise TestPushException: if DUT fail to run the test. |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 160 | """ |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 161 | t = models.Test.objects.get(name='platform_Powerwash') |
Jacob Kopczynski | 0ea4e04 | 2018-08-14 11:21:48 -0700 | [diff] [blame] | 162 | c = utils.read_file(os.path.join(AUTOTEST_DIR, t.path)) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 163 | job_id = rpc_utils.create_job_common( |
| 164 | 'powerwash', priority=priorities.Priority.SUPER, |
| 165 | control_type='Server', control_file=c, hosts=[hostname]) |
| 166 | |
Shuqian Zhao | e83a78c | 2016-09-16 15:01:25 -0700 | [diff] [blame] | 167 | end = time.time() + timeout |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 168 | while not TKO.get_job_test_statuses_from_db(job_id): |
Shuqian Zhao | e83a78c | 2016-09-16 15:01:25 -0700 | [diff] [blame] | 169 | if time.time() >= end: |
| 170 | AFE.run('abort_host_queue_entries', job=job_id) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 171 | raise TestPushException( |
Shuqian Zhao | e83a78c | 2016-09-16 15:01:25 -0700 | [diff] [blame] | 172 | 'Powerwash test on %s timeout after %ds, abort it.' % |
| 173 | (hostname, timeout)) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 174 | time.sleep(10) |
Xixuan Wu | 665cfad | 2018-08-10 10:08:14 -0700 | [diff] [blame] | 175 | verify_test_results(job_id, |
| 176 | test_push_common.EXPECTED_TEST_RESULTS_POWERWASH) |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 177 | # Kick off verify, verify will fail and a repair should be triggered. |
| 178 | AFE.reverify_hosts(hostnames=[hostname]) |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 179 | |
| 180 | |
Shuqian Zhao | 06deae0 | 2017-02-28 09:55:59 -0800 | [diff] [blame] | 181 | def reverify_all_push_duts(): |
| 182 | """Reverify all the push DUTs.""" |
| 183 | print 'Reverifying all DUTs.' |
| 184 | hosts = [h.hostname for h in AFE.get_hosts()] |
Shuqian Zhao | d2a99f0 | 2016-09-22 13:31:30 -0700 | [diff] [blame] | 185 | AFE.reverify_hosts(hostnames=hosts) |
| 186 | |
| 187 | |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 188 | def parse_arguments(argv): |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 189 | """Parse arguments for test_push tool. |
| 190 | |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 191 | @param argv Argument vector, as for `sys.argv`, including the |
| 192 | command name in `argv[0]`. |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 193 | @return: Parsed arguments. |
| 194 | |
| 195 | """ |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 196 | parser = argparse.ArgumentParser(prog=argv[0]) |
Dan Shi | 8df9c00 | 2016-03-08 15:37:39 -0800 | [diff] [blame] | 197 | parser.add_argument('-b', '--board', dest='board', default='gandof', |
| 198 | help='Default is gandof.') |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 199 | parser.add_argument('-sb', '--shard_board', dest='shard_board', |
| 200 | default='quawks', |
| 201 | help='Default is quawks.') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 202 | parser.add_argument('-i', '--build', dest='build', default=None, |
Shuqian Zhao | f3a114c | 2016-09-21 11:02:15 -0700 | [diff] [blame] | 203 | help='Default is the latest stale build of given ' |
| 204 | 'board. Must be a stable build, otherwise AU test ' |
| 205 | 'will fail. (ex: gandolf-release/R54-8743.25.0)') |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 206 | parser.add_argument('-si', '--shard_build', dest='shard_build', default=None, |
Shuqian Zhao | f3a114c | 2016-09-21 11:02:15 -0700 | [diff] [blame] | 207 | help='Default is the latest stable build of given ' |
| 208 | 'board. Must be a stable build, otherwise AU test ' |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 209 | 'will fail.') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 210 | parser.add_argument('-p', '--pool', dest='pool', default='bvt') |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 211 | parser.add_argument('-t', '--timeout_min', dest='timeout_min', type=int, |
xixuan | 2d66858 | 2016-06-10 14:02:32 -0700 | [diff] [blame] | 212 | default=DEFAULT_TIMEOUT_MIN_FOR_SUITE_JOB, |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 213 | help='Time in mins to wait before abort the jobs we ' |
| 214 | 'are waiting on. Only for the asynchronous suites ' |
| 215 | 'triggered by create_and_return flag.') |
Shuqian Zhao | 1f311c0 | 2016-09-01 19:30:54 -0700 | [diff] [blame] | 216 | parser.add_argument('-ud', '--num_duts', dest='num_duts', |
Allen Li | 64edf06 | 2017-11-27 15:33:54 -0800 | [diff] [blame] | 217 | default=dict(DEFAULT_NUM_DUTS), |
| 218 | type=ast.literal_eval, |
| 219 | help="Python dict literal that specifies the required" |
| 220 | " number of DUTs for each board. E.g {'gandof':4}") |
Shuqian Zhao | 676ed6f | 2016-09-21 14:20:50 -0700 | [diff] [blame] | 221 | parser.add_argument('-c', '--continue_on_failure', action='store_true', |
| 222 | dest='continue_on_failure', |
| 223 | help='All tests continue to run when there is failure') |
Shuqian Zhao | 0de876d | 2018-01-31 11:53:34 -0800 | [diff] [blame] | 224 | parser.add_argument('-sl', '--service_respawn_limit', type=int, |
| 225 | default=DEFAULT_SERVICE_RESPAWN_LIMIT, |
| 226 | help='If a service crashes more than this, the test ' |
| 227 | 'push is considered failed.') |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 228 | |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 229 | arguments = parser.parse_args(argv[1:]) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 230 | |
Shuqian Zhao | f3a114c | 2016-09-21 11:02:15 -0700 | [diff] [blame] | 231 | # Get latest stable build as default build. |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 232 | version_map = AFE.get_stable_version_map(AFE.CROS_IMAGE_TYPE) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 233 | if not arguments.build: |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 234 | arguments.build = version_map.get_image_name(arguments.board) |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 235 | if not arguments.shard_build: |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 236 | arguments.shard_build = version_map.get_image_name( |
| 237 | arguments.shard_board) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 238 | return arguments |
| 239 | |
| 240 | |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 241 | def do_run_suite(suite_name, arguments, use_shard=False, |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 242 | create_and_return=False): |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 243 | """Call run_suite to run a suite job, and return the suite job id. |
| 244 | |
| 245 | The script waits the suite job to finish before returning the suite job id. |
| 246 | Also it will echo the run_suite output to stdout. |
| 247 | |
| 248 | @param suite_name: Name of a suite, e.g., dummy. |
| 249 | @param arguments: Arguments for run_suite command. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 250 | @param use_shard: If true, suite is scheduled for shard board. |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 251 | @param create_and_return: If True, run_suite just creates the suite, print |
| 252 | the job id, then finish immediately. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 253 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 254 | @return: Suite job ID. |
| 255 | |
| 256 | """ |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 257 | if use_shard: |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 258 | board = arguments.shard_board |
| 259 | build = arguments.shard_build |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 260 | else: |
| 261 | board = arguments.board |
| 262 | build = arguments.build |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 263 | |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 264 | # Remove cros-version label to force provision. |
Shuqian Zhao | 7a49f1b | 2016-10-24 16:48:04 -0700 | [diff] [blame] | 265 | hosts = AFE.get_hosts(label=constants.Labels.BOARD_PREFIX+board, |
| 266 | locked=False) |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 267 | for host in hosts: |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 268 | labels_to_remove = [ |
| 269 | l for l in host.labels |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 270 | if l.startswith(provision.CROS_VERSION_PREFIX)] |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 271 | if labels_to_remove: |
| 272 | AFE.run('host_remove_labels', id=host.id, labels=labels_to_remove) |
Dan Shi | 47d3288 | 2014-12-22 16:25:05 -0800 | [diff] [blame] | 273 | |
Shuqian Zhao | d01fad0 | 2016-11-18 10:00:22 -0800 | [diff] [blame] | 274 | # Test repair work flow on shards, powerwash test will timeout after 7m. |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 275 | if use_shard and not create_and_return: |
Shuqian Zhao | d01fad0 | 2016-11-18 10:00:22 -0800 | [diff] [blame] | 276 | powerwash_dut_to_test_repair(host.hostname, timeout=420) |
Kevin Cheng | 6e4c264 | 2015-12-11 09:45:57 -0800 | [diff] [blame] | 277 | |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 278 | current_dir = os.path.dirname(os.path.realpath(__file__)) |
| 279 | cmd = [os.path.join(current_dir, RUN_SUITE_COMMAND), |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 280 | '-s', suite_name, |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 281 | '-b', board, |
| 282 | '-i', build, |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 283 | '-p', arguments.pool, |
Allen Li | 64edf06 | 2017-11-27 15:33:54 -0800 | [diff] [blame] | 284 | '--minimum_duts', str(arguments.num_duts[board])] |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 285 | if create_and_return: |
| 286 | cmd += ['-c'] |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 287 | |
| 288 | suite_job_id = None |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 289 | |
| 290 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
| 291 | stderr=subprocess.STDOUT) |
| 292 | |
| 293 | while True: |
| 294 | line = proc.stdout.readline() |
| 295 | |
| 296 | # Break when run_suite process completed. |
| 297 | if not line and proc.poll() != None: |
| 298 | break |
| 299 | print line.rstrip() |
Aviv Keshet | 0d679eb | 2017-11-08 13:25:01 -0800 | [diff] [blame] | 300 | _run_suite_output.append(line.rstrip()) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 301 | |
| 302 | if not suite_job_id: |
| 303 | m = re.match(SUITE_JOB_START_INFO_REGEX, line) |
| 304 | if m and m.group(1): |
| 305 | suite_job_id = int(m.group(1)) |
Aviv Keshet | 0d679eb | 2017-11-08 13:25:01 -0800 | [diff] [blame] | 306 | _all_suite_ids.append(suite_job_id) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 307 | |
| 308 | if not suite_job_id: |
| 309 | raise TestPushException('Failed to retrieve suite job ID.') |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 310 | |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 311 | # If create_and_return specified, wait for the suite to finish. |
| 312 | if create_and_return: |
| 313 | end = time.time() + arguments.timeout_min * 60 |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 314 | while not AFE.get_jobs(id=suite_job_id, finished=True): |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 315 | if time.time() < end: |
| 316 | time.sleep(10) |
| 317 | else: |
Dan Shi | efd403e | 2016-02-03 11:37:02 -0800 | [diff] [blame] | 318 | AFE.run('abort_host_queue_entries', job=suite_job_id) |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 319 | raise TestPushException( |
| 320 | 'Asynchronous suite triggered by create_and_return ' |
| 321 | 'flag has timed out after %d mins. Aborting it.' % |
| 322 | arguments.timeout_min) |
| 323 | |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 324 | print 'Suite job %s is completed.' % suite_job_id |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 325 | return suite_job_id |
| 326 | |
| 327 | |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 328 | def check_dut_image(build, suite_job_id): |
| 329 | """Confirm all DUTs used for the suite are imaged to expected build. |
| 330 | |
| 331 | @param build: Expected build to be imaged. |
| 332 | @param suite_job_id: job ID of the suite job. |
| 333 | @raise TestPushException: If a DUT does not have expected build imaged. |
| 334 | """ |
| 335 | print 'Checking image installed in DUTs...' |
| 336 | job_ids = [job.id for job in |
| 337 | models.Job.objects.filter(parent_job_id=suite_job_id)] |
| 338 | hqes = [models.HostQueueEntry.objects.filter(job_id=job_id)[0] |
| 339 | for job_id in job_ids] |
| 340 | hostnames = set([hqe.host.hostname for hqe in hqes]) |
| 341 | for hostname in hostnames: |
Prathmesh Prabhu | f10f41a | 2017-04-21 11:52:16 -0700 | [diff] [blame] | 342 | found_build = site_utils.get_build_from_afe(hostname, AFE) |
| 343 | if found_build != build: |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 344 | raise TestPushException('DUT is not imaged properly. Host %s has ' |
| 345 | 'build %s, while build %s is expected.' % |
Prathmesh Prabhu | f10f41a | 2017-04-21 11:52:16 -0700 | [diff] [blame] | 346 | (hostname, found_build, build)) |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 347 | |
| 348 | |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 349 | def test_suite(suite_name, expected_results, arguments, use_shard=False, |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 350 | create_and_return=False): |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 351 | """Call run_suite to start a suite job and verify results. |
| 352 | |
| 353 | @param suite_name: Name of a suite, e.g., dummy |
| 354 | @param expected_results: A dictionary of test name to test result. |
| 355 | @param arguments: Arguments for run_suite command. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 356 | @param use_shard: If true, suite is scheduled for shard board. |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 357 | @param create_and_return: If True, run_suite just creates the suite, print |
| 358 | the job id, then finish immediately. |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 359 | """ |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 360 | suite_job_id = do_run_suite(suite_name, arguments, use_shard, |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 361 | create_and_return) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 362 | |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 363 | # Confirm all DUTs used for the suite are imaged to expected build. |
Jakob Juelich | 8f14391 | 2014-10-10 14:08:05 -0700 | [diff] [blame] | 364 | # hqe.host_id for jobs running in shard is not synced back to master db, |
| 365 | # therefore, skip verifying dut build for jobs running in shard. |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 366 | build_expected = arguments.build |
| 367 | if not use_shard: |
Dan Shi | 81ddc42 | 2016-09-09 13:58:31 -0700 | [diff] [blame] | 368 | check_dut_image(build_expected, suite_job_id) |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 369 | |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 370 | # Verify test results are the expected results. |
| 371 | verify_test_results(suite_job_id, expected_results) |
| 372 | |
| 373 | |
| 374 | def verify_test_results(job_id, expected_results): |
| 375 | """Verify the test results with the expected results. |
| 376 | |
| 377 | @param job_id: id of the running jobs. For suite job, it is suite_job_id. |
| 378 | @param expected_results: A dictionary of test name to test result. |
| 379 | @raise TestPushException: If verify fails. |
| 380 | """ |
Dan Shi | a8da760 | 2014-05-09 15:18:15 -0700 | [diff] [blame] | 381 | print 'Comparing test results...' |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 382 | test_views = site_utils.get_test_views_from_tko(job_id, TKO) |
Xixuan Wu | 84a834f | 2018-08-10 15:22:26 -0700 | [diff] [blame] | 383 | summary = test_push_common.summarize_push(test_views, expected_results, |
| 384 | _IGNORED_TESTS) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 385 | |
| 386 | # Test link to log can be loaded. |
Shuqian Zhao | 327b695 | 2016-09-12 10:42:03 -0700 | [diff] [blame] | 387 | job_name = '%s-%s' % (job_id, getpass.getuser()) |
Prathmesh Prabhu | cd246f5 | 2018-01-03 13:45:48 -0800 | [diff] [blame] | 388 | log_link = URL_PATTERN % (rpc_client_lib.add_protocol(URL_HOST), job_name) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 389 | try: |
| 390 | urllib2.urlopen(log_link).read() |
| 391 | except urllib2.URLError: |
| 392 | summary.append('Failed to load page for link to log: %s.' % log_link) |
| 393 | |
| 394 | if summary: |
| 395 | raise TestPushException('\n'.join(summary)) |
| 396 | |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 397 | def test_suite_wrapper(queue, suite_name, expected_results, arguments, |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 398 | use_shard=False, create_and_return=False): |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 399 | """Wrapper to call test_suite. Handle exception and pipe it to parent |
| 400 | process. |
| 401 | |
| 402 | @param queue: Queue to save exception to be accessed by parent process. |
| 403 | @param suite_name: Name of a suite, e.g., dummy |
| 404 | @param expected_results: A dictionary of test name to test result. |
| 405 | @param arguments: Arguments for run_suite command. |
| 406 | @param use_shard: If true, suite is scheduled for shard board. |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 407 | @param create_and_return: If True, run_suite just creates the suite, print |
| 408 | the job id, then finish immediately. |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 409 | """ |
| 410 | try: |
Shuqian Zhao | d486477 | 2015-08-06 09:46:22 -0700 | [diff] [blame] | 411 | test_suite(suite_name, expected_results, arguments, use_shard, |
Richard Barnette | b12413a | 2018-04-25 01:00:27 +0000 | [diff] [blame] | 412 | create_and_return) |
Allen Li | 64edf06 | 2017-11-27 15:33:54 -0800 | [diff] [blame] | 413 | except Exception: |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 414 | # Store the whole exc_info leads to a PicklingError. |
| 415 | except_type, except_value, tb = sys.exc_info() |
| 416 | queue.put((except_type, except_value, traceback.extract_tb(tb))) |
| 417 | |
| 418 | |
Dan Shi | ef1a5c0 | 2015-04-07 17:37:09 -0700 | [diff] [blame] | 419 | def check_queue(queue): |
| 420 | """Check the queue for any exception being raised. |
| 421 | |
| 422 | @param queue: Queue used to store exception for parent process to access. |
| 423 | @raise: Any exception found in the queue. |
| 424 | """ |
| 425 | if queue.empty(): |
| 426 | return |
| 427 | exc_info = queue.get() |
| 428 | # Raise the exception with original backtrace. |
| 429 | print 'Original stack trace of the exception:\n%s' % exc_info[2] |
| 430 | raise exc_info[0](exc_info[1]) |
| 431 | |
| 432 | |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 433 | def _run_test_suites(arguments): |
| 434 | """Run the actual tests that comprise the test_push.""" |
| 435 | # Use daemon flag will kill child processes when parent process fails. |
| 436 | use_daemon = not arguments.continue_on_failure |
| 437 | queue = multiprocessing.Queue() |
| 438 | |
| 439 | push_to_prod_suite = multiprocessing.Process( |
| 440 | target=test_suite_wrapper, |
Xixuan Wu | 665cfad | 2018-08-10 10:08:14 -0700 | [diff] [blame] | 441 | args=(queue, PUSH_TO_PROD_SUITE, |
| 442 | test_push_common.EXPECTED_TEST_RESULTS, arguments)) |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 443 | push_to_prod_suite.daemon = use_daemon |
| 444 | push_to_prod_suite.start() |
| 445 | |
| 446 | # suite test with --create_and_return flag |
| 447 | asynchronous_suite = multiprocessing.Process( |
| 448 | target=test_suite_wrapper, |
Xixuan Wu | 665cfad | 2018-08-10 10:08:14 -0700 | [diff] [blame] | 449 | args=(queue, DUMMY_SUITE, |
| 450 | test_push_common.EXPECTED_TEST_RESULTS_DUMMY, |
| 451 | arguments, True, True)) |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 452 | asynchronous_suite.daemon = True |
| 453 | asynchronous_suite.start() |
| 454 | |
| 455 | while push_to_prod_suite.is_alive() or asynchronous_suite.is_alive(): |
| 456 | check_queue(queue) |
| 457 | time.sleep(5) |
| 458 | check_queue(queue) |
| 459 | push_to_prod_suite.join() |
| 460 | asynchronous_suite.join() |
| 461 | |
| 462 | |
Shuqian Zhao | 0de876d | 2018-01-31 11:53:34 -0800 | [diff] [blame] | 463 | def check_service_crash(respawn_limit, start_time): |
| 464 | """Check whether scheduler or host_scheduler crash during testing. |
| 465 | |
| 466 | Since the testing push is kicked off at the beginning of a given hour, the way |
| 467 | to check whether a service is crashed is to check whether the times of the |
| 468 | service being respawn during testing push is over the respawn_limit. |
| 469 | |
| 470 | @param respawn_limit: The maximum number of times the service is allowed to |
| 471 | be respawn. |
| 472 | @param start_time: The time that testing push is kicked off. |
| 473 | """ |
| 474 | def _parse(filename_prefix, filename): |
| 475 | """Helper method to parse the time of the log. |
| 476 | |
| 477 | @param filename_prefix: The prefix of the filename. |
| 478 | @param filename: The name of the log file. |
| 479 | """ |
| 480 | return datetime.datetime.strptime(filename[len(filename_prefix):], |
| 481 | "%Y-%m-%d-%H.%M.%S") |
| 482 | |
| 483 | services = ['scheduler', 'host_scheduler'] |
| 484 | logs = os.listdir('%s/logs/' % AUTOTEST_DIR) |
| 485 | curr_time = datetime.datetime.now() |
| 486 | |
| 487 | error_msg = '' |
| 488 | for service in services: |
| 489 | log_prefix = '%s.log.' % service |
| 490 | respawn_count = sum(1 for l in logs if l.startswith(log_prefix) |
| 491 | and start_time <= _parse(log_prefix, l) <= curr_time) |
| 492 | |
| 493 | if respawn_count > respawn_limit: |
| 494 | error_msg += ('%s has been respawned %s times during testing push at %s. ' |
| 495 | 'It is very likely crashed. Please check!\n' % |
| 496 | (service, respawn_count, |
| 497 | start_time.strftime("%Y-%m-%d-%H"))) |
| 498 | if error_msg: |
| 499 | raise TestPushException(error_msg) |
| 500 | |
| 501 | |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 502 | _SUCCESS_MSG = """ |
Aviv Keshet | f8c1789 | 2018-09-20 12:26:10 -0700 | [diff] [blame] | 503 | All staging tests completed successfully. |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 504 | |
| 505 | Instructions for pushing to prod are available at |
| 506 | https://goto.google.com/autotest-to-prod |
| 507 | """ |
| 508 | |
| 509 | |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 510 | def _main(arguments): |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 511 | """Run test and promote repo branches if tests succeed. |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 512 | |
| 513 | @param arguments: command line arguments. |
| 514 | """ |
Aviv Keshet | 0d679eb | 2017-11-08 13:25:01 -0800 | [diff] [blame] | 515 | |
| 516 | # TODO Use chromite.lib.parallel.Manager instead, to workaround the |
| 517 | # too-long-tmp-path problem. |
| 518 | mpmanager = multiprocessing.Manager() |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 519 | # These are globals used by other functions in this module to communicate |
| 520 | # back from worker processes. |
| 521 | global _run_suite_output |
Aviv Keshet | 0d679eb | 2017-11-08 13:25:01 -0800 | [diff] [blame] | 522 | _run_suite_output = mpmanager.list() |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 523 | global _all_suite_ids |
Aviv Keshet | 0d679eb | 2017-11-08 13:25:01 -0800 | [diff] [blame] | 524 | _all_suite_ids = mpmanager.list() |
| 525 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 526 | try: |
Shuqian Zhao | 0de876d | 2018-01-31 11:53:34 -0800 | [diff] [blame] | 527 | start_time = datetime.datetime.now() |
Shuqian Zhao | 06deae0 | 2017-02-28 09:55:59 -0800 | [diff] [blame] | 528 | reverify_all_push_duts() |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 529 | time.sleep(15) # Wait for the verify test to start. |
Shuqian Zhao | f239b31 | 2017-12-05 16:45:02 -0800 | [diff] [blame] | 530 | check_dut_inventory(arguments.num_duts, arguments.pool) |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 531 | _run_test_suites(arguments) |
Shuqian Zhao | 91b2014 | 2018-02-09 10:10:54 -0800 | [diff] [blame] | 532 | check_service_crash(arguments.service_respawn_limit, start_time) |
Jacob Kopczynski | 0ea4e04 | 2018-08-14 11:21:48 -0700 | [diff] [blame] | 533 | print _SUCCESS_MSG |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 534 | except Exception: |
Jacob Kopczynski | 0ea4e04 | 2018-08-14 11:21:48 -0700 | [diff] [blame] | 535 | # Abort running jobs unless flagged to continue when there is a failure. |
Shuqian Zhao | 676ed6f | 2016-09-21 14:20:50 -0700 | [diff] [blame] | 536 | if not arguments.continue_on_failure: |
Aviv Keshet | 0d679eb | 2017-11-08 13:25:01 -0800 | [diff] [blame] | 537 | for suite_id in _all_suite_ids: |
Shuqian Zhao | 676ed6f | 2016-09-21 14:20:50 -0700 | [diff] [blame] | 538 | if AFE.get_jobs(id=suite_id, finished=False): |
| 539 | AFE.run('abort_host_queue_entries', job=suite_id) |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 540 | raise |
| 541 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 542 | |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 543 | def main(): |
| 544 | """Entry point.""" |
Richard Barnette | 2af8221 | 2018-04-20 15:11:54 -0700 | [diff] [blame] | 545 | arguments = parse_arguments(sys.argv) |
Jacob Kopczynski | 5776514 | 2018-08-30 16:57:12 -0700 | [diff] [blame] | 546 | _main(arguments) |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 547 | |
Shuqian Zhao | 5696954 | 2017-05-30 12:56:57 -0700 | [diff] [blame] | 548 | |
Dan Shi | 7e04fa8 | 2013-07-25 15:08:48 -0700 | [diff] [blame] | 549 | if __name__ == '__main__': |
Prathmesh Prabhu | bac5be0 | 2018-01-09 11:38:23 -0800 | [diff] [blame] | 550 | main() |