1#!/usr/bin/env python2 2# Copyright 2015 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"""Install an initial test image on a set of DUTs. 7 8The methods in this module are meant for two nominally distinct use 9cases that share a great deal of code internally. The first use 10case is for deployment of DUTs that have just been placed in the lab 11for the first time. The second use case is for use after repairing 12a servo. 13 14Newly deployed DUTs may be in a somewhat anomalous state: 15 * The DUTs are running a production base image, not a test image. 16 By extension, the DUTs aren't reachable over SSH. 17 * The DUTs are not necessarily in the AFE database. DUTs that 18 _are_ in the database should be locked. Either way, the DUTs 19 cannot be scheduled to run tests. 20 * The servos for the DUTs need not be configured with the proper 21 overlay. 22 23More broadly, it's not expected that the DUT will be working at the 24start of this operation. If the DUT isn't working at the end of the 25operation, an error will be reported. 26 27The script performs the following functions: 28 * Configure the servo for the target overlay, and test that the 29 servo is generally in good order. 30 * For the full deployment case, install dev-signed RO firmware 31 from the designated stable test image for the DUTs. 32 * For both cases, use servo to install the stable test image from 33 USB. 34 * If the DUT isn't in the AFE database, add it. 35 36The script imposes these preconditions: 37 * Every DUT has a properly connected servo. 38 * Every DUT and servo have proper DHCP and DNS configurations. 39 * Every servo host is up and running, and accessible via SSH. 40 * There is a known, working test image that can be staged and 41 installed on the target DUTs via servo. 42 * Every DUT has the same board and model. 43 * For the full deployment case, every DUT must be in dev mode, 44 and configured to allow boot from USB with ctrl+U. 45 46The implementation uses the `multiprocessing` module to run all 47installations in parallel, separate processes. 48 49""" 50 51import atexit 52from collections import namedtuple 53import functools 54import json 55import logging 56import multiprocessing 57import os 58import shutil 59import sys 60import tempfile 61import time 62import traceback 63 64import common 65from autotest_lib.client.common_lib import error 66from autotest_lib.client.common_lib import host_states 67from autotest_lib.client.common_lib import time_utils 68from autotest_lib.client.common_lib import utils 69from autotest_lib.client.common_lib.cros import retry 70from autotest_lib.client.common_lib.utils import deprecated 71from autotest_lib.server import afe_utils 72from autotest_lib.server import constants 73from autotest_lib.server import frontend 74from autotest_lib.server import hosts 75from autotest_lib.server.cros.dynamic_suite.constants import VERSION_PREFIX 76from autotest_lib.server.hosts import afe_store 77from autotest_lib.server.hosts import servo_host 78from autotest_lib.server.hosts import servo_constants 79from autotest_lib.site_utils.deployment import cmdvalidate 80from autotest_lib.site_utils.deployment.prepare import dut as preparedut 81from autotest_lib.utils import labellib 82 83 84_LOG_FORMAT = '%(asctime)s | %(levelname)-10s | %(message)s' 85 86_DEFAULT_POOL = constants.Labels.POOL_PREFIX + 'suites' 87 88_LABSTATION_DEFAULT_POOL = constants.Labels.POOL_PREFIX + 'labstation_main' 89 90_DIVIDER = '\n============\n' 91 92_LOG_BUCKET_NAME = 'chromeos-install-logs' 93 94_OMAHA_STATUS = 'gs://chromeos-build-release-console/omaha_status.json' 95 96# Lock reasons we'll pass when locking DUTs, depending on the 97# host's prior state. 98_LOCK_REASON_EXISTING = 'Repairing or deploying an existing host' 99_LOCK_REASON_NEW_HOST = 'Repairing or deploying a new host' 100 101_ReportResult = namedtuple('_ReportResult', ['hostname', 'message']) 102 103 104class InstallFailedError(Exception): 105 """Generic error raised explicitly in this module.""" 106 107 108class _NoAFEServoPortError(InstallFailedError): 109 """Exception when there is no servo port stored in the AFE.""" 110 111 112class _MultiFileWriter(object): 113 114 """Group file objects for writing at once.""" 115 116 @deprecated 117 def __init__(self, files): 118 """Initialize _MultiFileWriter. 119 120 @param files Iterable of file objects for writing. 121 """ 122 self._files = files 123 124 @deprecated 125 def write(self, s): 126 """Write a string to the files. 127 128 @param s Write this string. 129 """ 130 for file in self._files: 131 file.write(s) 132 133 134@deprecated 135def _get_upload_log_path(arguments): 136 return 'gs://{bucket}/{name}'.format( 137 bucket=_LOG_BUCKET_NAME, 138 name=arguments.upload_basename) 139 140 141@deprecated 142def _upload_logs(dirpath, gspath): 143 """Upload report logs to Google Storage. 144 145 @param dirpath Path to directory containing the logs. 146 @param gspath Path to GS bucket. 147 """ 148 utils.run(['gsutil', 'cp', '-r', '--', dirpath, gspath]) 149 150 151@deprecated 152def _get_omaha_build(board): 153 """Get the currently preferred Beta channel build for `board`. 154 155 Open and read through the JSON file provided by GoldenEye that 156 describes what version Omaha is currently serving for all boards 157 on all channels. Find the entry for `board` on the Beta channel, 158 and return that version string. 159 160 @param board The board to look up from GoldenEye. 161 162 @return Returns a Chrome OS version string in standard form 163 R##-####.#.#. Will return `None` if no Beta channel 164 entry is found. 165 """ 166 ret = utils.run(['gsutil', 'cat', '--', _OMAHA_STATUS]) 167 omaha_status = json.loads(ret.stdout) 168 omaha_board = board.replace('_', '-') 169 for e in omaha_status['omaha_data']: 170 if (e['channel'] == 'beta' and 171 e['board']['public_codename'] == omaha_board): 172 milestone = e['chrome_version'].split('.')[0] 173 build = e['chrome_os_version'] 174 return 'R%s-%s' % (milestone, build) 175 return None 176 177 178@deprecated 179def _update_build(afe, report_log, arguments): 180 raise RuntimeError("site_utils.deployment::_update_build is intentionally deleted") 181 182 183@deprecated 184def _create_host(hostname, afe, afe_host): 185 """Create a CrosHost object for the DUT. 186 187 This host object is used to update AFE label information for the DUT, but 188 can not be used for installation image on the DUT. In particular, this host 189 object does not have the servo attribute populated. 190 191 @param hostname Hostname of the target DUT. 192 @param afe A frontend.AFE object. 193 @param afe_host AFE Host object for the DUT. 194 """ 195 machine_dict = { 196 'hostname': hostname, 197 'afe_host': afe_host, 198 'host_info_store': afe_store.AfeStore(hostname, afe), 199 } 200 return hosts.create_host(machine_dict) 201 202 203@deprecated 204def _try_lock_host(afe_host): 205 """Lock a host in the AFE, and report whether it succeeded. 206 207 The lock action is logged regardless of success; failures are 208 logged if they occur. 209 210 @param afe_host AFE Host instance to be locked. 211 212 @return `True` on success, or `False` on failure. 213 """ 214 try: 215 logging.warning('Locking host now.') 216 afe_host.modify(locked=True, 217 lock_reason=_LOCK_REASON_EXISTING) 218 except Exception as e: 219 logging.exception('Failed to lock: %s', e) 220 return False 221 return True 222 223 224@deprecated 225def _try_unlock_host(afe_host): 226 """Unlock a host in the AFE, and report whether it succeeded. 227 228 The unlock action is logged regardless of success; failures are 229 logged if they occur. 230 231 @param afe_host AFE Host instance to be unlocked. 232 233 @return `True` on success, or `False` on failure. 234 """ 235 try: 236 logging.warning('Unlocking host.') 237 afe_host.modify(locked=False, lock_reason='') 238 except Exception as e: 239 logging.exception('Failed to unlock: %s', e) 240 return False 241 return True 242 243 244@deprecated 245def _update_host_attributes(afe, hostname, host_attrs): 246 """Update the attributes for a given host. 247 248 @param afe AFE object for RPC calls. 249 @param hostname Host name of the DUT. 250 @param host_attrs Dictionary with attributes to be applied to the 251 host. 252 """ 253 s_hostname, s_port, s_serial = _extract_servo_attributes(hostname, 254 host_attrs) 255 afe.set_host_attribute(servo_constants.SERVO_HOST_ATTR, 256 s_hostname, 257 hostname=hostname) 258 afe.set_host_attribute(servo_constants.SERVO_PORT_ATTR, 259 s_port, 260 hostname=hostname) 261 if s_serial: 262 afe.set_host_attribute(servo_constants.SERVO_SERIAL_ATTR, 263 s_serial, 264 hostname=hostname) 265 266 267@deprecated 268def _extract_servo_attributes(hostname, host_attrs): 269 """Extract servo attributes from the host attribute dict, setting defaults. 270 271 @return (servo_hostname, servo_port, servo_serial) 272 """ 273 # Grab the servo hostname/port/serial from `host_attrs` if supplied. 274 # For new servo V4 deployments, we require the user to supply the 275 # attributes (because there are no appropriate defaults). So, if 276 # none are supplied, we assume it can't be V4, and apply the 277 # defaults for servo V3. 278 s_hostname = (host_attrs.get(servo_constants.SERVO_HOST_ATTR) or 279 servo_host.make_servo_hostname(hostname)) 280 s_port = (host_attrs.get(servo_constants.SERVO_PORT_ATTR) or 281 str(servo_host.ServoHost.DEFAULT_PORT)) 282 s_serial = host_attrs.get(servo_constants.SERVO_SERIAL_ATTR) 283 return s_hostname, s_port, s_serial 284 285 286@deprecated 287def _wait_for_idle(afe, host_id): 288 """Helper function for `_ensure_host_idle`. 289 290 Poll the host with the given `host_id` via `afe`, waiting for it 291 to become idle. Run forever; the caller takes care of timing out. 292 293 @param afe AFE object for RPC calls. 294 @param host_id Id of the host that's expected to become idle. 295 """ 296 while True: 297 afe_host = afe.get_hosts(id=host_id)[0] 298 if afe_host.status in host_states.IDLE_STATES: 299 return 300 # Let's not spam our server. 301 time.sleep(0.2) 302 303 304@deprecated 305def _ensure_host_idle(afe, afe_host): 306 """Abort any special task running on `afe_host`. 307 308 The given `afe_host` is currently locked. If there's a special task 309 running on the given `afe_host`, abort it, then wait for the host to 310 show up as idle, return whether the operation succeeded. 311 312 @param afe AFE object for RPC calls. 313 @param afe_host Host to be aborted. 314 315 @return A true value if the host is idle at return, or a false value 316 if the host wasn't idle after some reasonable time. 317 """ 318 # We need to talk to the shard, not the main, for at least two 319 # reasons: 320 # * The `abort_special_tasks` RPC doesn't forward from the main 321 # to the shard, and only the shard has access to the special 322 # tasks. 323 # * Host status on the main can lag actual status on the shard 324 # by several minutes. Only the shard can provide status 325 # guaranteed to post-date the call to lock the DUT. 326 if afe_host.shard: 327 afe = frontend.AFE(server=afe_host.shard) 328 afe_host = afe.get_hosts(id=afe_host.id)[0] 329 if afe_host.status in host_states.IDLE_STATES: 330 return True 331 afe.run('abort_special_tasks', host_id=afe_host.id, is_active=1) 332 return not retry.timeout(_wait_for_idle, (afe, afe_host.id), 333 timeout_sec=5.0)[0] 334 335 336@deprecated 337def _get_afe_host(afe, hostname, host_attrs, arguments): 338 """Get an AFE Host object for the given host. 339 340 If the host is found in the database, return the object 341 from the RPC call with the updated attributes in host_attr_dict. 342 343 If no host is found, create one with appropriate servo 344 attributes and the given board label. 345 346 @param afe AFE object for RPC calls. 347 @param hostname Host name of the DUT. 348 @param host_attrs Dictionary with attributes to be applied to the 349 host. 350 @param arguments Command line arguments with options. 351 352 @return A tuple of the afe_host, plus a flag. The flag indicates 353 whether the Host should be unlocked if subsequent operations 354 fail. (Hosts are always unlocked after success). 355 """ 356 hostlist = afe.get_hosts([hostname]) 357 unlock_on_failure = False 358 if hostlist: 359 afe_host = hostlist[0] 360 if not afe_host.locked: 361 if _try_lock_host(afe_host): 362 unlock_on_failure = True 363 else: 364 raise Exception('Failed to lock host') 365 if not _ensure_host_idle(afe, afe_host): 366 if unlock_on_failure and not _try_unlock_host(afe_host): 367 raise Exception('Failed to abort host, and failed to unlock it') 368 raise Exception('Failed to abort task on host') 369 # This host was pre-existing; if the user didn't supply 370 # attributes, don't update them, because the defaults may 371 # not be correct. 372 if host_attrs and not arguments.labstation: 373 _update_host_attributes(afe, hostname, host_attrs) 374 else: 375 afe_host = afe.create_host(hostname, 376 locked=True, 377 lock_reason=_LOCK_REASON_NEW_HOST) 378 379 if not arguments.labstation: 380 _update_host_attributes(afe, hostname, host_attrs) 381 382 # Correct board/model label is critical to installation. Always ensure user 383 # supplied board/model matches the AFE information. 384 _ensure_label_in_afe(afe_host, 'board', arguments.board) 385 _ensure_label_in_afe(afe_host, 'model', arguments.model) 386 387 afe_host = afe.get_hosts([hostname])[0] 388 return afe_host, unlock_on_failure 389 390 391@deprecated 392def _ensure_label_in_afe(afe_host, label_name, label_value): 393 """Add the given board label, only if one doesn't already exist. 394 395 @params label_name name of the label, e.g. 'board', 'model', etc. 396 @params label_value value of the label. 397 398 @raises InstallFailedError if supplied board is different from existing 399 board in AFE. 400 """ 401 if not label_value: 402 return 403 404 labels = labellib.LabelsMapping(afe_host.labels) 405 if label_name not in labels: 406 afe_host.add_labels(['%s:%s' % (label_name, label_value)]) 407 return 408 409 existing_value = labels[label_name] 410 if label_value != existing_value: 411 raise InstallFailedError( 412 'provided %s %s does not match the %s %s for host %s' % 413 (label_name, label_value, label_name, existing_value, 414 afe_host.hostname)) 415 416 417@deprecated 418def _create_host_for_installation(host, arguments): 419 """Creates a context manager of hosts.CrosHost object for installation. 420 421 The host object yielded by the returned context manager is agnostic of the 422 infrastructure environment. In particular, it does not have any references 423 to the AFE. 424 425 @param host: A server.hosts.CrosHost object. 426 @param arguments: Parsed commandline arguments for this script. 427 428 @return a context manager which yields hosts.CrosHost object. 429 """ 430 info = host.host_info_store.get() 431 s_host, s_port, s_serial = _extract_servo_attributes(host.hostname, 432 info.attributes) 433 return preparedut.create_cros_host(host.hostname, arguments.board, 434 arguments.model, s_host, s_port, 435 s_serial, arguments.logdir) 436 437 438@deprecated 439def _install_test_image(host, arguments): 440 """Install a test image to the DUT. 441 442 Install a stable test image on the DUT using the full servo 443 repair flow. 444 445 @param host Host instance for the DUT being installed. 446 @param arguments Command line arguments with options. 447 """ 448 repair_image = _get_cros_repair_image_name(host) 449 logging.info('Using repair image %s', repair_image) 450 if arguments.dry_run: 451 return 452 if arguments.stageusb: 453 try: 454 preparedut.download_image_to_servo_usb(host, repair_image) 455 except Exception as e: 456 logging.exception('Failed to stage image on USB: %s', e) 457 raise Exception('USB staging failed') 458 if arguments.install_test_image: 459 try: 460 preparedut.install_test_image(host) 461 except error.AutoservRunError as e: 462 logging.exception('Failed to install: %s', e) 463 raise Exception('chromeos-install failed') 464 if arguments.install_firmware: 465 try: 466 if arguments.using_servo: 467 logging.debug('Install FW using servo.') 468 preparedut.flash_firmware_using_servo(host, repair_image) 469 else: 470 logging.debug('Install FW by chromeos-firmwareupdate.') 471 preparedut.install_firmware(host) 472 except error.AutoservRunError as e: 473 logging.exception('Firmware update failed: %s', e) 474 msg = '%s failed' % ( 475 'Flashing firmware using servo' if arguments.using_servo 476 else 'chromeos-firmwareupdate') 477 raise Exception(msg) 478 if arguments.reinstall_test_image: 479 try: 480 preparedut.reinstall_test_image(host) 481 except error.AutoservRunError as e: 482 logging.exception('Failed to install: %s', e) 483 raise Exception('chromeos-install failed') 484 if arguments.install_test_image and arguments.install_firmware: 485 # we need to verify that DUT can successfully boot in to recovery mode 486 # if it's initial deploy. 487 try: 488 preparedut.verify_boot_into_rec_mode(host) 489 except error.AutoservRunError as e: 490 logging.exception('Failed to validate DUT can boot from ' 491 'recovery mode: %s', e) 492 raise Exception('recovery mode validation failed') 493 494 495@deprecated 496def _install_and_update_afe(afe, hostname, host_attrs, arguments): 497 """Perform all installation and AFE updates. 498 499 First, lock the host if it exists and is unlocked. Then, 500 install the test image on the DUT. At the end, unlock the 501 DUT, unless the installation failed and the DUT was locked 502 before we started. 503 504 If installation succeeds, make sure the DUT is in the AFE, 505 and make sure that it has basic labels. 506 507 @param afe AFE object for RPC calls. 508 @param hostname Host name of the DUT. 509 @param host_attrs Dictionary with attributes to be applied to the 510 host. 511 @param arguments Command line arguments with options. 512 """ 513 afe_host, unlock_on_failure = _get_afe_host(afe, hostname, host_attrs, 514 arguments) 515 host = None 516 try: 517 host = _create_host(hostname, afe, afe_host) 518 if arguments.labstation: 519 _setup_labstation(host) 520 else: 521 with _create_host_for_installation(host, arguments) as target_host: 522 _install_test_image(target_host, arguments) 523 _update_servo_type_attribute(target_host, host) 524 525 if ((arguments.install_test_image or arguments.reinstall_test_image) 526 and not arguments.dry_run): 527 host.labels.update_labels(host) 528 platform_labels = afe.get_labels( 529 host__hostname=hostname, platform=True) 530 if not platform_labels: 531 platform = host.get_platform() 532 new_labels = afe.get_labels(name=platform) 533 if not new_labels: 534 afe.create_label(platform, platform=True) 535 afe_host.add_labels([platform]) 536 version = [label for label in afe_host.labels 537 if label.startswith(VERSION_PREFIX)] 538 if version and not arguments.dry_run: 539 afe_host.remove_labels(version) 540 except Exception as e: 541 if unlock_on_failure and not _try_unlock_host(afe_host): 542 logging.error('Failed to unlock host!') 543 raise 544 finally: 545 if host is not None: 546 host.close() 547 548 if not _try_unlock_host(afe_host): 549 raise Exception('Install succeeded, but failed to unlock the DUT.') 550 551 552@deprecated 553def _install_dut(arguments, host_attr_dict, hostname): 554 """Deploy or repair a single DUT. 555 556 @param arguments Command line arguments with options. 557 @param host_attr_dict Dict mapping hostnames to attributes to be 558 stored in the AFE. 559 @param hostname Host name of the DUT to install on. 560 561 @return On success, return `None`. On failure, return a string 562 with an error message. 563 """ 564 # In some cases, autotest code that we call during install may 565 # put stuff onto stdout with 'print' statements. Most notably, 566 # the AFE frontend may print 'FAILED RPC CALL' (boo, hiss). We 567 # want nothing from this subprocess going to the output we 568 # inherited from our parent, so redirect stdout and stderr, before 569 # we make any AFE calls. Note that this is reasonable because we're 570 # in a subprocess. 571 572 logpath = os.path.join(arguments.logdir, hostname + '.log') 573 logfile = open(logpath, 'w') 574 sys.stderr = sys.stdout = logfile 575 _configure_logging_to_file(logfile) 576 577 afe = frontend.AFE(server=arguments.web) 578 try: 579 _install_and_update_afe(afe, hostname, 580 host_attr_dict.get(hostname, {}), 581 arguments) 582 except Exception as e: 583 logging.exception('Original exception: %s', e) 584 return str(e) 585 return None 586 587 588@deprecated 589def _report_hosts(report_log, heading, host_results_list): 590 """Report results for a list of hosts. 591 592 To improve visibility, results are preceded by a header line, 593 followed by a divider line. Then results are printed, one host 594 per line. 595 596 @param report_log File-like object for logging report 597 output. 598 @param heading The header string to be printed before 599 results. 600 @param host_results_list A list of _ReportResult tuples 601 to be printed one per line. 602 """ 603 if not host_results_list: 604 return 605 report_log.write(heading) 606 report_log.write(_DIVIDER) 607 for result in host_results_list: 608 report_log.write('{result.hostname:30} {result.message}\n' 609 .format(result=result)) 610 report_log.write('\n') 611 612 613@deprecated 614def _report_results(afe, report_log, hostnames, results, arguments): 615 """Gather and report a summary of results from installation. 616 617 Segregate results into successes and failures, reporting 618 each separately. At the end, report the total of successes 619 and failures. 620 621 @param afe AFE object for RPC calls. 622 @param report_log File-like object for logging report output. 623 @param hostnames List of the hostnames that were tested. 624 @param results List of error messages, in the same order 625 as the hostnames. `None` means the 626 corresponding host succeeded. 627 @param arguments Command line arguments with options. 628 """ 629 successful_hosts = [] 630 success_reports = [] 631 failure_reports = [] 632 for result, hostname in zip(results, hostnames): 633 if result is None: 634 successful_hosts.append(hostname) 635 else: 636 failure_reports.append(_ReportResult(hostname, result)) 637 if successful_hosts: 638 afe.repair_hosts(hostnames=successful_hosts) 639 for h in afe.get_hosts(hostnames=successful_hosts): 640 for label in h.labels: 641 if label.startswith(constants.Labels.POOL_PREFIX): 642 result = _ReportResult(h.hostname, 643 'Host already in %s' % label) 644 success_reports.append(result) 645 break 646 else: 647 if arguments.labstation: 648 target_pool = _LABSTATION_DEFAULT_POOL 649 else: 650 target_pool = _DEFAULT_POOL 651 h.add_labels([target_pool]) 652 result = _ReportResult(h.hostname, 653 'Host added to %s' % target_pool) 654 success_reports.append(result) 655 report_log.write(_DIVIDER) 656 _report_hosts(report_log, 'Successes', success_reports) 657 _report_hosts(report_log, 'Failures', failure_reports) 658 report_log.write( 659 'Installation complete: %d successes, %d failures.\n' % 660 (len(success_reports), len(failure_reports))) 661 662 663@deprecated 664def _clear_root_logger_handlers(): 665 """Remove all handlers from root logger.""" 666 root_logger = logging.getLogger() 667 for h in root_logger.handlers: 668 root_logger.removeHandler(h) 669 670 671@deprecated 672def _configure_logging_to_file(logfile): 673 """Configure the logging module for `install_duts()`. 674 675 @param log_file Log file object. 676 """ 677 _clear_root_logger_handlers() 678 handler = logging.StreamHandler(logfile) 679 formatter = logging.Formatter(_LOG_FORMAT, time_utils.TIME_FMT) 680 handler.setFormatter(formatter) 681 root_logger = logging.getLogger() 682 root_logger.addHandler(handler) 683 684 685@deprecated 686def _get_used_servo_ports(servo_hostname, afe): 687 """ 688 Return a list of used servo ports for the given servo host. 689 690 @param servo_hostname: Hostname of the servo host to check for. 691 @param afe: AFE instance. 692 693 @returns a list of used ports for the given servo host. 694 """ 695 used_ports = [] 696 host_list = afe.get_hosts_by_attribute( 697 attribute=servo_constants.SERVO_HOST_ATTR, value=servo_hostname) 698 for host in host_list: 699 afe_host = afe.get_hosts(hostname=host) 700 if afe_host: 701 servo_port = afe_host[0].attributes.get(servo_constants.SERVO_PORT_ATTR) 702 if servo_port: 703 used_ports.append(int(servo_port)) 704 return used_ports 705 706 707@deprecated 708def _get_free_servo_port(servo_hostname, used_servo_ports, afe): 709 """ 710 Get a free servo port for the servo_host. 711 712 @param servo_hostname: Hostname of the servo host. 713 @param used_servo_ports: Dict of dicts that contain the list of used ports 714 for the given servo host. 715 @param afe: AFE instance. 716 717 @returns a free servo port if servo_hostname is non-empty, otherwise an 718 empty string. 719 """ 720 used_ports = [] 721 servo_port = servo_host.ServoHost.DEFAULT_PORT 722 # If no servo hostname was specified we can assume we're dealing with a 723 # servo v3 or older deployment since the servo hostname can be 724 # inferred from the dut hostname (by appending '-servo' to it). We only 725 # need to find a free port if we're using a servo v4 since we can use the 726 # default port for v3 and older. 727 if not servo_hostname: 728 return '' 729 # If we haven't checked this servo host yet, check the AFE if other duts 730 # used this servo host and grab the ports specified for them. 731 elif servo_hostname not in used_servo_ports: 732 used_ports = _get_used_servo_ports(servo_hostname, afe) 733 else: 734 used_ports = used_servo_ports[servo_hostname] 735 used_ports.sort() 736 if used_ports: 737 # Range is taken from servod.py in hdctools. 738 start_port = servo_host.ServoHost.DEFAULT_PORT 739 end_port = start_port - 99 740 # We'll choose first port available in descending order. 741 for port in xrange(start_port, end_port - 1, -1): 742 if port not in used_ports: 743 servo_port = port 744 break 745 used_ports.append(servo_port) 746 used_servo_ports[servo_hostname] = used_ports 747 return servo_port 748 749 750@deprecated 751def _get_afe_servo_port(host_info, afe): 752 """ 753 Get the servo port from the afe if it matches the same servo host hostname. 754 755 @param host_info HostInfo tuple (hostname, host_attr_dict). 756 757 @returns Servo port (int) if servo host hostname matches the one specified 758 host_info.host_attr_dict, otherwise None. 759 760 @raises _NoAFEServoPortError: When there is no stored host info or servo 761 port host attribute in the AFE for the given host. 762 """ 763 afe_hosts = afe.get_hosts(hostname=host_info.hostname) 764 if not afe_hosts: 765 raise _NoAFEServoPortError 766 767 servo_port = afe_hosts[0].attributes.get(servo_constants.SERVO_PORT_ATTR) 768 afe_servo_host = afe_hosts[0].attributes.get(servo_constants.SERVO_HOST_ATTR) 769 host_info_servo_host = host_info.host_attr_dict.get( 770 servo_constants.SERVO_HOST_ATTR) 771 772 if afe_servo_host == host_info_servo_host and servo_port: 773 return int(servo_port) 774 else: 775 raise _NoAFEServoPortError 776 777 778@deprecated 779def _get_host_attributes(host_info_list, afe): 780 """ 781 Get host attributes if a hostname_file was supplied. 782 783 @param host_info_list List of HostInfo tuples (hostname, host_attr_dict). 784 785 @returns Dict of attributes from host_info_list. 786 """ 787 host_attributes = {} 788 # We need to choose servo ports for these hosts but we need to make sure 789 # we don't choose ports already used. We'll store all used ports in a 790 # dict of lists where the key is the servo_host and the val is a list of 791 # ports used. 792 used_servo_ports = {} 793 for host_info in host_info_list: 794 host_attr_dict = host_info.host_attr_dict 795 # If the host already has an entry in the AFE that matches the same 796 # servo host hostname and the servo port is set, use that port. 797 try: 798 host_attr_dict[servo_constants.SERVO_PORT_ATTR] = _get_afe_servo_port( 799 host_info, afe) 800 except _NoAFEServoPortError: 801 host_attr_dict[servo_constants.SERVO_PORT_ATTR] = _get_free_servo_port( 802 host_attr_dict[servo_constants.SERVO_HOST_ATTR], used_servo_ports, 803 afe) 804 host_attributes[host_info.hostname] = host_attr_dict 805 return host_attributes 806 807 808@deprecated 809def _get_cros_repair_image_name(host): 810 """Get the CrOS repair image name for given host. 811 812 @param host: hosts.CrosHost object. This object need not have an AFE 813 reference. 814 """ 815 info = host.host_info_store.get() 816 if not info.board: 817 raise InstallFailedError('Unknown board for given host') 818 return afe_utils.get_stable_cros_image_name_v2(info) 819 820 821@deprecated 822def install_duts(arguments): 823 """Install a test image on DUTs, and deploy them. 824 825 This handles command line parsing for both the repair and 826 deployment commands. The two operations are largely identical; 827 the main difference is that full deployment includes flashing 828 dev-signed firmware on the DUT prior to installing the test 829 image. 830 831 @param arguments Command line arguments with options, as 832 returned by `argparse.Argparser`. 833 """ 834 arguments = cmdvalidate.validate_arguments(arguments) 835 if arguments is None: 836 sys.exit(1) 837 sys.stderr.write('Installation output logs in %s\n' % arguments.logdir) 838 839 # Override tempfile.tempdir. Some of the autotest code we call 840 # will create temporary files that don't get cleaned up. So, we 841 # put the temp files in our results directory, so that we can 842 # clean up everything at one fell swoop. 843 tempfile.tempdir = tempfile.mkdtemp() 844 atexit.register(shutil.rmtree, tempfile.tempdir) 845 846 # We don't want to distract the user with logging output, so we catch 847 # logging output in a file. 848 logging_file_path = os.path.join(arguments.logdir, 'debug.log') 849 logfile = open(logging_file_path, 'w') 850 _configure_logging_to_file(logfile) 851 852 report_log_path = os.path.join(arguments.logdir, 'report.log') 853 with open(report_log_path, 'w') as report_log_file: 854 report_log = _MultiFileWriter([report_log_file, sys.stdout]) 855 afe = frontend.AFE(server=arguments.web) 856 if arguments.dry_run: 857 report_log.write('Dry run - installation and most testing ' 858 'will be skipped.\n') 859 host_attr_dict = _get_host_attributes(arguments.host_info_list, afe) 860 install_pool = multiprocessing.Pool(len(arguments.hostnames)) 861 install_function = functools.partial(_install_dut, arguments, 862 host_attr_dict) 863 results_list = install_pool.map(install_function, arguments.hostnames) 864 _report_results(afe, report_log, arguments.hostnames, results_list, 865 arguments) 866 867 if arguments.upload: 868 try: 869 gspath = _get_upload_log_path(arguments) 870 sys.stderr.write('Logs will be uploaded to %s\n' % (gspath,)) 871 _upload_logs(arguments.logdir, gspath) 872 except Exception: 873 upload_failure_log_path = os.path.join(arguments.logdir, 874 'gs_upload_failure.log') 875 with open(upload_failure_log_path, 'w') as file_: 876 traceback.print_exc(limit=None, file=file_) 877 sys.stderr.write('Failed to upload logs;' 878 ' failure details are stored in {}.\n' 879 .format(upload_failure_log_path)) 880 881 882@deprecated 883def _update_servo_type_attribute(host, host_to_update): 884 """Update servo_type attribute for the DUT. 885 886 @param host A CrOSHost with a initialized servo property. 887 @param host_to_update A CrOSHost with AfeStore as its host_info_store. 888 889 """ 890 info = host_to_update.host_info_store.get() 891 if 'servo_type' not in info.attributes: 892 logging.info("Collecting and adding servo_type attribute.") 893 info.attributes['servo_type'] = host.servo.get_servo_version() 894 host_to_update.host_info_store.commit(info) 895 896 897@deprecated 898def _setup_labstation(host): 899 """Do initial setup for labstation host. 900 901 @param host A LabstationHost object. 902 903 """ 904 try: 905 if not host.is_labstation(): 906 raise InstallFailedError('Current OS on host %s is not a labstation' 907 ' image.', host.hostname) 908 except AttributeError: 909 raise InstallFailedError('Unable to verify host has a labstation image,' 910 ' this can be caused by host is unsshable.') 911 912 try: 913 # TODO: we should setup hwid and serial number for DUT in deploy script 914 # as well, which is currently obtained from repair job. 915 info = host.host_info_store.get() 916 hwid = host.run('crossystem hwid', ignore_status=True).stdout 917 if hwid: 918 info.attributes['HWID'] = hwid 919 920 serial_number = host.run('vpd -g serial_number', 921 ignore_status=True).stdout 922 if serial_number: 923 info.attributes['serial_number'] = serial_number 924 if info != host.host_info_store.get(): 925 host.host_info_store.commit(info) 926 except Exception as e: 927 raise InstallFailedError('Failed to get HWID & Serial Number for host' 928 ' %s: %s' % (host.hostname, str(e))) 929 930 host.labels.update_labels(host) 931