• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8This library will be shared between Autotest and Skylab DUT deployment tools.
9"""
10
11from __future__ import absolute_import
12from __future__ import division
13from __future__ import print_function
14
15import contextlib
16import time
17
18import common
19from autotest_lib.client.common_lib import error
20from autotest_lib.client.common_lib import utils
21from autotest_lib.server import hosts
22from autotest_lib.server import site_utils as server_utils
23from autotest_lib.server.hosts import host_info
24from autotest_lib.server.hosts import servo_host
25
26
27_FIRMWARE_UPDATE_TIMEOUT = 600
28
29
30@contextlib.contextmanager
31def 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
83def 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
92def 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
103def 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
113def 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
163def _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
187def _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
210def _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
223def _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