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