| #!/usr/bin/python -u |
| # Copyright 2019 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Tool to (re)prepare a DUT for lab deployment.""" |
| |
| from __future__ import absolute_import |
| from __future__ import division |
| from __future__ import print_function |
| |
| import argparse |
| import errno |
| import logging |
| import logging.config |
| import os |
| |
| import common |
| from autotest_lib.server import afe_utils |
| from autotest_lib.server.hosts import file_store |
| from autotest_lib.site_utils.deployment.prepare import dut as preparedut |
| |
| |
| class DutPreparationError(Exception): |
| """Generic error raised during DUT preparation.""" |
| |
| |
| def main(): |
| """Tool to (re)prepare a DUT for lab deployment.""" |
| opts = _parse_args() |
| _configure_logging('prepare_dut', os.path.join(opts.results_dir, _LOG_FILE)) |
| |
| info = _read_store(opts.host_info_file) |
| repair_image = _get_cros_repair_image_name(info.board) |
| logging.info('Using repair image %s, obtained from AFE', repair_image) |
| with _create_host(opts.hostname, info, opts.results_dir) as host: |
| if opts.dry_run: |
| logging.info('DRY RUN: Would have run actions %s', opts.actions) |
| return |
| |
| if 'stage-usb' in opts.actions: |
| preparedut.download_image_to_servo_usb(host, repair_image) |
| if 'install-firmware' in opts.actions: |
| preparedut.install_firmware(host) |
| if 'install-test-image' in opts.actions: |
| preparedut.install_test_image(host) |
| |
| |
| _LOG_FILE = 'prepare_dut.log' |
| _DUT_LOGS_DIR = 'dut_logs' |
| |
| |
| def _parse_args(): |
| parser = argparse.ArgumentParser( |
| description='Prepare / validate DUT for lab deployment.') |
| |
| parser.add_argument( |
| 'actions', |
| nargs='+', |
| choices=['stage-usb', 'install-firmware', 'install-test-image'], |
| help='DUT preparation actions to execute.', |
| ) |
| parser.add_argument( |
| '--dry-run', |
| action='store_true', |
| default=False, |
| help='Run in dry-run mode. No changes will be made to the DUT.', |
| ) |
| parser.add_argument( |
| '--results-dir', |
| required=True, |
| help='Directory to drop logs and output artifacts in.', |
| ) |
| |
| parser.add_argument( |
| '--hostname', |
| required=True, |
| help='Hostname of the DUT to prepare.', |
| ) |
| parser.add_argument( |
| '--host-info-file', |
| required=True, |
| help=('Full path to HostInfo file.' |
| ' DUT inventory information is read from the HostInfo file.'), |
| ) |
| |
| return parser.parse_args() |
| |
| |
| def _configure_logging(name, tee_file): |
| """Configure logging globally. |
| |
| @param name: Name to prepend to log messages. |
| This should be the name of the program. |
| @param tee_file: File to tee logs to, in addition to stderr. |
| """ |
| logging.config.dictConfig({ |
| 'version': 1, |
| 'formatters': { |
| 'stderr': { |
| 'format': ('{name}: ' |
| '%(asctime)s:%(levelname)s' |
| ':%(module)s:%(funcName)s:%(lineno)d' |
| ': %(message)s' |
| .format(name=name)), |
| }, |
| 'tee_file': { |
| 'format': ('%(asctime)s:%(levelname)s' |
| ':%(module)s:%(funcName)s:%(lineno)d' |
| ': %(message)s'), |
| }, |
| }, |
| 'handlers': { |
| 'stderr': { |
| 'class': 'logging.StreamHandler', |
| 'formatter': 'stderr', |
| }, |
| 'tee_file': { |
| 'class': 'logging.FileHandler', |
| 'formatter': 'tee_file', |
| 'filename': tee_file, |
| }, |
| }, |
| 'root': { |
| 'level': 'DEBUG', |
| 'handlers': ['stderr', 'tee_file'], |
| }, |
| 'disable_existing_loggers': False, |
| }) |
| |
| |
| def _read_store(path): |
| """Read a HostInfo from a file at path.""" |
| store = file_store.FileStore(path) |
| return store.get() |
| |
| |
| def _create_host(hostname, info, results_dir): |
| """Yield a hosts.CrosHost object with the given inventory information. |
| |
| @param hostname: Hostname of the DUT. |
| @param info: A HostInfo with the inventory information to use. |
| @param results_dir: Path to directory for logs / output artifacts. |
| @yield server.hosts.CrosHost object. |
| """ |
| if not info.board: |
| raise DutPreparationError('No board in DUT labels') |
| if not info.model: |
| raise DutPreparationError('No model in DUT labels') |
| |
| servo_args = {} |
| if 'servo_host' not in info.attributes: |
| raise DutPreparationError('No servo_host in DUT attributes') |
| if 'servo_port' not in info.attributes: |
| raise DutPreparationError('No servo_port in DUT attributes') |
| |
| dut_logs_dir = os.path.join(results_dir, _DUT_LOGS_DIR) |
| try: |
| os.makedirs(dut_logs_dir) |
| except OSError as e: |
| if e.errno != errno.EEXIST: |
| raise |
| |
| return preparedut.create_host( |
| hostname, |
| info.board, |
| info.model, |
| info.attributes['servo_host'], |
| info.attributes['servo_port'], |
| info.attributes.get('servo_serial', ''), |
| dut_logs_dir, |
| ) |
| |
| |
| def _get_cros_repair_image_name(board): |
| """Get the CrOS repair image name for given host. |
| |
| TODO(pprabhu): This is an evil function with dependence on the environment |
| (global_config information) and the AFE. Remove this dependence when stable |
| image mappings move off of the AFE. |
| """ |
| return afe_utils.get_stable_cros_image_name(board) |
| |
| |
| if __name__ == '__main__': |
| main() |