blob: 0af8e873425703a40949827fa654fbb8d060432e [file] [log] [blame]
Kuang-che Wu875c89a2020-01-08 14:30:55 +08001#!/usr/bin/env python3
Kuang-che Wu6e4beca2018-06-27 17:45:02 +08002# -*- coding: utf-8 -*-
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08003# 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 Wu68db08a2018-03-30 11:50:34 +08006"""Helper script to manipulate chromeos DUT or query info."""
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08007from __future__ import print_function
8import argparse
9import json
10import logging
Kuang-che Wu0c9b7942019-10-30 16:55:39 +080011import random
12import time
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080013
Kuang-che Wufe1e88a2019-09-10 21:52:25 +080014from bisect_kit import cli
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080015from bisect_kit import common
Kuang-che Wuc45cfa42019-01-15 00:15:01 +080016from bisect_kit import cros_lab_util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080017from bisect_kit import cros_util
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080018from bisect_kit import errors
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +080019from bisect_kit import util
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080020
Zheng-Jie Chang17f36c82020-06-16 05:21:59 +080021DEFAULT_DUT_POOL = 'DUT_POOL_QUOTA'
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080022logger = logging.getLogger(__name__)
23
24
25def 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
35def cmd_query_dut_board(opts):
36 assert cros_util.is_dut(opts.dut)
37 print(cros_util.query_dut_board(opts.dut))
38
39
40def cmd_reboot(opts):
41 assert cros_util.is_dut(opts.dut)
42 cros_util.reboot(opts.dut)
43
44
Kuang-che Wuc45cfa42019-01-15 00:15:01 +080045def _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 Wuca456462019-11-04 17:32:55 +080052def cmd_lease_dut(opts):
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080053 host = cros_lab_util.dut_host_name(opts.dut)
Kuang-che Wu220cc162019-10-31 00:29:37 +080054 logger.info('trying to lease %s', host)
55 if cros_lab_util.skylab_lease_dut(host, opts.duration):
Kuang-che Wuca456462019-11-04 17:32:55 +080056 logger.info('leased %s', host)
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080057 else:
Kuang-che Wuca456462019-11-04 17:32:55 +080058 raise Exception('unable to lease %s' % host)
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080059
60
Kuang-che Wuca456462019-11-04 17:32:55 +080061def cmd_release_dut(opts):
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080062 host = cros_lab_util.dut_host_name(opts.dut)
Kuang-che Wu220cc162019-10-31 00:29:37 +080063 cros_lab_util.skylab_release_dut(host)
Kuang-che Wuca456462019-11-04 17:32:55 +080064 logger.info('%s released', host)
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080065
66
Kuang-che Wu1e56ce22020-06-29 11:21:51 +080067def 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
80def 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 Wu22aa9d42019-01-25 10:35:33 +080092def do_allocate_dut(opts):
93 """Helper of cmd_allocate_dut.
94
95 Returns:
96 (todo, host)
97 todo: 'ready' or 'wait'
Kuang-che Wuca456462019-11-04 17:32:55 +080098 host: leased host name
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080099 """
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800100 if not opts.dut_name and not opts.pool:
101 raise errors.ArgumentError('--pool',
102 'need to be specified if not --dut_name')
103 for v in opts.version_hint.split(','):
104 if cros_util.is_cros_version(v) or cros_util.is_cros_snapshot_version(v):
105 continue
Zheng-Jie Chang17f36c82020-06-16 05:21:59 +0800106 raise errors.ArgumentError(
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800107 '--version_hint',
108 'should be Chrome OS version numbers, separated by comma')
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800109
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +0800110 t0 = time.time()
Zheng-Jie Chang17f36c82020-06-16 05:21:59 +0800111 dimensions = ['dut_state:ready']
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800112 if not opts.dut_name:
Zheng-Jie Chang17f36c82020-06-16 05:21:59 +0800113 dimensions.append('label-pool:' + opts.pool)
114
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800115 variants = []
116 if opts.board:
117 for board in opts.board.split(','):
118 if opts.version_hint:
119 versions = opts.version_hint.split(',')
120 if not all(cros_util.has_test_image(board, v) for v in versions):
121 logger.warning(
122 'board=%s does not have prebuilt test image for %s, ignore',
123 board, opts.version_hint)
124 continue
125 variants.append('label-board:' +
126 cros_lab_util.normalize_board_name(board))
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800127 if opts.model:
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800128 for model in opts.model.split(','):
129 variants.append('label-model:' + model)
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800130 if opts.sku:
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800131 for sku in opts.sku.split(','):
132 variants.append('label-hwid_sku:' + cros_lab_util.normalize_sku_name(sku))
133 if opts.dut_name:
134 for dut_name in opts.dut_name.split(','):
135 variants.append('dut_name:' + dut_name)
136
137 verified_variants = verify_dimensions_by_lab(variants)
138 if not verified_variants:
139 raise errors.NoDutAvailable('No valid constraints (%s)' % variants)
140 variants = verified_variants
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800141
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +0800142 while True:
Kuang-che Wu9501f342019-11-15 17:15:21 +0800143 # Query every time because each iteration takes 10+ minutes
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800144 bot = select_available_bot_randomly(dimensions, variants, is_busy=False)
145 if not bot:
146 bot = select_available_bot_randomly(dimensions, variants, is_busy=True)
147 if not bot:
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800148 raise errors.NoDutAvailable(
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800149 'no bots satisfy constraints; all are in maintenance state?')
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800150
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800151 host = bot['dimensions']['dut_name'][0]
152 logger.info('trying to lease %s', host)
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +0800153 remaining_time = opts.time_limit - (time.time() - t0)
154 if remaining_time <= 0:
155 break
156 try:
157 if cros_lab_util.skylab_lease_dut(
158 host, opts.duration, timeout=remaining_time):
159 logger.info('leased %s (bot_id=%s)', host, bot['bot_id'])
Kuang-che Wuceaa5ec2019-11-05 14:21:33 +0800160 dut = host + '.cros'
161 if cros_util.is_good_dut(dut):
162 return 'ready', host
163 logger.warning('the leased DUT is broken; '
164 'return it and lease another one later')
165 cros_lab_util.skylab_release_dut(host)
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +0800166 except util.TimeoutExpired:
167 break
168 time.sleep(1)
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800169
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +0800170 logger.warning('unable to lease DUT in time limit')
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800171 return 'wait', None
172
173
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800174def cmd_allocate_dut(opts):
Kuang-che Wuca456462019-11-04 17:32:55 +0800175 leased_dut = None
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800176 try:
177 todo, host = do_allocate_dut(opts)
Kuang-che Wuca456462019-11-04 17:32:55 +0800178 leased_dut = host + '.cros' if host else None
Kuang-che Wu611939f2020-04-14 19:12:50 +0800179 result = {'result': todo, 'leased_dut': leased_dut}
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800180 print(json.dumps(result))
181 except Exception as e:
182 logger.exception('cmd_allocate_dut failed')
183 exception_name = e.__class__.__name__
184 result = {
185 'result': 'failed',
186 'exception': exception_name,
187 'text': str(e),
188 }
189 print(json.dumps(result))
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800190
191
Kuang-che Wua8c3c3e2019-08-28 18:49:28 +0800192def cmd_repair_dut(opts):
193 cros_lab_util.repair(opts.dut)
194
195
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800196@cli.fatal_error_handler
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800197def main():
198 common.init()
199 parser = argparse.ArgumentParser()
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800200 cli.patching_argparser_exit(parser)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800201 common.add_common_arguments(parser)
202 subparsers = parser.add_subparsers(
203 dest='command', title='commands', metavar='<command>')
204
205 parser_version_info = subparsers.add_parser(
206 'version_info',
207 help='Query version info of given chromeos build',
208 description='Given chromeos `board` and `version`, '
209 'print version information of components.')
210 parser_version_info.add_argument(
211 'board', help='ChromeOS board name, like "samus".')
212 parser_version_info.add_argument(
213 'version',
214 type=cros_util.argtype_cros_version,
215 help='ChromeOS version, like "9876.0.0" or "R62-9876.0.0"')
216 parser_version_info.add_argument(
217 'name',
218 nargs='?',
219 help='Component name. If specified, output its version string. '
220 'Otherwise output all version info as dict in json format.')
221 parser_version_info.set_defaults(func=cmd_version_info)
222
223 parser_query_dut_board = subparsers.add_parser(
224 'query_dut_board', help='Query board name of given DUT')
225 parser_query_dut_board.add_argument('dut')
226 parser_query_dut_board.set_defaults(func=cmd_query_dut_board)
227
228 parser_reboot = subparsers.add_parser(
229 'reboot',
230 help='Reboot a DUT',
231 description='Reboot a DUT and verify the reboot is successful.')
232 parser_reboot.add_argument('dut')
233 parser_reboot.set_defaults(func=cmd_reboot)
234
Kuang-che Wuca456462019-11-04 17:32:55 +0800235 parser_lease_dut = subparsers.add_parser(
236 'lease_dut',
237 help='Lease a DUT in the lab',
238 description='Lease a DUT in the lab. '
239 'This is implemented by `skylab lease-dut` with additional checking.')
240 # "skylab lease-dut" doesn't take reason, so this is not required=True.
241 parser_lease_dut.add_argument('--session', help='session name')
242 parser_lease_dut.add_argument('dut')
243 parser_lease_dut.add_argument(
244 '--duration',
245 type=float,
246 help='duration in seconds; will be round to minutes')
247 parser_lease_dut.set_defaults(func=cmd_lease_dut)
248
249 parser_release_dut = subparsers.add_parser(
250 'release_dut',
251 help='Release a DUT in the lab',
252 description='Release a DUT in the lab. '
253 'This is implemented by `skylab release-dut` with additional checking.')
254 parser_release_dut.add_argument('--session', help='session name')
255 parser_release_dut.add_argument('dut')
256 parser_release_dut.set_defaults(func=cmd_release_dut)
257
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800258 parser_allocate_dut = subparsers.add_parser(
259 'allocate_dut',
260 help='Allocate a DUT in the lab',
Kuang-che Wuca456462019-11-04 17:32:55 +0800261 description='Allocate a DUT in the lab. It will lease a DUT in the lab '
262 'for bisecting. The caller (bisect-kit runner) of this command should '
263 'retry this command again later if no DUT available now.')
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800264 parser_allocate_dut.add_argument(
265 '--session', required=True, help='session name')
266 parser_allocate_dut.add_argument(
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800267 '--pool', help='Pool to search DUT', default=DEFAULT_DUT_POOL)
268 group = parser_allocate_dut.add_mutually_exclusive_group(required=True)
269 group.add_argument('--board', help='allocation criteria; comma separated')
270 group.add_argument('--model', help='allocation criteria; comma separated')
271 group.add_argument('--sku', help='allocation criteria; comma separated')
272 group.add_argument('--dut_name', help='allocation criteria; comma separated')
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800273 parser_allocate_dut.add_argument(
Kuang-che Wu1e56ce22020-06-29 11:21:51 +0800274 '--version_hint', help='chromeos version; comma separated')
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +0800275 # Pubsub ack deadline is 10 minutes (b/143663659). Default 9 minutes with 1
276 # minute buffer.
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800277 parser_allocate_dut.add_argument(
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800278 '--time_limit',
279 type=int,
Kuang-che Wuc26dcdf2019-11-01 16:30:06 +0800280 default=9 * 60,
Kuang-che Wu0c9b7942019-10-30 16:55:39 +0800281 help='Time limit to attempt lease in seconds (default: %(default)s)')
282 parser_allocate_dut.add_argument(
283 '--duration',
284 type=float,
285 help='lease duration in seconds; will be round to minutes')
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800286 parser_allocate_dut.set_defaults(func=cmd_allocate_dut)
287
Kuang-che Wua8c3c3e2019-08-28 18:49:28 +0800288 parser_repair_dut = subparsers.add_parser(
289 'repair_dut',
290 help='Repair a DUT in the lab',
291 description='Repair a DUT in the lab. '
292 'This is simply wrapper of "deploy repair" with additional checking.')
293 parser_repair_dut.add_argument('dut')
294 parser_repair_dut.set_defaults(func=cmd_repair_dut)
295
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800296 opts = parser.parse_args()
297 common.config_logging(opts)
Kuang-che Wud3a4e842019-12-11 12:15:23 +0800298
299 # It's optional by default since python3.
300 if not opts.command:
301 parser.error('command is missing')
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800302 opts.func(opts)
303
304
305if __name__ == '__main__':
306 main()