blob: 79bfb27b70c5cfb6db3e397354e04dd4dd960ebd [file] [log] [blame]
Kuang-che Wu2ea804f2017-11-28 17:11:41 +08001#!/usr/bin/env python2
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
Kuang-che Wuc45cfa42019-01-15 00:15:01 +08009import collections
10import functools
Kuang-che Wu2ea804f2017-11-28 17:11:41 +080011import json
12import logging
13
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 Wu2ea804f2017-11-28 17:11:41 +080019
20logger = logging.getLogger(__name__)
21
22
23def cmd_version_info(opts):
24 info = cros_util.version_info(opts.board, opts.version)
25 if opts.name:
26 if opts.name not in info:
27 logger.error('unknown name=%s', opts.name)
28 print(info[opts.name])
29 else:
30 print(json.dumps(info, sort_keys=True, indent=4))
31
32
33def cmd_query_dut_board(opts):
34 assert cros_util.is_dut(opts.dut)
35 print(cros_util.query_dut_board(opts.dut))
36
37
38def cmd_reboot(opts):
39 assert cros_util.is_dut(opts.dut)
40 cros_util.reboot(opts.dut)
41
42
Kuang-che Wuc45cfa42019-01-15 00:15:01 +080043def _get_label_by_prefix(info, prefix):
44 for label in info['Labels']:
45 if label.startswith(prefix + ':'):
46 return label
47 return None
48
49
50def cmd_search_dut(opts):
51 labels = []
52 if opts.label:
53 labels += opts.label.split(',')
54
55 def match(info):
56 if not opts.condition:
57 return True
58
59 for label in info['Labels']:
60 for condition in opts.condition:
61 if ':' in condition:
62 keys = [condition]
63 else:
64 keys = [
65 'board:' + condition,
66 'model:' + condition,
67 'sku:' + condition,
68 ]
69 for key in keys:
70 if label.lower().startswith(key.lower()):
71 return True
72 return False
73
74 for pool in opts.pools.split(','):
75 print('pool:' + pool)
76
77 group = collections.defaultdict(dict)
78 counter = collections.defaultdict(collections.Counter)
79 for host, info in cros_lab_util.list_host(labels=labels +
80 ['pool:' + pool]).items():
81 if not match(info):
82 continue
83
84 model = _get_label_by_prefix(info, 'model')
Kuang-che Wu22aa9d42019-01-25 10:35:33 +080085 if info.get('Locked'):
Kuang-che Wuc45cfa42019-01-15 00:15:01 +080086 state = 'Locked'
87 else:
88 state = info['Status']
89 if state not in group[model]:
90 group[model][state] = {}
91 group[model][state][host] = info
92 counter[model][state] += 1
93
94 def availability(counter, model):
95 return -counter[model]['Ready']
96
97 for model in sorted(group, key=functools.partial(availability, counter)):
98 print('%s\t%s' % (model, dict(counter[model])))
99 for host, info in group[model].get('Ready', {}).items():
100 print('\t%s\t%s' % (host, _get_label_by_prefix(info, 'board')))
101 if not opts.all:
102 break
103 print('-' * 30)
104
105 if not opts.all:
106 print('Only list one host per model by default. Use --all to output all.')
107
108
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800109def cmd_lock_dut(opts):
110 host = cros_lab_util.dut_host_name(opts.dut)
111 if opts.session:
112 reason = cros_lab_util.make_lock_reason(opts.session)
113 else:
114 reason = opts.reason
115 cros_lab_util.lock_host(host, reason)
116 logger.info('%s locked', host)
117
118
119def cmd_unlock_dut(opts):
120 host = cros_lab_util.dut_host_name(opts.dut)
121 hosts = cros_lab_util.list_host(host=host)
122 assert hosts, 'host=%s does not exist' % host
123 info = hosts[host]
124 assert info['Locked'], '%s is not locked?' % host
125 if opts.session:
126 reason = cros_lab_util.make_lock_reason(opts.session)
127 assert info['Lock Reason'] == reason
128
129 cros_lab_util.unlock_host(host)
130 logger.info('%s unlocked', host)
131
132
133def do_allocate_dut(opts):
134 """Helper of cmd_allocate_dut.
135
136 Returns:
137 (todo, host)
138 todo: 'ready' or 'wait'
139 host: locked host name
140 """
141 if not opts.model and not opts.sku:
142 raise errors.ArgumentError('--model or --sku', 'need to be specified')
143
144 reason = cros_lab_util.make_lock_reason(opts.session)
145 waiting = None
146 if opts.locked_dut:
147 host = cros_lab_util.dut_host_name(opts.locked_dut)
148 logger.info('check current status of previous locked host %s', host)
149 hosts = cros_lab_util.list_host(host=host)
150 if not hosts:
151 logger.warning('we have ever locked %s but it disappeared now', host)
152 waiting = None
153 else:
154 waiting = hosts[host]
155
156 logger.info('current status=%r, locked=%s, by=%s, reason=%r',
157 waiting['Status'], waiting.get('Locked'),
158 waiting.get('Locked by'), waiting.get('Lock Reason'))
159 if not waiting['Locked'] or waiting['Lock Reason'] != reason:
160 # Special case: not locked by us, so do not unlock it.
161 opts.locked_dut = None
162 raise errors.ExternalError(
163 '%s should be locked by us with reason=%r' % (host, reason))
164
165 if waiting['Status'] == cros_lab_util.READY_STATE:
166 return 'ready', host
167 elif waiting['Status'] in cros_lab_util.BAD_STATES:
168 logger.warning('locked host=%s, became %s; give it up', host,
169 waiting['Status'])
170 waiting = None
171
172 # No matter we have locked a host or not, check if any other hosts are
173 # available.
174 candidates = cros_lab_util.seek_host(
175 opts.pools.split(','), opts.model, opts.sku, opts.label)
176
177 for info in candidates:
178 if info['Locked']:
179 continue
180
181 to_lock = False
182 if info['Status'] == cros_lab_util.READY_STATE:
183 if waiting:
184 logger.info(
185 'although we locked and are waiting for %s, '
186 'we found another host=%s is ready', waiting['Host'], info['Host'])
187 to_lock = True
188 elif info['Status'] in cros_lab_util.GOOD_STATES and not waiting:
189 to_lock = True
190
191 if not to_lock:
192 continue
193
194 after_lock = cros_lab_util.lock_host(info['Host'], reason)
195
196 if after_lock['Status'] == cros_lab_util.READY_STATE:
197 # Lucky, became ready just before we lock.
198 return 'ready', after_lock['Host']
199
200 if waiting:
201 logger.info('but %s became %s just before we lock it', after_lock['Host'],
202 after_lock['Status'])
203 cros_lab_util.unlock_host(after_lock['Host'])
204 continue
205
206 logger.info('locked %s and wait it ready', after_lock['Host'])
207 return 'wait', after_lock['Host']
208
209 if waiting:
210 logger.info('continue to wait %s', waiting['Host'])
211 return 'wait', waiting['Host']
212
213 if not candidates:
214 raise errors.NoDutAvailable('all are in bad states')
215
216 logger.info(
217 'we did not lock any hosts, but are waiting %d hosts in '
218 'transient states', len(candidates))
219 return 'wait', None
220
221
222def cmd_allocate_dut(opts):
223 locked_dut = None
224 try:
225 todo, host = do_allocate_dut(opts)
226 locked_dut = host + '.cros' if host else None
227 result = {'result': todo, 'locked_dut': locked_dut}
228 print(json.dumps(result))
229 except Exception as e:
230 logger.exception('cmd_allocate_dut failed')
231 exception_name = e.__class__.__name__
232 result = {
233 'result': 'failed',
234 'exception': exception_name,
235 'text': str(e),
236 }
237 print(json.dumps(result))
238 finally:
239 # For any reasons, if we locked a new DUT, unlock the previous one.
240 if opts.locked_dut and opts.locked_dut != locked_dut:
241 cros_lab_util.unlock_host(cros_lab_util.dut_host_name(opts.locked_dut))
242
243
Kuang-che Wua8c3c3e2019-08-28 18:49:28 +0800244def cmd_repair_dut(opts):
245 cros_lab_util.repair(opts.dut)
246
247
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800248@cli.fatal_error_handler
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800249def main():
250 common.init()
251 parser = argparse.ArgumentParser()
Kuang-che Wufe1e88a2019-09-10 21:52:25 +0800252 cli.patching_argparser_exit(parser)
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800253 common.add_common_arguments(parser)
254 subparsers = parser.add_subparsers(
255 dest='command', title='commands', metavar='<command>')
256
257 parser_version_info = subparsers.add_parser(
258 'version_info',
259 help='Query version info of given chromeos build',
260 description='Given chromeos `board` and `version`, '
261 'print version information of components.')
262 parser_version_info.add_argument(
263 'board', help='ChromeOS board name, like "samus".')
264 parser_version_info.add_argument(
265 'version',
266 type=cros_util.argtype_cros_version,
267 help='ChromeOS version, like "9876.0.0" or "R62-9876.0.0"')
268 parser_version_info.add_argument(
269 'name',
270 nargs='?',
271 help='Component name. If specified, output its version string. '
272 'Otherwise output all version info as dict in json format.')
273 parser_version_info.set_defaults(func=cmd_version_info)
274
275 parser_query_dut_board = subparsers.add_parser(
276 'query_dut_board', help='Query board name of given DUT')
277 parser_query_dut_board.add_argument('dut')
278 parser_query_dut_board.set_defaults(func=cmd_query_dut_board)
279
280 parser_reboot = subparsers.add_parser(
281 'reboot',
282 help='Reboot a DUT',
283 description='Reboot a DUT and verify the reboot is successful.')
284 parser_reboot.add_argument('dut')
285 parser_reboot.set_defaults(func=cmd_reboot)
286
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800287 parser_lock_dut = subparsers.add_parser(
288 'lock_dut',
289 help='Lock a DUT in the lab',
290 description='Lock a DUT in the lab. '
291 'This is simply wrapper of "atest" with additional checking.')
292 group = parser_lock_dut.add_mutually_exclusive_group(required=True)
293 group.add_argument('--session', help='session name; for creating lock reason')
294 group.add_argument('--reason', help='specify lock reason manually')
295 parser_lock_dut.add_argument('dut')
296 parser_lock_dut.set_defaults(func=cmd_lock_dut)
297
298 parser_unlock_dut = subparsers.add_parser(
299 'unlock_dut',
300 help='Unlock a DUT in the lab',
301 description='Unlock a DUT in the lab. '
302 'This is simply wrapper of "atest" with additional checking.')
303 parser_unlock_dut.add_argument(
304 '--session', help='session name; for checking lock reason before unlock')
305 parser_unlock_dut.add_argument('dut')
306 parser_unlock_dut.set_defaults(func=cmd_unlock_dut)
307
308 parser_allocate_dut = subparsers.add_parser(
309 'allocate_dut',
310 help='Allocate a DUT in the lab',
311 description='Allocate a DUT in the lab. It will lock a DUT in the lab '
312 'for bisecting. If no DUT is available (ready), it will lock one. The '
313 'caller (bisect-kit runner) of this command should keep note of the '
314 'locked DUT name and retry this command again later.')
315 parser_allocate_dut.add_argument(
316 '--session', required=True, help='session name')
317 parser_allocate_dut.add_argument(
318 '--pools', required=True, help='Pools to search dut, comma separated')
319 parser_allocate_dut.add_argument('--model', help='allocation criteria')
320 parser_allocate_dut.add_argument('--sku', help='allocation criteria')
321 parser_allocate_dut.add_argument(
322 '--label', '-b', help='Additional required labels, comma separated')
323 parser_allocate_dut.add_argument(
324 '--locked_dut', help='Locked DUT name by last run')
325 parser_allocate_dut.set_defaults(func=cmd_allocate_dut)
326
Kuang-che Wua8c3c3e2019-08-28 18:49:28 +0800327 parser_repair_dut = subparsers.add_parser(
328 'repair_dut',
329 help='Repair a DUT in the lab',
330 description='Repair a DUT in the lab. '
331 'This is simply wrapper of "deploy repair" with additional checking.')
332 parser_repair_dut.add_argument('dut')
333 parser_repair_dut.set_defaults(func=cmd_repair_dut)
334
Kuang-che Wuc45cfa42019-01-15 00:15:01 +0800335 parser_search_dut = subparsers.add_parser(
336 'search_dut',
337 help='Search DUT with conditions',
338 description='Search hosts in the lab. Grouped by model and ordered by '
339 'availability. When your test could be run on many models, this command '
340 'help you to decide what model to use.')
341 parser_search_dut.add_argument(
342 '--pools',
343 default='performance,crosperf,suites',
344 help='Pools to search, comma separated. The searching will be performed '
345 'for each pool.')
346 parser_search_dut.add_argument(
347 '--label',
348 '-b',
349 help='Additional required labels, comma separated. '
350 'If more than one, all need to be satisfied.')
351 parser_search_dut.add_argument(
352 '--all',
353 action='store_true',
354 help='List all DUTs; (list only one DUT per board by default)')
355 parser_search_dut.add_argument(
356 'condition',
357 nargs='*',
358 help='Conditions, ex. board name, model name, sku name, etc. If more '
359 'than one is specified, matching any one is sufficient. If none is '
360 'specified, all hosts will be listed.')
361 parser_search_dut.set_defaults(func=cmd_search_dut)
362
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800363 opts = parser.parse_args()
364 common.config_logging(opts)
365 opts.func(opts)
366
367
368if __name__ == '__main__':
369 main()