1 #!/usr/bin/python -u 2 # Copyright 2019 The Chromium OS Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """Tool to (re)prepare a DUT for lab deployment.""" 7 8 from __future__ import absolute_import 9 from __future__ import division 10 from __future__ import print_function 11 12 import argparse 13 import errno 14 import logging 15 import logging.config 16 import os 17 18 import common 19 from autotest_lib.server import afe_utils 20 from autotest_lib.server.hosts import file_store 21 from autotest_lib.site_utils.deployment.prepare import dut as preparedut 22 23 24 class DutPreparationError(Exception): 25 """Generic error raised during DUT preparation.""" 26 27 28 def main(): 29 """Tool to (re)prepare a DUT for lab deployment.""" 30 opts = _parse_args() 31 _configure_logging('prepare_dut', os.path.join(opts.results_dir, _LOG_FILE)) 32 33 info = _read_store(opts.host_info_file) 34 repair_image = _get_cros_repair_image_name(info.board) 35 logging.info('Using repair image %s, obtained from AFE', repair_image) 36 with _create_host(opts.hostname, info, opts.results_dir) as host: 37 if opts.dry_run: 38 logging.info('DRY RUN: Would have run actions %s', opts.actions) 39 return 40 41 if 'stage-usb' in opts.actions: 42 preparedut.download_image_to_servo_usb(host, repair_image) 43 if 'install-firmware' in opts.actions: 44 preparedut.install_firmware(host, opts.force_firmware) 45 if 'install-test-image' in opts.actions: 46 preparedut.install_test_image(host) 47 48 49 _LOG_FILE = 'prepare_dut.log' 50 _DUT_LOGS_DIR = 'dut_logs' 51 52 53 def _parse_args(): 54 parser = argparse.ArgumentParser( 55 description='Prepare / validate DUT for lab deployment.') 56 57 parser.add_argument( 58 'actions', 59 nargs='+', 60 choices=['stage-usb', 'install-firmware', 'install-test-image'], 61 help='DUT preparation actions to execute.', 62 ) 63 parser.add_argument( 64 '--dry-run', 65 action='store_true', 66 default=False, 67 help='Run in dry-run mode. No changes will be made to the DUT.', 68 ) 69 parser.add_argument( 70 '--results-dir', 71 required=True, 72 help='Directory to drop logs and output artifacts in.', 73 ) 74 75 parser.add_argument( 76 '--hostname', 77 required=True, 78 help='Hostname of the DUT to prepare.', 79 ) 80 parser.add_argument( 81 '--host-info-file', 82 required=True, 83 help=('Full path to HostInfo file.' 84 ' DUT inventory information is read from the HostInfo file.'), 85 ) 86 87 parser.add_argument( 88 '--force-firmware', 89 action='store_true', 90 help='Force firmware isntallation via chromeos-installfirmware.', 91 ) 92 93 return parser.parse_args() 94 95 96 def _configure_logging(name, tee_file): 97 """Configure logging globally. 98 99 @param name: Name to prepend to log messages. 100 This should be the name of the program. 101 @param tee_file: File to tee logs to, in addition to stderr. 102 """ 103 logging.config.dictConfig({ 104 'version': 1, 105 'formatters': { 106 'stderr': { 107 'format': ('{name}: ' 108 '%(asctime)s:%(levelname)s' 109 ':%(module)s:%(funcName)s:%(lineno)d' 110 ': %(message)s' 111 .format(name=name)), 112 }, 113 'tee_file': { 114 'format': ('%(asctime)s:%(levelname)s' 115 ':%(module)s:%(funcName)s:%(lineno)d' 116 ': %(message)s'), 117 }, 118 }, 119 'handlers': { 120 'stderr': { 121 'class': 'logging.StreamHandler', 122 'formatter': 'stderr', 123 }, 124 'tee_file': { 125 'class': 'logging.FileHandler', 126 'formatter': 'tee_file', 127 'filename': tee_file, 128 }, 129 }, 130 'root': { 131 'level': 'DEBUG', 132 'handlers': ['stderr', 'tee_file'], 133 }, 134 'disable_existing_loggers': False, 135 }) 136 137 138 def _read_store(path): 139 """Read a HostInfo from a file at path.""" 140 store = file_store.FileStore(path) 141 return store.get() 142 143 144 def _create_host(hostname, info, results_dir): 145 """Yield a hosts.CrosHost object with the given inventory information. 146 147 @param hostname: Hostname of the DUT. 148 @param info: A HostInfo with the inventory information to use. 149 @param results_dir: Path to directory for logs / output artifacts. 150 @yield server.hosts.CrosHost object. 151 """ 152 if not info.board: 153 raise DutPreparationError('No board in DUT labels') 154 if not info.model: 155 raise DutPreparationError('No model in DUT labels') 156 157 servo_args = {} 158 if 'servo_host' not in info.attributes: 159 raise DutPreparationError('No servo_host in DUT attributes') 160 if 'servo_port' not in info.attributes: 161 raise DutPreparationError('No servo_port in DUT attributes') 162 163 dut_logs_dir = os.path.join(results_dir, _DUT_LOGS_DIR) 164 try: 165 os.makedirs(dut_logs_dir) 166 except OSError as e: 167 if e.errno != errno.EEXIST: 168 raise 169 170 return preparedut.create_host( 171 hostname, 172 info.board, 173 info.model, 174 info.attributes['servo_host'], 175 info.attributes['servo_port'], 176 info.attributes.get('servo_serial', ''), 177 dut_logs_dir, 178 ) 179 180 181 def _get_cros_repair_image_name(board): 182 """Get the CrOS repair image name for given host. 183 184 TODO(pprabhu): This is an evil function with dependence on the environment 185 (global_config information) and the AFE. Remove this dependence when stable 186 image mappings move off of the AFE. 187 """ 188 return afe_utils.get_stable_cros_image_name(board) 189 190 191 if __name__ == '__main__': 192 main() 193