Kuang-che Wu | 875c89a | 2020-01-08 14:30:55 +0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Kuang-che Wu | 6e4beca | 2018-06-27 17:45:02 +0800 | [diff] [blame] | 2 | # -*- coding: utf-8 -*- |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 3 | # Copyright 2017 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. |
Kuang-che Wu | 68db08a | 2018-03-30 11:50:34 +0800 | [diff] [blame] | 6 | """Helper script to manipulate chromeos DUT or query info.""" |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 7 | from __future__ import print_function |
| 8 | import argparse |
| 9 | import json |
| 10 | import logging |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 11 | import random |
| 12 | import time |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 13 | |
Kuang-che Wu | fe1e88a | 2019-09-10 21:52:25 +0800 | [diff] [blame] | 14 | from bisect_kit import cli |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 15 | from bisect_kit import common |
Kuang-che Wu | c45cfa4 | 2019-01-15 00:15:01 +0800 | [diff] [blame] | 16 | from bisect_kit import cros_lab_util |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 17 | from bisect_kit import cros_util |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 18 | from bisect_kit import errors |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 19 | from bisect_kit import util |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 20 | |
Zheng-Jie Chang | 17f36c8 | 2020-06-16 05:21:59 +0800 | [diff] [blame] | 21 | DEFAULT_DUT_POOL = 'DUT_POOL_QUOTA' |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 22 | logger = logging.getLogger(__name__) |
| 23 | |
| 24 | |
| 25 | def cmd_version_info(opts): |
| 26 | info = cros_util.version_info(opts.board, opts.version) |
| 27 | if opts.name: |
| 28 | if opts.name not in info: |
| 29 | logger.error('unknown name=%s', opts.name) |
| 30 | print(info[opts.name]) |
| 31 | else: |
| 32 | print(json.dumps(info, sort_keys=True, indent=4)) |
| 33 | |
| 34 | |
| 35 | def cmd_query_dut_board(opts): |
| 36 | assert cros_util.is_dut(opts.dut) |
| 37 | print(cros_util.query_dut_board(opts.dut)) |
| 38 | |
| 39 | |
| 40 | def cmd_reboot(opts): |
| 41 | assert cros_util.is_dut(opts.dut) |
| 42 | cros_util.reboot(opts.dut) |
| 43 | |
| 44 | |
Kuang-che Wu | c45cfa4 | 2019-01-15 00:15:01 +0800 | [diff] [blame] | 45 | def _get_label_by_prefix(info, prefix): |
| 46 | for label in info['Labels']: |
| 47 | if label.startswith(prefix + ':'): |
| 48 | return label |
| 49 | return None |
| 50 | |
| 51 | |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 52 | def cmd_lease_dut(opts): |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 53 | host = cros_lab_util.dut_host_name(opts.dut) |
Kuang-che Wu | 220cc16 | 2019-10-31 00:29:37 +0800 | [diff] [blame] | 54 | logger.info('trying to lease %s', host) |
| 55 | if cros_lab_util.skylab_lease_dut(host, opts.duration): |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 56 | logger.info('leased %s', host) |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 57 | else: |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 58 | raise Exception('unable to lease %s' % host) |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 59 | |
| 60 | |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 61 | def cmd_release_dut(opts): |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 62 | host = cros_lab_util.dut_host_name(opts.dut) |
Kuang-che Wu | 220cc16 | 2019-10-31 00:29:37 +0800 | [diff] [blame] | 63 | cros_lab_util.skylab_release_dut(host) |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 64 | logger.info('%s released', host) |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 65 | |
| 66 | |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 67 | def verify_dimensions_by_lab(dimensions): |
| 68 | result = [] |
| 69 | bots_dimensions = cros_lab_util.swarming_bots_dimensions() |
| 70 | for dimension in dimensions: |
| 71 | key, value = dimension.split(':', 1) |
| 72 | if value in bots_dimensions.get(key, []): |
| 73 | result.append(dimension) |
| 74 | else: |
| 75 | logger.warning('dimension=%s is unknown in the lab, typo? ignored', |
| 76 | dimension) |
| 77 | return result |
| 78 | |
| 79 | |
| 80 | def select_available_bot_randomly(dimensions, variants, is_busy=None): |
| 81 | bots = [] |
| 82 | for variant in variants: |
| 83 | # There might be thousand bots available, set 'limit' to reduce swarming |
| 84 | # API cost. This is not uniform random, but should be good enough. |
| 85 | bots += cros_lab_util.swarming_bots_list( |
| 86 | dimensions + [variant], is_busy=is_busy, limit=10) |
| 87 | if not bots: |
| 88 | return None |
| 89 | return random.choice(bots) |
| 90 | |
| 91 | |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 92 | def do_allocate_dut(opts): |
| 93 | """Helper of cmd_allocate_dut. |
| 94 | |
| 95 | Returns: |
| 96 | (todo, host) |
| 97 | todo: 'ready' or 'wait' |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 98 | host: leased host name |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 99 | """ |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 100 | if not opts.dut_name and not opts.pool: |
| 101 | raise errors.ArgumentError('--pool', |
| 102 | 'need to be specified if not --dut_name') |
Kuang-che Wu | 7e8abe6 | 2020-07-02 09:42:27 +0800 | [diff] [blame^] | 103 | if opts.version_hint: |
| 104 | for v in opts.version_hint.split(','): |
| 105 | if cros_util.is_cros_version(v) or cros_util.is_cros_snapshot_version(v): |
| 106 | continue |
| 107 | raise errors.ArgumentError( |
| 108 | '--version_hint', |
| 109 | 'should be Chrome OS version numbers, separated by comma') |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 110 | |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 111 | t0 = time.time() |
Zheng-Jie Chang | 17f36c8 | 2020-06-16 05:21:59 +0800 | [diff] [blame] | 112 | dimensions = ['dut_state:ready'] |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 113 | if not opts.dut_name: |
Zheng-Jie Chang | 17f36c8 | 2020-06-16 05:21:59 +0800 | [diff] [blame] | 114 | dimensions.append('label-pool:' + opts.pool) |
| 115 | |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 116 | variants = [] |
| 117 | if opts.board: |
| 118 | for board in opts.board.split(','): |
| 119 | if opts.version_hint: |
| 120 | versions = opts.version_hint.split(',') |
| 121 | if not all(cros_util.has_test_image(board, v) for v in versions): |
| 122 | logger.warning( |
| 123 | 'board=%s does not have prebuilt test image for %s, ignore', |
| 124 | board, opts.version_hint) |
| 125 | continue |
| 126 | variants.append('label-board:' + |
| 127 | cros_lab_util.normalize_board_name(board)) |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 128 | if opts.model: |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 129 | for model in opts.model.split(','): |
| 130 | variants.append('label-model:' + model) |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 131 | if opts.sku: |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 132 | for sku in opts.sku.split(','): |
| 133 | variants.append('label-hwid_sku:' + cros_lab_util.normalize_sku_name(sku)) |
| 134 | if opts.dut_name: |
| 135 | for dut_name in opts.dut_name.split(','): |
| 136 | variants.append('dut_name:' + dut_name) |
| 137 | |
| 138 | verified_variants = verify_dimensions_by_lab(variants) |
| 139 | if not verified_variants: |
| 140 | raise errors.NoDutAvailable('No valid constraints (%s)' % variants) |
| 141 | variants = verified_variants |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 142 | |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 143 | while True: |
Kuang-che Wu | 9501f34 | 2019-11-15 17:15:21 +0800 | [diff] [blame] | 144 | # Query every time because each iteration takes 10+ minutes |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 145 | bot = select_available_bot_randomly(dimensions, variants, is_busy=False) |
| 146 | if not bot: |
| 147 | bot = select_available_bot_randomly(dimensions, variants, is_busy=True) |
| 148 | if not bot: |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 149 | raise errors.NoDutAvailable( |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 150 | 'no bots satisfy constraints; all are in maintenance state?') |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 151 | |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 152 | host = bot['dimensions']['dut_name'][0] |
| 153 | logger.info('trying to lease %s', host) |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 154 | remaining_time = opts.time_limit - (time.time() - t0) |
| 155 | if remaining_time <= 0: |
| 156 | break |
| 157 | try: |
| 158 | if cros_lab_util.skylab_lease_dut( |
| 159 | host, opts.duration, timeout=remaining_time): |
| 160 | logger.info('leased %s (bot_id=%s)', host, bot['bot_id']) |
Kuang-che Wu | ceaa5ec | 2019-11-05 14:21:33 +0800 | [diff] [blame] | 161 | dut = host + '.cros' |
| 162 | if cros_util.is_good_dut(dut): |
| 163 | return 'ready', host |
| 164 | logger.warning('the leased DUT is broken; ' |
| 165 | 'return it and lease another one later') |
| 166 | cros_lab_util.skylab_release_dut(host) |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 167 | except util.TimeoutExpired: |
| 168 | break |
| 169 | time.sleep(1) |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 170 | |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 171 | logger.warning('unable to lease DUT in time limit') |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 172 | return 'wait', None |
| 173 | |
| 174 | |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 175 | def cmd_allocate_dut(opts): |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 176 | leased_dut = None |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 177 | try: |
| 178 | todo, host = do_allocate_dut(opts) |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 179 | leased_dut = host + '.cros' if host else None |
Kuang-che Wu | 611939f | 2020-04-14 19:12:50 +0800 | [diff] [blame] | 180 | result = {'result': todo, 'leased_dut': leased_dut} |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 181 | print(json.dumps(result)) |
| 182 | except Exception as e: |
| 183 | logger.exception('cmd_allocate_dut failed') |
| 184 | exception_name = e.__class__.__name__ |
| 185 | result = { |
| 186 | 'result': 'failed', |
| 187 | 'exception': exception_name, |
| 188 | 'text': str(e), |
| 189 | } |
| 190 | print(json.dumps(result)) |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 191 | |
| 192 | |
Kuang-che Wu | a8c3c3e | 2019-08-28 18:49:28 +0800 | [diff] [blame] | 193 | def cmd_repair_dut(opts): |
| 194 | cros_lab_util.repair(opts.dut) |
| 195 | |
| 196 | |
Kuang-che Wu | fe1e88a | 2019-09-10 21:52:25 +0800 | [diff] [blame] | 197 | @cli.fatal_error_handler |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 198 | def main(): |
| 199 | common.init() |
| 200 | parser = argparse.ArgumentParser() |
Kuang-che Wu | fe1e88a | 2019-09-10 21:52:25 +0800 | [diff] [blame] | 201 | cli.patching_argparser_exit(parser) |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 202 | common.add_common_arguments(parser) |
| 203 | subparsers = parser.add_subparsers( |
| 204 | dest='command', title='commands', metavar='<command>') |
| 205 | |
| 206 | parser_version_info = subparsers.add_parser( |
| 207 | 'version_info', |
| 208 | help='Query version info of given chromeos build', |
| 209 | description='Given chromeos `board` and `version`, ' |
| 210 | 'print version information of components.') |
| 211 | parser_version_info.add_argument( |
| 212 | 'board', help='ChromeOS board name, like "samus".') |
| 213 | parser_version_info.add_argument( |
| 214 | 'version', |
| 215 | type=cros_util.argtype_cros_version, |
| 216 | help='ChromeOS version, like "9876.0.0" or "R62-9876.0.0"') |
| 217 | parser_version_info.add_argument( |
| 218 | 'name', |
| 219 | nargs='?', |
| 220 | help='Component name. If specified, output its version string. ' |
| 221 | 'Otherwise output all version info as dict in json format.') |
| 222 | parser_version_info.set_defaults(func=cmd_version_info) |
| 223 | |
| 224 | parser_query_dut_board = subparsers.add_parser( |
| 225 | 'query_dut_board', help='Query board name of given DUT') |
| 226 | parser_query_dut_board.add_argument('dut') |
| 227 | parser_query_dut_board.set_defaults(func=cmd_query_dut_board) |
| 228 | |
| 229 | parser_reboot = subparsers.add_parser( |
| 230 | 'reboot', |
| 231 | help='Reboot a DUT', |
| 232 | description='Reboot a DUT and verify the reboot is successful.') |
| 233 | parser_reboot.add_argument('dut') |
| 234 | parser_reboot.set_defaults(func=cmd_reboot) |
| 235 | |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 236 | parser_lease_dut = subparsers.add_parser( |
| 237 | 'lease_dut', |
| 238 | help='Lease a DUT in the lab', |
| 239 | description='Lease a DUT in the lab. ' |
| 240 | 'This is implemented by `skylab lease-dut` with additional checking.') |
| 241 | # "skylab lease-dut" doesn't take reason, so this is not required=True. |
| 242 | parser_lease_dut.add_argument('--session', help='session name') |
| 243 | parser_lease_dut.add_argument('dut') |
| 244 | parser_lease_dut.add_argument( |
| 245 | '--duration', |
| 246 | type=float, |
| 247 | help='duration in seconds; will be round to minutes') |
| 248 | parser_lease_dut.set_defaults(func=cmd_lease_dut) |
| 249 | |
| 250 | parser_release_dut = subparsers.add_parser( |
| 251 | 'release_dut', |
| 252 | help='Release a DUT in the lab', |
| 253 | description='Release a DUT in the lab. ' |
| 254 | 'This is implemented by `skylab release-dut` with additional checking.') |
| 255 | parser_release_dut.add_argument('--session', help='session name') |
| 256 | parser_release_dut.add_argument('dut') |
| 257 | parser_release_dut.set_defaults(func=cmd_release_dut) |
| 258 | |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 259 | parser_allocate_dut = subparsers.add_parser( |
| 260 | 'allocate_dut', |
| 261 | help='Allocate a DUT in the lab', |
Kuang-che Wu | ca45646 | 2019-11-04 17:32:55 +0800 | [diff] [blame] | 262 | description='Allocate a DUT in the lab. It will lease a DUT in the lab ' |
| 263 | 'for bisecting. The caller (bisect-kit runner) of this command should ' |
| 264 | 'retry this command again later if no DUT available now.') |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 265 | parser_allocate_dut.add_argument( |
| 266 | '--session', required=True, help='session name') |
| 267 | parser_allocate_dut.add_argument( |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 268 | '--pool', help='Pool to search DUT', default=DEFAULT_DUT_POOL) |
| 269 | group = parser_allocate_dut.add_mutually_exclusive_group(required=True) |
| 270 | group.add_argument('--board', help='allocation criteria; comma separated') |
| 271 | group.add_argument('--model', help='allocation criteria; comma separated') |
| 272 | group.add_argument('--sku', help='allocation criteria; comma separated') |
| 273 | group.add_argument('--dut_name', help='allocation criteria; comma separated') |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 274 | parser_allocate_dut.add_argument( |
Kuang-che Wu | 1e56ce2 | 2020-06-29 11:21:51 +0800 | [diff] [blame] | 275 | '--version_hint', help='chromeos version; comma separated') |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 276 | # Pubsub ack deadline is 10 minutes (b/143663659). Default 9 minutes with 1 |
| 277 | # minute buffer. |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 278 | parser_allocate_dut.add_argument( |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 279 | '--time_limit', |
| 280 | type=int, |
Kuang-che Wu | c26dcdf | 2019-11-01 16:30:06 +0800 | [diff] [blame] | 281 | default=9 * 60, |
Kuang-che Wu | 0c9b794 | 2019-10-30 16:55:39 +0800 | [diff] [blame] | 282 | help='Time limit to attempt lease in seconds (default: %(default)s)') |
| 283 | parser_allocate_dut.add_argument( |
| 284 | '--duration', |
| 285 | type=float, |
| 286 | help='lease duration in seconds; will be round to minutes') |
Kuang-che Wu | 22aa9d4 | 2019-01-25 10:35:33 +0800 | [diff] [blame] | 287 | parser_allocate_dut.set_defaults(func=cmd_allocate_dut) |
| 288 | |
Kuang-che Wu | a8c3c3e | 2019-08-28 18:49:28 +0800 | [diff] [blame] | 289 | parser_repair_dut = subparsers.add_parser( |
| 290 | 'repair_dut', |
| 291 | help='Repair a DUT in the lab', |
| 292 | description='Repair a DUT in the lab. ' |
| 293 | 'This is simply wrapper of "deploy repair" with additional checking.') |
| 294 | parser_repair_dut.add_argument('dut') |
| 295 | parser_repair_dut.set_defaults(func=cmd_repair_dut) |
| 296 | |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 297 | opts = parser.parse_args() |
| 298 | common.config_logging(opts) |
Kuang-che Wu | d3a4e84 | 2019-12-11 12:15:23 +0800 | [diff] [blame] | 299 | |
| 300 | # It's optional by default since python3. |
| 301 | if not opts.command: |
| 302 | parser.error('command is missing') |
Kuang-che Wu | 2ea804f | 2017-11-28 17:11:41 +0800 | [diff] [blame] | 303 | opts.func(opts) |
| 304 | |
| 305 | |
| 306 | if __name__ == '__main__': |
| 307 | main() |