• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python2
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
19import logging
20from autotest_lib.client.common_lib import error
21from autotest_lib.client.common_lib import utils
22from autotest_lib.server import hosts
23from autotest_lib.server import site_utils as server_utils
24from autotest_lib.server.hosts import host_info
25from autotest_lib.server.hosts import servo_host
26
27
28_FIRMWARE_UPDATE_TIMEOUT = 600
29
30
31@contextlib.contextmanager
32def create_cros_host(hostname, board, model, servo_hostname, servo_port,
33                servo_serial=None, logs_dir=None):
34    """Yield a server.hosts.CrosHost object to use for DUT preparation.
35
36    This object contains just enough inventory data to be able to prepare the
37    DUT for lab deployment. It does not contain any reference to AFE / Skylab so
38    that DUT preparation is guaranteed to be isolated from the scheduling
39    infrastructure.
40
41    @param hostname:        FQDN of the host to prepare.
42    @param board:           The autotest board label for the DUT.
43    @param model:           The autotest model label for the DUT.
44    @param servo_hostname:  FQDN of the servo host controlling the DUT.
45    @param servo_port:      Servo host port used for the controlling servo.
46    @param servo_serial:    (Optional) Serial number of the controlling servo.
47    @param logs_dir:        (Optional) Directory to save logs obtained from the
48                            host.
49
50    @yield a server.hosts.Host object.
51    """
52    labels = [
53            'board:%s' % board,
54            'model:%s' % model,
55    ]
56    attributes = {
57            servo_host.SERVO_HOST_ATTR: servo_hostname,
58            servo_host.SERVO_PORT_ATTR: servo_port,
59    }
60    if servo_serial is not None:
61        attributes[servo_host.SERVO_SERIAL_ATTR] = servo_serial
62
63    store = host_info.InMemoryHostInfoStore(info=host_info.HostInfo(
64            labels=labels,
65            attributes=attributes,
66    ))
67    machine_dict = _get_machine_dict(hostname, store)
68    host = hosts.create_host(machine_dict)
69    servohost = servo_host.ServoHost(
70            **servo_host.get_servo_args_for_host(host))
71    _prepare_servo(servohost)
72    host.set_servo_host(servohost)
73    host.servo.uart_logs_dir = logs_dir
74    try:
75        yield host
76    finally:
77        host.close()
78
79
80@contextlib.contextmanager
81def create_labstation_host(hostname, board, model):
82    """Yield a server.hosts.LabstationHost object to use for labstation
83    preparation.
84
85    This object contains just enough inventory data to be able to prepare the
86    labstation for lab deployment. It does not contain any reference to
87    AFE / Skylab so that DUT preparation is guaranteed to be isolated from
88    the scheduling infrastructure.
89
90    @param hostname:        FQDN of the host to prepare.
91    @param board:           The autotest board label for the DUT.
92    @param model:           The autotest model label for the DUT.
93
94    @yield a server.hosts.Host object.
95    """
96    labels = [
97        'board:%s' % board,
98        'model:%s' % model,
99        'os:labstation'
100        ]
101
102    store = host_info.InMemoryHostInfoStore(info=host_info.HostInfo(
103        labels=labels,
104    ))
105    machine_dict = _get_machine_dict(hostname, store)
106    host = hosts.create_host(machine_dict)
107    try:
108        yield host
109    finally:
110        host.close()
111
112
113def _get_machine_dict(hostname, host_info_store):
114    """Helper function to generate a machine_dic to feed hosts.create_host.
115
116    @param hostname
117    @param host_info_store
118
119    @return A dict that hosts.create_host can consume.
120    """
121    return {'hostname': hostname,
122            'host_info_store': host_info_store,
123            'afe_host': server_utils.EmptyAFEHost(),
124            }
125
126
127def download_image_to_servo_usb(host, build):
128    """Download the given image to the USB attached to host's servo.
129
130    @param host   A server.hosts.Host object.
131    @param build  A Chrome OS version string for the build to download.
132    """
133    _, update_url = host.stage_image_for_servo(build)
134    host.servo.image_to_servo_usb(update_url)
135
136
137def power_cycle_via_servo(host):
138    """Power cycle a host though it's attached servo.
139
140    @param host   A server.hosts.Host object.
141    """
142    logging.info("Shutting down the host...")
143    host.halt()
144
145    logging.info('Power cycling DUT through servo...')
146    host.servo.get_power_state_controller().power_off()
147    host.servo.switch_usbkey('off')
148    time.sleep(host.SHUTDOWN_TIMEOUT)
149    # N.B. The Servo API requires that we use power_on() here
150    # for two reasons:
151    #  1) After turning on a DUT in recovery mode, you must turn
152    #     it off and then on with power_on() once more to
153    #     disable recovery mode (this is a Parrot specific
154    #     requirement).
155    #  2) After power_off(), the only way to turn on is with
156    #     power_on() (this is a Storm specific requirement).
157    time.sleep(host.SHUTDOWN_TIMEOUT)
158    host.servo.get_power_state_controller().power_on()
159
160    logging.info('Waiting for DUT to come back up.')
161    if not host.wait_up(timeout=host.BOOT_TIMEOUT):
162        raise error.AutoservError('DUT failed to come back after %d seconds' %
163                                  host.BOOT_TIMEOUT)
164
165
166def verify_boot_into_rec_mode(host):
167    """Verify that we can boot into USB when in recover mode, and reset tpm.
168
169    The new deploy process will install test image before firmware update, so
170    we don't need boot into recovery mode during deploy, but we still want to
171    make sure that DUT can boot into recover mode as it's critical for
172    auto-repair capability.
173
174    @param host   servers.host.Host object.
175    """
176    logging.info("Shutting down DUT...")
177    host.halt()
178    host.servo.get_power_state_controller().power_off()
179    time.sleep(host.SHUTDOWN_TIMEOUT)
180    logging.info("Booting DUT into recovery mode...")
181    host.servo.boot_in_recovery_mode()
182
183    if not host.wait_up(timeout=host.USB_BOOT_TIMEOUT):
184        raise Exception('DUT failed to boot into recovery mode.')
185
186    logging.info('Resetting the TPM status')
187    try:
188        host.run('chromeos-tpm-recovery')
189    except error.AutoservRunError:
190        logging.warn('chromeos-tpm-recovery is too old.')
191
192    logging.info("Rebooting host into normal mode.")
193    power_cycle_via_servo(host)
194    logging.info("Verify boot into recovery mode completed successfully.")
195
196
197def install_test_image(host):
198    """Initial install a test image on a DUT.
199
200    This function assumes that the required image is already downloaded onto the
201    USB key connected to the DUT via servo, and the DUT is in dev mode with
202    dev_boot_usb enabled.
203
204    @param host   servers.host.Host object.
205    """
206    servo = host.servo
207    # First power on.  We sleep to allow the firmware plenty of time
208    # to display the dev-mode screen; some boards take their time to
209    # be ready for the ctrl+U after power on.
210    servo.get_power_state_controller().power_off()
211    time.sleep(host.SHUTDOWN_TIMEOUT)
212    servo.switch_usbkey('dut')
213    servo.get_power_state_controller().power_on()
214
215    # Dev mode screen should be up now:  type ctrl+U and wait for
216    # boot from USB to finish.
217    time.sleep(10)
218    servo.ctrl_u()
219
220    if not host.wait_up(timeout=host.USB_BOOT_TIMEOUT):
221        raise Exception('DUT failed to boot from USB for install test image.')
222
223    host.run('chromeos-install --yes', timeout=host.INSTALL_TIMEOUT)
224
225    logging.info("Rebooting DUT to boot from hard drive.")
226    power_cycle_via_servo(host)
227    logging.info("Install test image completed successfully.")
228
229
230def reinstall_test_image(host):
231    """Install the test image of given build to DUT.
232
233    This function assumes that the required image is already downloaded onto the
234    USB key connected to the DUT via servo.
235
236    @param host   servers.host.Host object.
237    """
238    host.servo_install()
239
240
241def flash_firmware_using_servo(host, build):
242    """Flash DUT firmware directly using servo.
243
244    Rather than running `chromeos-firmwareupdate` on DUT, we can flash DUT
245    firmware directly using servo (run command `flashrom`, etc. on servo). In
246    this way, we don't require DUT to be in dev mode and with dev_boot_usb
247    enabled."""
248    host.firmware_install(build)
249
250
251def install_firmware(host):
252    """Install dev-signed firmware after removing write-protect.
253
254    At start, it's assumed that hardware write-protect is disabled,
255    the DUT is in dev mode, and the servo's USB stick already has a
256    test image installed.
257
258    The firmware is installed by powering on and typing ctrl+U on
259    the keyboard in order to boot the test image from USB.  Once
260    the DUT is booted, we run a series of commands to install the
261    read-only firmware from the test image.  Then we clear debug
262    mode, and shut down.
263
264    @param host   Host instance to use for servo and ssh operations.
265    """
266    # Disable software-controlled write-protect for both FPROMs, and
267    # install the RO firmware.
268    for fprom in ['host', 'ec']:
269        host.run('flashrom -p %s --wp-disable' % fprom,
270                 ignore_status=True)
271
272    fw_update_log = '/mnt/stateful_partition/home/root/cros-fw-update.log'
273    pid = _start_firmware_update(host, fw_update_log)
274    _wait_firmware_update_process(host, pid)
275    _check_firmware_update_result(host, fw_update_log)
276
277    # Get us out of dev-mode and clear GBB flags.  GBB flags are
278    # non-zero because boot from USB was enabled.
279    logging.info("Resting gbb flags and disable dev mode.")
280    host.run('/usr/share/vboot/bin/set_gbb_flags.sh 0',
281             ignore_status=True)
282    host.run('crossystem disable_dev_request=1',
283             ignore_status=True)
284
285    logging.info("Rebooting DUT in normal mode(non-dev).")
286    power_cycle_via_servo(host)
287    logging.info("Install firmware completed successfully.")
288
289
290
291def _start_firmware_update(host, result_file):
292    """Run `chromeos-firmwareupdate` in background.
293
294    In scenario servo v4 type C, some boards of DUT may lose ethernet
295    connectivity on firmware update. There's no way to bring it back except
296    rebooting the system.
297
298    @param host         Host instance to use for servo and ssh operations.
299    @param result_file  Path on DUT to save operation logs.
300
301    @returns The process id."""
302    # TODO(guocb): Use `make_dev_firmware` to re-sign from MP to test/dev.
303    fw_update_cmd = 'chromeos-firmwareupdate --mode=factory --force'
304
305    cmd = [
306        "date > %s" % result_file,
307        "nohup %s &>> %s" % (fw_update_cmd, result_file),
308        "/usr/local/bin/hooks/check_ethernet.hook"
309    ]
310    return host.run_background(';'.join(cmd))
311
312
313def _wait_firmware_update_process(host, pid, timeout=_FIRMWARE_UPDATE_TIMEOUT):
314    """Wait `chromeos-firmwareupdate` to finish.
315
316    @param host     Host instance to use for servo and ssh operations.
317    @param pid      The process ID of `chromeos-firmwareupdate`.
318    @param timeout  Maximum time to wait for firmware updating.
319    """
320    try:
321        utils.poll_for_condition(
322            lambda: host.run('ps -f -p %s' % pid, timeout=20).exit_status,
323            exception=Exception(
324                    "chromeos-firmwareupdate (pid: %s) didn't complete in %s "
325                    'seconds.' % (pid, timeout)),
326            timeout=_FIRMWARE_UPDATE_TIMEOUT,
327            sleep_interval=10,
328        )
329    except error.AutoservRunError:
330        # We lose the connectivity, so the DUT should be booting up.
331        if not host.wait_up(timeout=host.USB_BOOT_TIMEOUT):
332            raise Exception(
333                    'DUT failed to boot up after firmware updating.')
334
335
336def _check_firmware_update_result(host, result_file):
337    """Check if firmware updating is good or not.
338
339    @param host         Host instance to use for servo and ssh operations.
340    @param result_file  Path of the file saving output of
341                        `chromeos-firmwareupdate`.
342    """
343    fw_update_was_good = ">> DONE: Firmware updater exits successfully."
344    result = host.run('cat %s' % result_file)
345    if result.stdout.rstrip().rsplit('\n', 1)[1] != fw_update_was_good:
346        raise Exception("chromeos-firmwareupdate failed!")
347
348
349def _prepare_servo(servohost):
350    """Prepare servo connected to host for installation steps.
351
352    @param servohost  A server.hosts.servo_host.ServoHost object.
353    """
354    # Stopping `servod` on the servo host will force `repair()` to
355    # restart it.  We want that restart for a few reasons:
356    #   + `servod` caches knowledge about the image on the USB stick.
357    #     We want to clear the cache to force the USB stick to be
358    #     re-imaged unconditionally.
359    #   + If there's a problem with servod that verify and repair
360    #     can't find, this provides a UI through which `servod` can
361    #     be restarted.
362    servohost.run('stop servod PORT=%d' % servohost.servo_port,
363                  ignore_status=True)
364    servohost.repair()
365
366    # Don't timeout probing for the host usb device, there could be a bunch
367    # of servos probing at the same time on the same servo host.  And
368    # since we can't pass None through the xml rpcs, use 0 to indicate None.
369    if not servohost.get_servo().probe_host_usb_dev(timeout=0):
370        raise Exception('No USB stick detected on Servo host')
371
372
373def setup_labstation(host):
374    """Do initial setup for labstation host.
375
376    @param host    A LabstationHost object.
377
378    """
379    try:
380        if not host.is_labstation():
381            raise Exception('Current OS on host %s is not a labstation image.'
382                            % host.hostname)
383    except AttributeError:
384        raise Exception('Unable to verify host has a labstation image, this can'
385                        ' be caused by host is unsshable.')
386
387    try:
388        # TODO: we should setup hwid and serial number for DUT in deploy script
389        #  as well, which is currently obtained from repair job.
390        info = host.host_info_store.get()
391        hwid = host.run('crossystem hwid', ignore_status=True).stdout
392        if hwid:
393            info.attributes['HWID'] = hwid
394
395        serial_number = host.run('vpd -g serial_number',
396                                 ignore_status=True).stdout
397        if serial_number:
398            info.attributes['serial_number'] = serial_number
399        if info != host.host_info_store.get():
400            host.host_info_store.commit(info)
401    except Exception as e:
402        raise Exception('Failed to get HWID & Serial Number for host %s: %s'
403                        % (host.hostname, str(e)))
404