普通文本  |  193行  |  5.49 KB

#!/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, opts.force_firmware)
    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.'),
  )

  parser.add_argument(
      '--force-firmware',
      action='store_true',
      help='Force firmware isntallation via chromeos-installfirmware.',
  )

  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()