1#!/usr/bin/python2 -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 8from __future__ import absolute_import 9from __future__ import division 10from __future__ import print_function 11 12import argparse 13import errno 14import logging 15import logging.config 16import os 17import sys 18 19import common 20from autotest_lib.client.common_lib import autotest_enum 21from autotest_lib.client.common_lib import logging_manager 22from autotest_lib.server import afe_utils 23from autotest_lib.server import server_logging_config 24from autotest_lib.server.hosts import file_store 25from autotest_lib.site_utils.deployment.prepare import dut as preparedut 26from autotest_lib.server.hosts import factory 27from autotest_lib.site_utils.admin_audit import rpm_validator 28 29 30RETURN_CODES = autotest_enum.AutotestEnum( 31 'OK', 32 'STAGE_USB_FAILURE', 33 'INSTALL_FIRMWARE_FAILURE', 34 'INSTALL_TEST_IMAGE_FAILURE', 35 'PRE_DEPLOY_VERIFICATION_FAILURE', 36 'BOOT_FROM_RECOVERY_MODE_FAILURE', 37 'SETUP_LABSTATION_FAILURE', 38 'UPDATE_LABEL_FAILURE', 39 'OTHER_FAILURES', 40) 41 42_SERVO_UART_LOGS = 'servo_uart' 43 44 45class DutPreparationError(Exception): 46 """Generic error raised during DUT preparation.""" 47 48 49def main(): 50 """Tool to (re)prepare a DUT for lab deployment.""" 51 opts = _parse_args() 52 53 # Create logging setting 54 logging_manager.configure_logging( 55 server_logging_config.ServerLoggingConfig(), 56 results_dir=opts.results_dir) 57 58 try: 59 host_info = _read_store(opts.host_info_file) 60 except Exception as err: 61 logging.error("fail to prepare: %s", err) 62 return RETURN_CODES.OTHER_FAILURES 63 64 with create_host(opts.hostname, host_info, opts.results_dir) as host: 65 if opts.dry_run: 66 logging.info('DRY RUN: Would have run actions %s', opts.actions) 67 return 68 69 is_labstation = (host_info.get().os == "labstation") 70 71 if 'stage-usb' in opts.actions: 72 try: 73 repair_image = afe_utils.get_stable_cros_image_name_v2( 74 host_info.get()) 75 logging.info('Using repair image %s, obtained from AFE', 76 repair_image) 77 preparedut.download_image_to_servo_usb(host, repair_image) 78 except Exception as err: 79 logging.error("fail to stage image to usb: %s", err) 80 return RETURN_CODES.STAGE_USB_FAILURE 81 82 if 'install-test-image' in opts.actions: 83 try: 84 preparedut.install_test_image(host) 85 except Exception as err: 86 logging.error("fail to install test image: %s", err) 87 return RETURN_CODES.INSTALL_TEST_IMAGE_FAILURE 88 89 if 'install-firmware' in opts.actions: 90 try: 91 preparedut.install_firmware(host) 92 except Exception as err: 93 logging.error("fail to install firmware: %s", err) 94 return RETURN_CODES.INSTALL_FIRMWARE_FAILURE 95 96 if 'verify-recovery-mode' in opts.actions: 97 try: 98 preparedut.verify_boot_into_rec_mode(host) 99 except Exception as err: 100 logging.error("fail to boot from recovery mode: %s", err) 101 return RETURN_CODES.BOOT_FROM_RECOVERY_MODE_FAILURE 102 103 # TODO (otabek): mix this step with update-label later. 104 if 'setup-labstation' in opts.actions: 105 try: 106 preparedut.setup_hwid_and_serialnumber(host) 107 except Exception as err: 108 logging.error("fail to setup labstation: %s", err) 109 return RETURN_CODES.SETUP_LABSTATION_FAILURE 110 111 if 'update-label' in opts.actions: 112 try: 113 preparedut.setup_hwid_and_serialnumber(host) 114 if not is_labstation: 115 host.labels.update_labels(host, task_name='deploy') 116 except Exception as err: 117 logging.error("fail to update label: %s", err) 118 return RETURN_CODES.UPDATE_LABEL_FAILURE 119 120 if 'run-pre-deploy-verification' in opts.actions: 121 try: 122 if is_labstation: 123 logging.info("testing RPM information on labstation") 124 preparedut.verify_labstation_RPM_config_unsafe(host) 125 else: 126 preparedut.verify_servo(host) 127 preparedut.verify_battery_status(host) 128 preparedut.verify_ccd_testlab_enable(host) 129 rpm_validator.verify_unsafe(host) 130 except Exception as err: 131 logging.error("fail on pre-deploy verification: %s", err) 132 return RETURN_CODES.PRE_DEPLOY_VERIFICATION_FAILURE 133 134 return RETURN_CODES.OK 135 136 137def _parse_args(): 138 parser = argparse.ArgumentParser( 139 description='Prepare / validate DUT for lab deployment.') 140 141 parser.add_argument( 142 'actions', 143 nargs='+', 144 choices=[ 145 'stage-usb', 'install-test-image', 'install-firmware', 146 'verify-recovery-mode', 'run-pre-deploy-verification', 147 'update-label', 'setup-labstation' 148 ], 149 help='DUT preparation actions to execute.', 150 ) 151 parser.add_argument( 152 '--dry-run', 153 action='store_true', 154 default=False, 155 help='Run in dry-run mode. No changes will be made to the DUT.', 156 ) 157 parser.add_argument( 158 '--results-dir', 159 required=True, 160 help='Directory to drop logs and output artifacts in.', 161 ) 162 163 parser.add_argument( 164 '--hostname', 165 required=True, 166 help='Hostname of the DUT to prepare.', 167 ) 168 parser.add_argument( 169 '--host-info-file', 170 required=True, 171 help=('Full path to HostInfo file.' 172 ' DUT inventory information is read from the HostInfo file.' 173 ), 174 ) 175 176 return parser.parse_args() 177 178 179def _read_store(path): 180 """Read a HostInfo from a file at path.""" 181 store = file_store.FileStore(path) 182 return store 183 184 185def create_host(hostname, host_info, results_dir): 186 """Yield a hosts.CrosHost object with the given inventory information. 187 188 @param hostname: Hostname of the DUT. 189 @param info: A HostInfo with the inventory information to use. 190 @param results_dir: Path to directory for logs / output artifacts. 191 192 @yield server.hosts.CrosHost object. 193 """ 194 info = host_info.get() 195 if not info.board: 196 raise DutPreparationError('No board in DUT labels') 197 if not info.model: 198 raise DutPreparationError('No model in DUT labels') 199 200 need_servo = info.os != 'labstation' 201 dut_logs_dir = None 202 203 if need_servo: 204 # We assume target host is a cros DUT by default 205 if 'servo_host' not in info.attributes: 206 raise DutPreparationError('No servo_host in DUT attributes') 207 if 'servo_port' not in info.attributes: 208 raise DutPreparationError('No servo_port in DUT attributes') 209 210 dut_logs_dir = os.path.join(results_dir, _SERVO_UART_LOGS) 211 try: 212 os.makedirs(dut_logs_dir) 213 except OSError as e: 214 if e.errno != errno.EEXIST: 215 raise 216 217 return factory.create_target_host(hostname, 218 host_info_store=host_info, 219 try_lab_servo=need_servo, 220 try_servo_repair=need_servo, 221 servo_uart_logs_dir=dut_logs_dir) 222 223 224if __name__ == '__main__': 225 sys.exit(main()) 226