blob: 4c156ead78f63b4f8f7859aa05b4c9f45bcf97c5 [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 Wu2ea804f2017-11-28 17:11:41 +0800243def main():
244 common.init()
245 parser = argparse.ArgumentParser()
246 common.add_common_arguments(parser)
247 subparsers = parser.add_subparsers(
248 dest='command', title='commands', metavar='<command>')
249
250 parser_version_info = subparsers.add_parser(
251 'version_info',
252 help='Query version info of given chromeos build',
253 description='Given chromeos `board` and `version`, '
254 'print version information of components.')
255 parser_version_info.add_argument(
256 'board', help='ChromeOS board name, like "samus".')
257 parser_version_info.add_argument(
258 'version',
259 type=cros_util.argtype_cros_version,
260 help='ChromeOS version, like "9876.0.0" or "R62-9876.0.0"')
261 parser_version_info.add_argument(
262 'name',
263 nargs='?',
264 help='Component name. If specified, output its version string. '
265 'Otherwise output all version info as dict in json format.')
266 parser_version_info.set_defaults(func=cmd_version_info)
267
268 parser_query_dut_board = subparsers.add_parser(
269 'query_dut_board', help='Query board name of given DUT')
270 parser_query_dut_board.add_argument('dut')
271 parser_query_dut_board.set_defaults(func=cmd_query_dut_board)
272
273 parser_reboot = subparsers.add_parser(
274 'reboot',
275 help='Reboot a DUT',
276 description='Reboot a DUT and verify the reboot is successful.')
277 parser_reboot.add_argument('dut')
278 parser_reboot.set_defaults(func=cmd_reboot)
279
Kuang-che Wu22aa9d42019-01-25 10:35:33 +0800280 parser_lock_dut = subparsers.add_parser(
281 'lock_dut',
282 help='Lock a DUT in the lab',
283 description='Lock a DUT in the lab. '
284 'This is simply wrapper of "atest" with additional checking.')
285 group = parser_lock_dut.add_mutually_exclusive_group(required=True)
286 group.add_argument('--session', help='session name; for creating lock reason')
287 group.add_argument('--reason', help='specify lock reason manually')
288 parser_lock_dut.add_argument('dut')
289 parser_lock_dut.set_defaults(func=cmd_lock_dut)
290
291 parser_unlock_dut = subparsers.add_parser(
292 'unlock_dut',
293 help='Unlock a DUT in the lab',
294 description='Unlock a DUT in the lab. '
295 'This is simply wrapper of "atest" with additional checking.')
296 parser_unlock_dut.add_argument(
297 '--session', help='session name; for checking lock reason before unlock')
298 parser_unlock_dut.add_argument('dut')
299 parser_unlock_dut.set_defaults(func=cmd_unlock_dut)
300
301 parser_allocate_dut = subparsers.add_parser(
302 'allocate_dut',
303 help='Allocate a DUT in the lab',
304 description='Allocate a DUT in the lab. It will lock a DUT in the lab '
305 'for bisecting. If no DUT is available (ready), it will lock one. The '
306 'caller (bisect-kit runner) of this command should keep note of the '
307 'locked DUT name and retry this command again later.')
308 parser_allocate_dut.add_argument(
309 '--session', required=True, help='session name')
310 parser_allocate_dut.add_argument(
311 '--pools', required=True, help='Pools to search dut, comma separated')
312 parser_allocate_dut.add_argument('--model', help='allocation criteria')
313 parser_allocate_dut.add_argument('--sku', help='allocation criteria')
314 parser_allocate_dut.add_argument(
315 '--label', '-b', help='Additional required labels, comma separated')
316 parser_allocate_dut.add_argument(
317 '--locked_dut', help='Locked DUT name by last run')
318 parser_allocate_dut.set_defaults(func=cmd_allocate_dut)
319
Kuang-che Wuc45cfa42019-01-15 00:15:01 +0800320 parser_search_dut = subparsers.add_parser(
321 'search_dut',
322 help='Search DUT with conditions',
323 description='Search hosts in the lab. Grouped by model and ordered by '
324 'availability. When your test could be run on many models, this command '
325 'help you to decide what model to use.')
326 parser_search_dut.add_argument(
327 '--pools',
328 default='performance,crosperf,suites',
329 help='Pools to search, comma separated. The searching will be performed '
330 'for each pool.')
331 parser_search_dut.add_argument(
332 '--label',
333 '-b',
334 help='Additional required labels, comma separated. '
335 'If more than one, all need to be satisfied.')
336 parser_search_dut.add_argument(
337 '--all',
338 action='store_true',
339 help='List all DUTs; (list only one DUT per board by default)')
340 parser_search_dut.add_argument(
341 'condition',
342 nargs='*',
343 help='Conditions, ex. board name, model name, sku name, etc. If more '
344 'than one is specified, matching any one is sufficient. If none is '
345 'specified, all hosts will be listed.')
346 parser_search_dut.set_defaults(func=cmd_search_dut)
347
Kuang-che Wu2ea804f2017-11-28 17:11:41 +0800348 opts = parser.parse_args()
349 common.config_logging(opts)
350 opts.func(opts)
351
352
353if __name__ == '__main__':
354 main()