blob: 056e52ceb3dcb8d4ebe87877e65c0a47c3dac523 [file] [log] [blame]
#!/usr/bin/python2 -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 sys
import common
from autotest_lib.client.common_lib import enum
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
RETURN_CODES = enum.Enum(
'OK',
'STAGE_USB_FAILURE',
'INSTALL_FIRMWARE_FAILURE',
'INSTALL_TEST_IMAGE_FAILURE',
'OTHER_FAILURES',
)
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))
try:
info = _read_store(opts.host_info_file)
repair_image = _get_cros_repair_image_name(info.board)
except Exception as err:
logging.error("fail to prepare: %s", err)
return RETURN_CODES.OTHER_FAILURES
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:
try:
preparedut.download_image_to_servo_usb(host, repair_image)
except Exception as err:
logging.error("fail to stage image to usb: %s", err)
return RETURN_CODES.STAGE_USB_FAILURE
if 'install-firmware' in opts.actions:
try:
preparedut.install_firmware(host)
except Exception as err:
logging.error("fail to install firmware: %s", err)
return RETURN_CODES.INSTALL_FIRMWARE_FAILURE
if 'install-test-image' in opts.actions:
try:
preparedut.install_test_image(host)
except Exception as err:
logging.error("fail to install test image: %s", err)
return RETURN_CODES.INSTALL_TEST_IMAGE_FAILURE
return RETURN_CODES.OK
_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__':
sys.exit(main())