blob: 26bc1fab41632e30e70632e14a4bd729b0891c78 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2017 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper script to manipulate chromeos DUT or query info."""
from __future__ import annotations
import dataclasses
import json
import logging
from bisect_kit import cli
from bisect_kit import common
from bisect_kit import core
from bisect_kit import cros_lab_util
from bisect_kit import cros_util
from bisect_kit import dut_allocator
from bisect_kit import errors
DEFAULT_DUT_POOL = 'DUT_POOL_QUOTA'
logger = logging.getLogger(__name__)
def cmd_version_info(opts):
info = dataclasses.asdict(
cros_util.query_version_info(opts.board, opts.version)
)
if opts.name:
if opts.name not in info:
logger.error('unknown name=%s', opts.name)
print(info[opts.name])
else:
print(json.dumps(info, sort_keys=True, indent=4))
def cmd_query_dut_board(opts):
assert cros_util.is_dut(opts.dut)
print(cros_util.query_dut_board(opts.dut))
def cmd_reboot(opts):
if not cros_util.is_dut(opts.dut):
if opts.force:
logger.warning('%s is not a ChromeOS device?', opts.dut)
else:
raise errors.ArgumentError(
'dut', 'not a ChromeOS device (--force to continue)'
)
cros_util.reboot(
opts.dut, force_reboot_callback=cros_lab_util.reboot_via_servo
)
def cmd_lease_dut(opts):
if opts.duration is not None and opts.duration < 60:
raise errors.ArgumentError('--duration', 'must be at least 60 seconds')
reason = opts.reason or cros_lab_util.make_lease_reason(opts.session)
host = cros_lab_util.dut_host_name(opts.dut)
logger.info('trying to lease %s', host)
if cros_lab_util.crosfleet_lease_dut(host, reason, opts.duration):
logger.info('leased %s', host)
else:
raise errors.DutLeaseException('unable to lease %s' % host)
def cmd_release_dut(opts):
host = cros_lab_util.dut_host_name(opts.dut)
if opts.enable_rootfs_verification:
cros_lab_util.enable_rootfs_verification(host, opts.chromeos_root)
cros_lab_util.crosfleet_release_dut(host)
logger.info('%s released', host)
@dataclasses.dataclass
class AllocateDutResult:
"""Result of allocate_dut."""
result: str | None = None
leased_dut: str | None = None
board: str | None = None
exception: str | None = None
text: str | None = None
def allocate_dut(spec: core.DutAllocateSpec):
result = AllocateDutResult()
try:
host, board = dut_allocator.allocate_dut(spec)
result.result = 'ready'
if host:
result.leased_dut = cros_lab_util.dut_name_to_address(host)
result.board = board
logger.debug('dut allocated: %s', result)
except Exception as e:
logger.exception('cmd_allocate_dut failed')
result.result = 'failed'
result.exception = e.__class__.__name__
result.text = str(e)
logger.warning('failed to allocate dut: %s', result)
return result
def cmd_allocate_dut(opts):
spec = core.DutAllocateSpec(
pool=opts.pool,
dimensions=opts.dimensions,
board=opts.board,
model=opts.model,
sku=opts.sku,
dut_name=opts.dut_name,
satlab_ip=opts.satlab_ip,
version_hint=opts.version_hint,
builder_hint=opts.builder_hint,
time_limit=opts.time_limit,
duration=opts.duration,
parallel=opts.parallel,
chromeos_root=opts.chromeos_root,
session=opts.session,
)
result = allocate_dut(spec)
print(json.dumps(dataclasses.asdict(result)))
def cmd_repair_dut(opts):
cros_lab_util.repair(opts.dut, opts.chromeos_root)
def add_allocate_dut_arguments(
parser, allow_required: bool, flags_prefix: str = ''
):
"""Add DUT allocate related argument to |parser|.
Args:
parser: an ArgumentParser object which supports add_argument(), etc.
It can be an argument parser, an argument group, or others alike.
allow_required: whether any of the argument are allowed to be set as required.
When it's False, all arguments should be optional.
flags_prefix: a string to prefix all flags to avoid possible
name collision with other flags. If empty, prefix nothing.
"""
# All flags below are prepended with a prefix to avoid name collision
# with other flags. Defaults to prepend an empty string.
def flag_name(name):
return '--%s%s' % (flags_prefix + '-' if flags_prefix else '', name)
parser.add_argument(
flag_name('pool'),
# TODO(cylee): for possible backward compatibility, remove it after
# ensuring no caller is using it.
'--pool',
help='Pools to search DUT (default: %(default)s); comma separated',
default=DEFAULT_DUT_POOL,
)
parser.add_argument(
flag_name('dimensions'),
help='Dimension filters used for searching DUTs, in format '
'key1=val1 key2=val2 ...',
type=cli.argtype_key_value,
nargs='+',
default=[],
)
group = parser.add_mutually_exclusive_group(required=allow_required)
group.add_argument(
flag_name('board'),
help='allocation criteria; comma separated',
)
group.add_argument(
flag_name('model'),
# TODO(cylee): for possible backward compatibility, remove it after
# ensuring no caller is using it.
'--model',
help='allocation criteria; comma separated',
)
group.add_argument(
flag_name('sku'),
# TODO(cylee): for possible backward compatibility, remove it after
# ensuring no caller is using it.
'--sku',
help='allocation criteria; comma separated',
)
group.add_argument(
flag_name('dut-name'),
help='allocation criteria; comma separated',
)
parser.add_argument(
flag_name('satlab-ip'),
# TODO(cylee): for possible backward compatibility, remove it after
# ensuring no caller is using it.
'--satlab-ip',
help='Optional, required only if the dut is a satlab dut',
type=cli.argtype_re(r'\d+\.\d+\.\d+\.\d+', '172.30.251.188'),
)
parser.add_argument(
flag_name('version-hint'),
help='chromeos version; comma separated',
)
parser.add_argument(
flag_name('builder-hint'),
help='chromeos builder; comma separated',
)
# Pubsub ack deadline is 10 minutes (b/143663659). Default 9 minutes with 1
# minute buffer.
parser.add_argument(
flag_name('time-limit'),
type=int,
default=9 * 60,
help='Time limit to attempt lease in seconds (default: %(default)s)',
)
parser.add_argument(
flag_name('duration'),
type=float,
help='lease duration in seconds; will be round to minutes',
)
parser.add_argument(
flag_name('parallel'),
type=int,
default=1,
help=(
'The number of multiple lease tasks to speed up '
'(default: %(default)d)'
),
)
@cli.fatal_error_handler
def main():
parents = [cli.create_session_optional_parser()]
parser = cli.ArgumentParser()
subparsers = parser.add_subparsers(
dest='command', title='commands', metavar='<command>', required=True
)
parser_version_info = subparsers.add_parser(
'version_info',
help='Query version info of given chromeos build',
parents=parents,
description='Given chromeos `board` and `version`, '
'print version information of components.',
)
parser_version_info.add_argument(
'board', help='ChromeOS board name, like "samus".'
)
parser_version_info.add_argument(
'version',
type=cros_util.argtype_cros_version,
help='ChromeOS version, like "9876.0.0" or "R62-9876.0.0"',
)
parser_version_info.add_argument(
'name',
nargs='?',
help='Component name. If specified, output its version string. '
'Otherwise output all version info as dict in json format.',
)
parser_version_info.set_defaults(func=cmd_version_info)
parser_query_dut_board = subparsers.add_parser(
'query_dut_board', help='Query board name of given DUT', parents=parents
)
parser_query_dut_board.add_argument('dut')
parser_query_dut_board.set_defaults(func=cmd_query_dut_board)
parser_reboot = subparsers.add_parser(
'reboot',
help='Reboot a DUT',
parents=parents,
description='Reboot a DUT and verify the reboot is successful.',
)
parser_reboot.add_argument('--force', action='store_true')
parser_reboot.add_argument('dut')
parser_reboot.set_defaults(func=cmd_reboot)
parser_lease_dut = subparsers.add_parser(
'lease_dut',
help='Lease a DUT in the lab',
parents=[cli.create_common_argument_parser()],
description='Lease a DUT in the lab. '
'This is implemented by `skylab lease-dut` with additional checking.',
)
group = parser_lease_dut.add_mutually_exclusive_group(required=True)
group.add_argument('--session', help='session name')
group.add_argument('--reason', help='specify lease reason manually')
parser_lease_dut.add_argument('dut')
parser_lease_dut.add_argument(
'--duration',
type=float,
help='duration in seconds; will be round to minutes',
)
parser_lease_dut.set_defaults(func=cmd_lease_dut)
parser_release_dut = subparsers.add_parser(
'release_dut',
help='Release a DUT in the lab',
parents=parents,
description='Release a DUT in the lab. '
'This is implemented by `skylab release-dut` with additional checking.',
)
parser_release_dut.add_argument('dut')
parser_release_dut.set_defaults(func=cmd_release_dut)
parser_release_dut.add_argument(
'--chromeos-root',
type=cli.argtype_dir_path,
default=common.get_default_chromeos_root(),
help='ChromeOS source tree, for overlay data (default: %(default)s)',
)
parser_release_dut.add_argument(
'--enable-rootfs-verification',
dest='enable_rootfs_verification',
action='store_true',
help='Enable rootfs verification when releasing the DUT',
)
parser_allocate_dut = subparsers.add_parser(
'allocate_dut',
help='Allocate a DUT in the lab',
parents=[cli.create_session_optional_parser()],
description='Allocate a DUT in the lab. It will lease a DUT in the lab '
'for bisecting. The caller (bisect-kit runner) of this command should '
'retry this command again later if no DUT available now.',
)
add_allocate_dut_arguments(
parser_allocate_dut,
allow_required=True,
)
# add_allocate_dut_arguments() is shared by both
# "cros_helper.py allocate_dut" and "diagnose_cros_*.py init".
# --chromeos-root is already an argument of the latter in another
# argument group, so it is left out here.
parser_allocate_dut.add_argument(
'--chromeos-root',
type=cli.argtype_dir_path,
default=common.get_default_chromeos_root(),
help='ChromeOS source tree, for overlay data (default: %(default)s)',
)
parser_allocate_dut.set_defaults(func=cmd_allocate_dut)
parser_repair_dut = subparsers.add_parser(
'repair_dut',
help='Repair a DUT in the lab',
parents=parents,
description='Repair a DUT in the lab. '
'This is simply wrapper of "deploy repair" with additional checking.',
)
parser_repair_dut.add_argument('dut')
parser_repair_dut.set_defaults(func=cmd_repair_dut)
opts = parser.parse_args()
common.config_logging(opts)
opts.func(opts)
if __name__ == '__main__':
main()