Home | History | Annotate | Download | only in prepare
      1 #!/usr/bin/env python
      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 """library functions to prepare a DUT for lab deployment.
      7 
      8 This library will be shared between Autotest and Skylab DUT deployment tools.
      9 """
     10 
     11 from __future__ import absolute_import
     12 from __future__ import division
     13 from __future__ import print_function
     14 
     15 import contextlib
     16 import time
     17 
     18 import common
     19 from autotest_lib.client.common_lib import error
     20 from autotest_lib.client.common_lib import utils
     21 from autotest_lib.server import hosts
     22 from autotest_lib.server import site_utils as server_utils
     23 from autotest_lib.server.hosts import host_info
     24 from autotest_lib.server.hosts import servo_host
     25 
     26 
     27 _FIRMWARE_UPDATE_TIMEOUT = 600
     28 
     29 
     30 @contextlib.contextmanager
     31 def create_host(hostname, board, model, servo_hostname, servo_port,
     32                 servo_serial=None, logs_dir=None):
     33     """Yield a server.hosts.CrosHost object to use for DUT preparation.
     34 
     35     This object contains just enough inventory data to be able to prepare the
     36     DUT for lab deployment. It does not contain any reference to AFE / Skylab so
     37     that DUT preparation is guaranteed to be isolated from the scheduling
     38     infrastructure.
     39 
     40     @param hostname:        FQDN of the host to prepare.
     41     @param board:           The autotest board label for the DUT.
     42     @param model:           The autotest model label for the DUT.
     43     @param servo_hostname:  FQDN of the servo host controlling the DUT.
     44     @param servo_port:      Servo host port used for the controlling servo.
     45     @param servo_serial:    (Optional) Serial number of the controlling servo.
     46     @param logs_dir:        (Optional) Directory to save logs obtained from the
     47                             host.
     48 
     49     @yield a server.hosts.Host object.
     50     """
     51     labels = [
     52             'board:%s' % board,
     53             'model:%s' % model,
     54     ]
     55     attributes = {
     56             servo_host.SERVO_HOST_ATTR: servo_hostname,
     57             servo_host.SERVO_PORT_ATTR: servo_port,
     58     }
     59     if servo_serial is not None:
     60         attributes[servo_host.SERVO_SERIAL_ATTR] = servo_serial
     61 
     62     store = host_info.InMemoryHostInfoStore(info=host_info.HostInfo(
     63             labels=labels,
     64             attributes=attributes,
     65     ))
     66     machine_dict = {
     67             'hostname': hostname,
     68             'host_info_store': store,
     69             'afe_host': server_utils.EmptyAFEHost(),
     70     }
     71     host = hosts.create_host(machine_dict)
     72     servohost = servo_host.ServoHost(
     73             **servo_host.get_servo_args_for_host(host))
     74     _prepare_servo(servohost)
     75     host.set_servo_host(servohost)
     76     host.servo.uart_logs_dir = logs_dir
     77     try:
     78         yield host
     79     finally:
     80         host.close()
     81 
     82 
     83 def download_image_to_servo_usb(host, build):
     84     """Download the given image to the USB attached to host's servo.
     85 
     86     @param host   A server.hosts.Host object.
     87     @param build  A Chrome OS version string for the build to download.
     88     """
     89     host.servo.image_to_servo_usb(host.stage_image_for_servo(build))
     90 
     91 
     92 def install_test_image(host):
     93     """Install the test image for the given build to DUT.
     94 
     95     This function assumes that the required image is already downloaded onto the
     96     USB key connected to the DUT via servo.
     97 
     98     @param host   servers.host.Host object.
     99     """
    100     host.servo_install()
    101 
    102 
    103 def flash_firmware_using_servo(host, build):
    104     """Flash DUT firmware directly using servo.
    105 
    106     Rather than running `chromeos-firmwareupdate` on DUT, we can flash DUT
    107     firmware directly using servo (run command `flashrom`, etc. on servo). In
    108     this way, we don't require DUT to be in dev mode and with dev_boot_usb
    109     enabled."""
    110     host.firmware_install(build)
    111 
    112 
    113 def install_firmware(host, force):
    114     """Install dev-signed firmware after removing write-protect.
    115 
    116     At start, it's assumed that hardware write-protect is disabled,
    117     the DUT is in dev mode, and the servo's USB stick already has a
    118     test image installed.
    119 
    120     The firmware is installed by powering on and typing ctrl+U on
    121     the keyboard in order to boot the test image from USB.  Once
    122     the DUT is booted, we run a series of commands to install the
    123     read-only firmware from the test image.  Then we clear debug
    124     mode, and shut down.
    125 
    126     @param host   Host instance to use for servo and ssh operations.
    127     @param force  Boolean value determining if firmware install is forced.
    128     """
    129     servo = host.servo
    130     # First power on.  We sleep to allow the firmware plenty of time
    131     # to display the dev-mode screen; some boards take their time to
    132     # be ready for the ctrl+U after power on.
    133     servo.get_power_state_controller().power_off()
    134     servo.switch_usbkey('dut')
    135     servo.get_power_state_controller().power_on()
    136     time.sleep(10)
    137     # Dev mode screen should be up now:  type ctrl+U and wait for
    138     # boot from USB to finish.
    139     servo.ctrl_u()
    140     if not host.wait_up(timeout=host.USB_BOOT_TIMEOUT):
    141         raise Exception('DUT failed to boot in dev mode for '
    142                         'firmware update')
    143     # Disable software-controlled write-protect for both FPROMs, and
    144     # install the RO firmware.
    145     for fprom in ['host', 'ec']:
    146         host.run('flashrom -p %s --wp-disable' % fprom,
    147                  ignore_status=True)
    148 
    149     fw_update_log = '/mnt/stateful_partition/home/root/cros-fw-update.log'
    150     pid = _start_firmware_update(host, force, fw_update_log)
    151     _wait_firmware_update_process(host, pid)
    152     _check_firmware_update_result(host, fw_update_log)
    153 
    154     # Get us out of dev-mode and clear GBB flags.  GBB flags are
    155     # non-zero because boot from USB was enabled.
    156     host.run('/usr/share/vboot/bin/set_gbb_flags.sh 0',
    157              ignore_status=True)
    158     host.run('crossystem disable_dev_request=1',
    159              ignore_status=True)
    160     host.halt()
    161 
    162 
    163 def _start_firmware_update(host, force, result_file):
    164     """Run `chromeos-firmwareupdate` in background.
    165 
    166     In scenario servo v4 type C, some boards of DUT may lose ethernet
    167     connectivity on firmware update. There's no way to bring it back except
    168     rebooting the system.
    169 
    170     @param host         Host instance to use for servo and ssh operations.
    171     @param force        Boolean value determining if firmware install is forced.
    172     @param result_file  Path on DUT to save operation logs.
    173 
    174     @returns The process id."""
    175     fw_update_cmd = 'chromeos-firmwareupdate --mode=factory'
    176     if force:
    177         fw_update_cmd += ' --force'
    178 
    179     cmd = [
    180         "date > %s" % result_file,
    181         "nohup %s &>> %s" % (fw_update_cmd, result_file),
    182         "/usr/local/bin/hooks/check_ethernet.hook"
    183     ]
    184     return host.run_background(';'.join(cmd))
    185 
    186 
    187 def _wait_firmware_update_process(host, pid, timeout=_FIRMWARE_UPDATE_TIMEOUT):
    188     """Wait `chromeos-firmwareupdate` to finish.
    189 
    190     @param host     Host instance to use for servo and ssh operations.
    191     @param pid      The process ID of `chromeos-firmwareupdate`.
    192     @param timeout  Maximum time to wait for firmware updating.
    193     """
    194     try:
    195         utils.poll_for_condition(
    196             lambda: host.run('ps -f -p %s' % pid, timeout=20).exit_status,
    197             exception=Exception(
    198                     "chromeos-firmwareupdate (pid: %s) didn't complete in %s "
    199                     'seconds.' % (pid, timeout)),
    200             timeout=_FIRMWARE_UPDATE_TIMEOUT,
    201             sleep_interval=10,
    202         )
    203     except error.AutoservRunError:
    204         # We lose the connectivity, so the DUT should be booting up.
    205         if not host.wait_up(timeout=host.USB_BOOT_TIMEOUT):
    206             raise Exception(
    207                     'DUT failed to boot up after firmware updating.')
    208 
    209 
    210 def _check_firmware_update_result(host, result_file):
    211     """Check if firmware updating is good or not.
    212 
    213     @param host         Host instance to use for servo and ssh operations.
    214     @param result_file  Path of the file saving output of
    215                         `chromeos-firmwareupdate`.
    216     """
    217     fw_update_was_good = ">> DONE: Firmware updater exits successfully."
    218     result = host.run('cat %s' % result_file)
    219     if result.stdout.rstrip().rsplit('\n', 1)[1] != fw_update_was_good:
    220         raise Exception("chromeos-firmwareupdate failed!")
    221 
    222 
    223 def _prepare_servo(servohost):
    224     """Prepare servo connected to host for installation steps.
    225 
    226     @param servohost  A server.hosts.servo_host.ServoHost object.
    227     """
    228     # Stopping `servod` on the servo host will force `repair()` to
    229     # restart it.  We want that restart for a few reasons:
    230     #   + `servod` caches knowledge about the image on the USB stick.
    231     #     We want to clear the cache to force the USB stick to be
    232     #     re-imaged unconditionally.
    233     #   + If there's a problem with servod that verify and repair
    234     #     can't find, this provides a UI through which `servod` can
    235     #     be restarted.
    236     servohost.run('stop servod PORT=%d' % servohost.servo_port,
    237                   ignore_status=True)
    238     servohost.repair()
    239 
    240     # Don't timeout probing for the host usb device, there could be a bunch
    241     # of servos probing at the same time on the same servo host.  And
    242     # since we can't pass None through the xml rpcs, use 0 to indicate None.
    243     if not servohost.get_servo().probe_host_usb_dev(timeout=0):
    244         raise Exception('No USB stick detected on Servo host')
    245