• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright 2016 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
6from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
10import logging
11import math
12import six
13import sys
14import time
15
16import common
17from autotest_lib.client.common_lib import error
18from autotest_lib.client.common_lib import global_config
19from autotest_lib.client.common_lib import hosts
20from autotest_lib.client.common_lib import utils
21from autotest_lib.client.common_lib.cros import dev_server
22from autotest_lib.client.common_lib.cros import retry
23from autotest_lib.client.common_lib.cros import tpm_utils
24from autotest_lib.server import afe_utils
25from autotest_lib.server import crashcollect
26from autotest_lib.server.cros import provisioner
27from autotest_lib.server.cros.dynamic_suite import tools
28from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
29from autotest_lib.server.cros.servo.keyboard import servo_keyboard_flasher
30from autotest_lib.server.cros.repair import mac_address_helper
31from autotest_lib.server.hosts import cros_constants
32from autotest_lib.server.hosts import cros_firmware
33from autotest_lib.server.hosts import repair_utils
34from autotest_lib.site_utils.admin_audit import verifiers as audit_verify
35from autotest_lib.site_utils.admin_audit import constants as audit_const
36from autotest_lib.site_utils.admin_audit import battery_validator
37from six.moves import range
38
39try:
40    from autotest_lib.utils.frozen_chromite.lib import metrics
41except ImportError:
42    metrics = utils.metrics_mock
43
44from autotest_lib.utils.frozen_chromite.lib import timeout_util
45
46DEFAULT_SERVO_RESET_TRIGGER = (
47        'ping',
48        'ssh',
49        'stop_start_ui',
50        'power',
51)
52
53
54# _DEV_MODE_ALLOW_POOLS - The set of pools that are allowed to be
55# in dev mode (usually, those should be unmanaged devices)
56#
57_DEV_MODE_ALLOWED_POOLS = set(
58    global_config.global_config.get_config_value(
59            'CROS',
60            'pools_dev_mode_allowed',
61            type=str,
62            default='',
63            allow_blank=True).split(','))
64
65# Setting to suppress dev mode check; primarily used for moblab where all
66# DUT's are in dev mode.
67_DEV_MODE_ALWAYS_ALLOWED = global_config.global_config.get_config_value(
68            'CROS',
69            'dev_mode_allowed',
70            type=bool,
71            default=False)
72
73# Triggers for the 'provision', 'powerwash', and 'usb' repair actions.
74# These are also used as dependencies in the `CrosHost` repair
75# sequence, as follows:
76#
77# provision:
78#   - triggers: _CROS_PROVISION_TRIGGERS
79#   - depends on: _CROS_USB_TRIGGERS + _CROS_POWERWASH_TRIGGERS
80#
81# powerwash:
82#   - triggers: _CROS_POWERWASH_TRIGGERS + _CROS_PROVISION_TRIGGERS
83#   - depends on: _CROS_USB_TRIGGERS
84#
85# usb:
86#   - triggers: _CROS_USB_TRIGGERS + _CROS_POWERWASH_TRIGGERS +
87#               _CROS_PROVISION_TRIGGERS
88#   - depends on: _CROS_USB_DEPENDENCIES
89#
90# N.B. AC power detection depends on software on the DUT, and there
91# have been bugs where detection failed even though the DUT really
92# did have power.  So, we make the 'power' verifier a trigger for
93# reinstall repair actions, too.
94#
95# TODO(jrbarnette):  provision repair can't fix all problems reported by
96# the 'cros' verifier; it's listed as an provision trigger as a
97# simplification.  The ultimate fix is to split the 'cros' verifier
98# into smaller individual verifiers.
99_CROS_PROVISION_TRIGGERS = (
100        'power',
101        'rwfw',
102        'fwstatus',
103        'python',
104        'hwid',
105        'cros',
106        'dev_default_boot',
107)
108_CROS_POWERWASH_TRIGGERS = ('tpm', 'good_provision', 'ext4',)
109_CROS_USB_TRIGGERS = (
110        'ping',
111        'ssh',
112        'writable',
113)
114_JETSTREAM_USB_TRIGGERS = (
115        'ping',
116        'ssh',
117        'writable',
118)
119_CROS_FIRMWARE_TRIGGERS = (
120        'ping',
121        'ssh',
122)
123_CROS_AC_TRIGGERS = (
124        'ping',
125        'power',
126)
127_CROS_USB_DEPENDENCIES = ('usb_drive', )
128
129
130class ACPowerVerifier(hosts.Verifier):
131    """Check for AC power and battery charging state."""
132
133    # Battery discharging state in power_supply_info file.
134    BATTERY_DISCHARGING = 'Discharging'
135    # Power controller can discharge battery any time till 90% for any model.
136    # Setting level to 90% in case we have wearout of it.
137    BATTERY_DISCHARGE_MIN = 90
138
139    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
140    def verify(self, host):
141        # pylint: disable=missing-docstring
142        info = self._load_info(host)
143        self._validate_ac_plugged(info)
144        self._validate_battery(host, info)
145
146    def _load_info(self, host):
147        try:
148            info = host.get_power_supply_info()
149        except error.AutoservRunError:
150            raise hosts.AutoservVerifyError(
151                    'Failed to get power supply info')
152        return info
153
154    def _validate_ac_plugged(self, info):
155        # Validate that DUT is plugged to the AC.
156        try:
157            if info['Line Power']['online'] != 'yes':
158                raise hosts.AutoservVerifyError(
159                        'AC power is not plugged in')
160        except KeyError:
161            raise hosts.AutoservVerifyError(
162                    'Cannot determine AC power status')
163
164    def _validate_battery(self, host, info):
165        host_info = host.host_info_store.get()
166        if host_info.get_label_value('power') == 'battery':
167            if 'Battery' not in info:
168                data = {'host': host.hostname, 'model': host_info.model}
169                metrics.Counter('chromeos/autotest/battery_not_detected'
170                                ).increment(fields=data)
171                logging.info('Battery is not presented but expected!'
172                             ' Probably hardware issue.')
173
174        try:
175            charging_state = info['Battery']['state']
176            battery_level = float(info['Battery']['percentage'])
177
178            # Collect info to determine which battery level is better to call
179            # as MIN_BATTERY_LEVEL for DUTs in the lab.
180            if battery_level < cros_constants.MIN_BATTERY_LEVEL:
181                level_by_10 = int(math.floor(battery_level / 10.0)) * 10
182                metrics_data = {
183                        'host': host.hostname,
184                        'level': level_by_10,
185                        'mode': charging_state
186                }
187                metrics.Counter('chromeos/autotest/battery/state2').increment(
188                        fields=metrics_data)
189
190            if (charging_state == self.BATTERY_DISCHARGING
191                        and battery_level < self.BATTERY_DISCHARGE_MIN):
192                logging.debug('Try to fix discharging state of the battery. '
193                              'Possible that a test left wrong state.')
194                # Here is the chance that battery is discharging because
195                # of some test did not clean up the state.
196                # We are going to try to fix it by set charging to normal.
197                host.run('ectool chargecontrol normal', ignore_status=True)
198                # wait to change state.
199                time.sleep(10)
200                info = self._load_info(host)
201                charging_state = info['Battery']['state']
202                fixed = charging_state != self.BATTERY_DISCHARGING
203                # TODO (@otabek) remove metrics after research
204                logging.debug('Fixed battery discharge mode.')
205                metrics_data = {
206                        'model': host.host_info_store.get().model,
207                        'fixed': fixed
208                }
209                metrics.Counter(
210                    'chromeos/autotest/repair/chargecontrol_fixed'
211                ).increment(fields=metrics_data)
212
213            if (battery_level < cros_constants.MIN_BATTERY_LEVEL
214                        and charging_state == self.BATTERY_DISCHARGING):
215                # TODO(@xianuowang) remove metrics here once we have device
216                # health profile to collect history of DUT's metrics.
217                metrics_data = {'host': host.hostname,
218                                'board': host.host_info_store.get().board}
219                metrics.Counter(
220                    'chromeos/autotest/repair/verifier/power').increment(
221                        fields=metrics_data)
222                raise hosts.AutoservVerifyError(
223                        'Battery is in discharging state and current level'
224                        ' is less than %s%%' %
225                        cros_constants.MIN_BATTERY_LEVEL)
226        except (KeyError, ValueError):
227            logging.warning('Cannot determine battery state -'
228                            ' skipping check.')
229
230    @property
231    def description(self):
232        # pylint: disable=missing-docstring
233        return 'The DUT is plugged in to AC power and battery is charging'
234
235
236class ProvisioningLabelsVerifier(hosts.Verifier):
237    """Confirm that current ChromeOS image on the host is matches
238    to provision labels.
239
240    Some tests behavior may changed DUT image while they don't update
241    provision-cros_version or provisioning-job_repo_url labels, which could
242    cause the next test run on the same host gets an unexpected data and
243    yields false positive test result.
244    """
245
246    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
247    def verify(self, host):
248        self._verify_cros_version(host)
249        self._verify_job_repo_url(host)
250
251    def _verify_cros_version(self, host):
252        """Verify that cros-version match version on the host."""
253        label_match = True
254        try:
255            label_match = host.verify_cros_version_label()
256        except Exception as e:
257            # We don't want fail this verifier for any errors that other
258            # than a actual version mismatch, as that can make debugging
259            # more challenge.
260            logging.warning(
261                    'Unexpected error during verify cros version on %s; %s',
262                    host.hostname, e)
263
264        if not label_match:
265            raise hosts.AutoservVerifyError('ChromeOS image on the host'
266                                            ' does not match to cros-version'
267                                            ' label.')
268
269    def _verify_job_repo_url(self, host):
270        """Verify that job_repo_url match version on the host."""
271        info = host.host_info_store.get()
272        job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
273        if not job_repo_url:
274            logging.debug('job_repo_url is empty. Skip check.')
275            return
276        os_from_host = host.get_release_builder_path()
277        if not os_from_host in job_repo_url:
278            raise hosts.AutoservVerifyError('ChromeOS image on the host'
279                                            ' does not match to job_repo_url'
280                                            ' label.')
281
282    @property
283    def description(self):
284        # pylint: disable=missing-docstring
285        return 'ChromeOS image on host matches cros_version label'
286
287
288class WritableVerifier(hosts.Verifier):
289    """
290    Confirm the stateful file systems are writable.
291
292    The standard linux response to certain unexpected file system errors
293    (including hardware errors in block devices) is to change the file
294    system status to read-only.  This checks that that hasn't happened.
295
296    The test covers the two file systems that need to be writable for
297    critical operations like AU:
298      * The (unencrypted) stateful system which includes
299        /mnt/stateful_partition.
300      * The encrypted stateful partition, which includes /var.
301
302    The test doesn't check various bind mounts; those are expected to
303    fail the same way as their underlying main mounts.  Whether the
304    Linux kernel can guarantee that is untested...
305    """
306
307    # N.B. Order matters here:  Encrypted stateful is loop-mounted from
308    # a file in unencrypted stateful, so we don't test for errors in
309    # encrypted stateful if unencrypted fails.
310    _TEST_DIRECTORIES = ['/mnt/stateful_partition', '/var/tmp']
311
312    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
313    def verify(self, host):
314        # pylint: disable=missing-docstring
315        # This deliberately stops looking after the first error.
316        # See above for the details.
317        for testdir in self._TEST_DIRECTORIES:
318            if not host.is_file_system_writable([testdir]):
319                msg = 'Can\'t create a file in %s' % testdir
320                raise hosts.AutoservVerifyError(msg)
321
322    @property
323    def description(self):
324        # pylint: disable=missing-docstring
325        return 'The stateful filesystems are writable'
326
327
328class EXT4fsErrorVerifier(hosts.Verifier):
329    """
330    Confirm we have not seen critical file system kernel errors.
331    """
332
333    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
334    def verify(self, host):
335        # pylint: disable=missing-docstring
336        # grep for stateful FS errors of the type "EXT4-fs error (device sda1):"
337        command = ("dmesg | grep -E \"EXT4-fs error \(device "
338                   "$(cut -d ' ' -f 5,9 /proc/$$/mountinfo | "
339                   "grep -e '^/mnt/stateful_partition ' | "
340                   "cut -d ' ' -f 2 | cut -d '/' -f 3)\):\"")
341        output = host.run(command=command, ignore_status=True).stdout
342        if output:
343            sample = output.splitlines()[0]
344            message = 'Saw file system error: %s' % sample
345            raise hosts.AutoservVerifyError(message)
346        # Check for other critical FS errors.
347        command = 'dmesg | grep "This should not happen!!  Data will be lost"'
348        output = host.run(command=command, ignore_status=True).stdout
349        if output:
350            message = 'Saw file system error: Data will be lost'
351            raise hosts.AutoservVerifyError(message)
352        else:
353            logging.error('Could not determine stateful mount.')
354
355    @property
356    def description(self):
357        # pylint: disable=missing-docstring
358        return 'Did not find critical file system errors'
359
360
361class UpdateSuccessVerifier(hosts.Verifier):
362    """
363    Checks that the DUT successfully finished its last provision job.
364
365    At the start of any update (e.g. for a Provision job), the code
366    creates a marker file named `PROVISION_FAILED`.  The file is located
367    in a part of the stateful partition that will be removed if an
368    update finishes successfully.  Thus, the presence of the file
369    indicates that a prior update failed.
370
371    The verifier tests for the existence of the marker file and fails if
372    it still exists.
373    """
374
375    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
376    def verify(self, host):
377        # pylint: disable=missing-docstring
378        result = host.run('test -f %s' % provisioner.PROVISION_FAILED,
379                          ignore_status=True)
380        if result.exit_status == 0:
381            raise hosts.AutoservVerifyError(
382                    'Last provision on this DUT failed')
383
384    @property
385    def description(self):
386        # pylint: disable=missing-docstring
387        return 'The most recent provision attempt on this DUT succeeded'
388
389
390class TPMStatusVerifier(hosts.Verifier):
391    """Verify that the host's TPM is in a good state."""
392
393    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
394    def verify(self, host):
395        # pylint: disable=missing-docstring
396        if _is_virtual_machine(host):
397            # We do not forward host TPM / emulated TPM to qemu VMs, so skip
398            # this verification step.
399            logging.debug('Skipped verification %s on VM', self)
400            return
401
402        try:
403            status = TpmStatus(host)
404        except hosts.AutoservVerifyError:
405            logging.info('Cannot determine the Cryptohome valid status - '
406                         'skipping check.')
407            return
408        try:
409            if not status['is_enabled']:
410                raise hosts.AutoservVerifyError(
411                        'TPM is not enabled -- Hardware is not working.')
412            if status['is_owned'] and not status['is_srk_default_auth']:
413                raise hosts.AutoservVerifyError('Cannot load the TPM SRK')
414        except KeyError:
415            logging.info('Cannot determine the TPM valid status - '
416                         'skipping check.')
417
418    @property
419    def description(self):
420        # pylint: disable=missing-docstring
421        return 'The host\'s TPM is available and working'
422
423
424class PythonVerifier(hosts.Verifier):
425    """Confirm the presence of a working Python interpreter."""
426
427    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
428    def verify(self, host):
429        # pylint: disable=missing-docstring
430        result = host.run('python -c "import json"',
431                          ignore_status=True)
432        if result.exit_status != 0:
433            message = 'The python interpreter is broken'
434            if result.exit_status == 127:
435                search = host.run('which python', ignore_status=True)
436                if search.exit_status != 0 or not search.stdout:
437                    message = ('Python is missing; may be caused by '
438                               'powerwash')
439            raise hosts.AutoservVerifyError(message)
440
441    @property
442    def description(self):
443        # pylint: disable=missing-docstring
444        return 'Python on the host is installed and working'
445
446
447class DevModeVerifier(hosts.Verifier):
448    """Verify that the host is not in dev mode."""
449
450    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
451    def verify(self, host):
452        # pylint: disable=missing-docstring
453        # Some pools are allowed to be in dev mode
454        info = host.host_info_store.get()
455        if (_DEV_MODE_ALWAYS_ALLOWED or
456                bool(info.pools & _DEV_MODE_ALLOWED_POOLS)):
457            return
458
459        result = host.run('crossystem devsw_boot', ignore_status=True).stdout
460        if result != '0':
461            raise hosts.AutoservVerifyError('The host is in dev mode')
462
463    @property
464    def description(self):
465        # pylint: disable=missing-docstring
466        return 'The host should not be in dev mode'
467
468
469class DevDefaultBootVerifier(hosts.Verifier):
470    """Verify that the host is set to boot the internal disk by default."""
471
472    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
473    def verify(self, host):
474        # pylint: disable=missing-docstring
475        result = host.run('crossystem dev_default_boot', ignore_status=True)
476        default_boot = result.stdout.strip()
477        if default_boot != 'disk':
478            raise hosts.AutoservVerifyError(
479                    'The host has incorrect dev_default_boot value: %r'
480                    % default_boot)
481
482    @property
483    def description(self):
484        # pylint: disable=missing-docstring
485        return 'The host should have dev_default_boot=disk'
486
487
488class HWIDVerifier(hosts.Verifier):
489    """Verify that the host has HWID & serial number."""
490
491    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
492    def verify(self, host):
493        # pylint: disable=missing-docstring
494        info = host.host_info_store.get()
495        if not info.board or not info.model:
496            # if board or model missed in host_info file then it is empty
497            # skip verifier
498            return
499        info_hwid = info.attributes.get('HWID')
500        info_serial_number = info.attributes.get('serial_number')
501
502        if not info_hwid or not info_serial_number:
503            logging.info('Missing HWID or/and SerialNumber.'
504                         ' Probably device was not deployed properly.'
505                         ' Marking DUT for need re-deployment.')
506            host.set_device_repair_state(
507                    cros_constants.DEVICE_STATE_NEEDS_DEPLOY)
508            return
509
510        host_hwid = host.run('crossystem hwid', ignore_status=True).stdout
511        host_serial_number = self._get_serial_number(host, info_serial_number)
512        if not host_hwid or not host_serial_number:
513            raise hosts.AutoservVerifyError(
514                    'Failed to get HWID & Serial Number for host %s' %
515                    host.hostname)
516
517        if host_hwid != info_hwid:
518            # We not fail verifier as it not critical for majority tests.
519            metrics.Counter('chromeos/autotest/repair/hwid_change').increment(
520                    fields={
521                            'host': host.hostname,
522                            'board': info.board or ''
523                    })
524            logging.info(
525                    'HWID changed to: %s required manual work'
526                    ' to fix it.', host_hwid)
527
528        if host_serial_number and host_serial_number != info_serial_number:
529            logging.info(
530                    'The SerialNumber mismatch detected %s != %s.'
531                    ' Probably attempt to replace DUT without deployment.'
532                    ' Marking DUT for need re-deployment.', info_serial_number,
533                    host_serial_number)
534            host.set_device_repair_state(
535                    cros_constants.DEVICE_STATE_NEEDS_DEPLOY)
536
537    def _get_serial_number(self, host, serial_number):
538        """Read serial_number from VPD.
539
540        If VPD does not have any value for serial_number then it will
541        try to restore from host_info.
542
543        @param host             CrosHost
544        @param serial_number    Serial-number from host-info
545        """
546        req = host.run('vpd -g serial_number', ignore_status=True)
547        # serial_number not found in the VPD info
548        if not req.stdout and req.exit_status == 3 and serial_number:
549            logging.debug('Cannot find serial_number from VPD.')
550            # check if vpd working fine without error
551            l1 = host.run('vpd -l', ignore_status=True)
552            l2 = host.run('vpd -l |grep "\"serial_number\"="',
553                          ignore_status=True)
554            if l1.exit_status == 0 and l2.exit_status == 1:
555                logging.info('Start restoring serial_number:%s for VPD.',
556                             serial_number)
557                # update serial_number for VPD
558                cmd = 'vpd -s serial_number=%s'
559                host.run(cmd % serial_number, ignore_status=True)
560                host.run('dump_vpd_log --force', ignore_status=True)
561                # reading from VPD to see what we updated
562                req = host.run('vpd -g serial_number', ignore_status=True)
563        return req.stdout
564
565    def _is_applicable(self, host):
566        if host.is_satlab():
567            logging.info('Not critical for Satlab. Skipping')
568            return False
569        return True
570
571    @property
572    def description(self):
573        # pylint: disable=missing-docstring
574        return 'The host should have valid HWID and Serial Number'
575
576
577class EnrollmentStateVerifier(hosts.Verifier):
578    """Verify that the device's enrollment state is clean.
579
580    There are two "flags" that generate 3 possible enrollment states here.
581    Flag 1 - The presence of install attributes file in
582             /home/.shadow/install_attributes.pb
583
584    Flag 2 - The value of "check_enrollment" from VPD. Can be obtained by
585             reading the cache file in
586             /mnt/stateful_partition/unencrypted/cache/vpd/full-v2.txt
587
588    The states:
589    State 1 - Device is enrolled, means flag 1 is true and in
590              flag 2 check_enrollment=1
591    State 2 - Device is consumer owned, means flag 1 is true and in
592              flag 2 check_enrollment=0
593    State 3 - Device is enrolled and has been powerwashed, means flag 1 is
594              false. If the value in flag 2 is check_enrollment=1 then the
595              device will perform forced re-enrollment check and depending
596              on the response from the server might force the device to enroll
597              again. If the value is check_enrollment=0, then device can be
598              used like a new device.
599
600    We consider state 1, and first scenario(check_enrollment=1) of state 3
601    as unacceptable state here as they may interfere with normal tests.
602    """
603
604    VPD_CACHE = '/mnt/stateful_partition/unencrypted/cache/vpd/full-v2.txt'
605
606    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
607    def verify(self, host):
608        # pylint: disable=missing-docstring
609        if self._get_enrollment_state(host):
610            raise hosts.AutoservNonCriticalVerifyError('The device is enrolled,'
611                                                       ' it may interfere with'
612                                                       ' some tests.')
613
614    def _get_enrollment_state(self, host):
615        logging.debug('checking enrollment state from VPD cache...')
616        response = host.run('grep "check_enrollment" %s' % self.VPD_CACHE,
617                            ignore_status=True)
618        if response.exit_status == 0:
619            result = response.stdout.strip()
620            logging.info('Enrollment state in VPD cache: %s', result)
621            return result == '"check_enrollment"="1"'
622
623        logging.error('Unexpected error occured during verify enrollment state'
624                      ' in VPD cache, skipping verify process.')
625        return False
626
627    def _is_applicable(self, host):
628        info = host.host_info_store.get()
629        # if os type is missing from host_info, then we assume it's cros.
630        return getattr(info, 'os', 'cros') in ('', 'cros')
631
632    @property
633    def description(self):
634        # pylint: disable=missing-docstring
635        return 'The enrollment state is clean on the host'
636
637
638class FirmwareTpmVerifier(hosts.Verifier):
639    """Verifier that firmware tpm info is correct.
640
641    For dev-signed firmware, tpm_fwver and tpm_kernver reported from
642    crossystem should always be 0x10001. Firmware update on DUTs with
643    incorrect tmp_fwver or tpm_kernver may fail due to firmware
644    rollback protection.
645    """
646    # A list of field we want check from crossystem and expected value.
647    CHECK_LIST = [
648            ('tpm_fwver', '0x00010001'),
649            ('tpm_kernver', '0x00010001'),
650    ]
651
652    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
653    def verify(self, host):
654        # pylint: disable=missing-docstring
655        for field, expected_value in self.CHECK_LIST:
656            result = host.run('crossystem %s' % field, ignore_status=True)
657            if result.exit_status != 0:
658                raise hosts.AutoservNonCriticalVerifyError(
659                        'Unable to get %s from crossystem.' % field)
660            if result.stdout != expected_value:
661                raise hosts.AutoservNonCriticalVerifyError(
662                        'Unexpected %s value: %s, expected: %s. This error'
663                        ' may cause firmware provision fail due to the'
664                        ' rollback protection.' %
665                        (field, result.stdout, expected_value))
666
667    def _is_applicable(self, host):
668        return cros_firmware._is_firmware_testing_device(host)
669
670    @property
671    def description(self):
672        # pylint: disable=missing-docstring
673        return 'Firmware tpm info is correct in crossystem.'
674
675
676class JetstreamTpmVerifier(hosts.Verifier):
677    """Verify that Jetstream TPM is in a good state."""
678
679    @retry.retry(error.AutoservError, timeout_min=2, delay_sec=10)
680    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
681    def verify(self, host):
682        # pylint: disable=missing-docstring
683        try:
684            status = TpmStatus(host)
685            if not status.tpm_enabled:
686                raise hosts.AutoservVerifyError('TPM is not enabled')
687            if not status.tpm_owned:
688                raise hosts.AutoservVerifyError('TPM is not owned')
689            if not status.tpm_can_load_srk:
690                raise hosts.AutoservVerifyError('TPM cannot load SRK')
691            if not status.tpm_can_load_srk_pubkey:
692                raise hosts.AutoservVerifyError('TPM cannot load SRK pubkey')
693
694            # Check that the TPM is fully initialized. The output of this
695            # command is line-oriented property/value pairs.
696            result = host.run('cryptohome --action=tpm_status')
697            if 'TPM Ready: true' not in result.stdout:
698                raise hosts.AutoservVerifyError('TPM is not ready')
699        except error.AutoservRunError:
700            raise hosts.AutoservVerifyError(
701                    'Could not determine TPM status')
702
703    @property
704    def description(self):
705        # pylint: disable=missing-docstring
706        return 'Jetstream TPM state check'
707
708
709class JetstreamAttestationVerifier(hosts.Verifier):
710    """Verify that Jetstream attestation client has a certificate."""
711
712    @retry.retry(error.AutoservError, timeout_min=2, delay_sec=10)
713    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
714    def verify(self, host):
715        # pylint: disable=missing-docstring
716        try:
717            # This output is in text protobuf format.
718            result = host.run('cryptohome --action=tpm_more_status')
719            if 'attestation_prepared: true' not in result.stdout:
720                raise hosts.AutoservVerifyError(
721                        'Attestation has not been prepared')
722
723            result = host.run('cryptohome --action=tpm_attestation_get_ek')
724            if 'EK Certificate' not in result.stdout:
725                raise hosts.AutoservVerifyError(
726                        'Endorsement certificate not found')
727        except error.AutoservRunError:
728            raise hosts.AutoservVerifyError(
729                    'Unable to fetch endorsement certificate')
730
731    @property
732    def description(self):
733        # pylint: disable=missing-docstring
734        return 'Jetstream attestation endorsement check'
735
736
737class JetstreamServicesVerifier(hosts.Verifier):
738    """Verify that Jetstream services are running."""
739
740    # Retry for b/62576902
741    @retry.retry(error.AutoservError, timeout_min=1, delay_sec=10)
742    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
743    def verify(self, host):
744        # pylint: disable=missing-docstring
745        try:
746            host.run('pgrep ap-controller')
747        except error.AutoservRunError:
748            raise hosts.AutoservVerifyError(
749                'ap-controller process is not running')
750
751    @property
752    def description(self):
753        # pylint: disable=missing-docstring
754        return 'Jetstream services must be running'
755
756
757class StopStartUIVerifier(hosts.Verifier):
758    """Verify that command 'stop ui' won't crash the DUT.
759
760    We run 'stop ui' in AU and provision. We found some bad images broke
761    this command and then broke all the provision of all following test. We add
762    this verifier to ensure it works and will trigger reimaging to a good
763    version if it fails.
764    """
765
766    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
767    def verify(self, host):
768        try:
769            host.run('stop ui && start ui', ignore_status=True, timeout=45)
770        except error.AutoservSSHTimeout:
771            raise hosts.AutoservVerifyError(
772                "Got timeout when stop ui/start ui. DUT might crash.")
773
774    @property
775    def description(self):
776        return 'The DUT image works fine when stop ui/start ui.'
777
778
779class GscToolPresentVerifier(hosts.Verifier):
780    """Verify that GSC tool is functional.
781
782    If board/model expected to have GSC tool but it does not have it then need
783    to re-image the host to recover it.
784    If host-info has label 'cr50' then we expect to have GSC tool on the host.
785    """
786
787    VERIFY_GSC_CMD = 'gsctool -a -f'
788
789    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
790    def verify(self, host):
791        r = host.run(self.VERIFY_GSC_CMD, ignore_status=True, timeout=10)
792        if r.exit_status != 0:
793            raise hosts.AutoservNonCriticalVerifyError(
794                    "GSC tool issue detected.")
795        logging.debug('GSC tool is functional.')
796
797    def _is_applicable(self, host):
798        host_info = host.host_info_store.get()
799        if host_info.get_label_value('cr50'):
800            return True
801        logging.info('GSC is not on the host.')
802        return False
803
804    @property
805    def description(self):
806        return 'Verify GSC tool is functional.'
807
808
809class ServoUSBDriveVerifier(hosts.Verifier):
810    """Verify that USB drive on Servo is good to use.
811
812    Check if USB drive is detected on servo and verified on servohost and
813    USB is not marked for replacement.
814    """
815
816    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
817    def verify(self, host):
818        # pylint: disable=missing-docstring
819        usb_dev = ''
820        try:
821            usb_dev = host._servo_host._probe_and_validate_usb_dev()
822        except hosts.AutoservRepairError as e:
823            # We USB drive not detected by servod
824            logging.debug('(Not critical) %s', e)
825        host_info = host.host_info_store.get()
826        if not usb_dev:
827            host_info.set_version_label(audit_const.SERVO_USB_STATE_PREFIX,
828                                        audit_const.HW_STATE_NOT_DETECTED)
829            host.host_info_store.commit(host_info)
830            raise hosts.AutoservNonCriticalVerifyError(
831                    'USB-drive is not detected or bad')
832
833        # Check if USB-drive marked for replacement.
834        usb_state = host_info.get_label_value(
835                audit_const.SERVO_USB_STATE_PREFIX)
836        if usb_state and usb_state == audit_const.HW_STATE_NEED_REPLACEMENT:
837            # Allow to use USB-key marked for replacement.
838            # Goal to collect metrics to see if DUT still can recovered
839            return
840            # TODO(otabek): restory when fix crbug.com/1164408
841            # raise hosts.AutoservNonCriticalVerifyError(
842            #         'USB-drive marked for replacement')
843
844        # The USB-drive detected and was not mark for replacement.
845        # Set as normal for future audit.
846        host_info.set_version_label(audit_const.SERVO_USB_STATE_PREFIX,
847                                    audit_const.HW_STATE_NORMAL)
848        host.host_info_store.commit(host_info)
849
850    def _is_applicable(self, host):
851        if host.servo:
852            return True
853        return False
854
855    @property
856    def description(self):
857        return 'Ensure USB drive on Servo is in good state.'
858
859
860class DUTStorageVerifier(hosts.Verifier):
861    """Verify that main storage on DUT is good to use.
862
863    Check if DUT drive is providing good SMART stats which not showing any
864    issues on it. The verifier can mark DUT for replacement if SMART stats
865    show outworn data.
866    """
867
868    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
869    def verify(self, host):
870        # pylint: disable=missing-docstring
871        verifier = audit_verify.VerifyDutStorage(host)
872        verifier.verify(set_label=True, run_badblocks='NOT')
873        state = verifier.get_state() or audit_const.HW_STATE_UNKNOWN
874        if not state:
875            raise hosts.AutoservNonCriticalVerifyError(
876                    'DUT storage did not detected or state cannot extracted.')
877        if state == audit_const.HW_STATE_NEED_REPLACEMENT:
878            logging.info('Detected issue with storage on the DUT.')
879            host.set_device_needs_replacement()
880
881    @property
882    def description(self):
883        return 'Ensure DUT storage SMART information is in good state.'
884
885
886class AuditBattery(hosts.Verifier):
887    """Verify that battery on DUT is good to use.
888
889    Check if DUT drive is providing good SMART stats which not showing any
890    issues on it. The verifier can mark DUT for replacement if SMART stats
891    show outworn data.
892    """
893
894    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
895    def verify(self, host):
896        # pylint: disable=missing-docstring
897        state = None
898        try:
899            state = self._get_validator(host).validate()
900        except Exception as e:
901            # We do not want stop main process if it fail.
902            logging.debug('(Not critical) %s', e)
903        if not state:
904            raise hosts.AutoservNonCriticalVerifyError(
905                    'DUT battery did not detected or state cannot extracted.')
906        if state == audit_const.HW_STATE_NEED_REPLACEMENT:
907            logging.info('Detected issue with storage on the DUT.')
908            host.set_device_needs_replacement()
909
910    def _is_applicable(self, host):
911        return self._get_validator(host).is_battery_expected()
912
913    def _get_validator(self, host):
914        if not getattr(self, '_validator', None):
915            self._validator = battery_validator.BatteryValidator(host)
916        return self._validator
917
918    @property
919    def description(self):
920        return 'Ensure DUT battery is in good state.'
921
922
923class ServoKeyboardMapVerifier(hosts.Verifier):
924    """Not critical verify to flash servo keyboard for the host.
925
926    Check if host support servo keyboard and update if firmware is not present.
927    """
928
929    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
930    def verify(self, host):
931        try:
932            flasher = servo_keyboard_flasher.ServoKeyboardMapFlasher()
933            if flasher.is_image_supported(host):
934                flasher.update(host)
935        except Exception as e:
936            logging.debug('(Not critical) %s', e)
937            raise hosts.AutoservNonCriticalVerifyError(
938                    'Fail to verify/update servo keyboard map on the host.')
939
940    def _is_applicable(self, host):
941        if host.servo:
942            return True
943        return False
944
945    @property
946    def description(self):
947        return 'Verify and update servo keyboard map.'
948
949
950class ServoMacAddressVerifier(hosts.Verifier):
951    """Not critical verify to cache NIC mac address for the host on servo.
952
953    Servo_v4 plugged to the DUT and providing NIC for that. We caching mac
954    address on servod side for better debugging.
955    """
956
957    @timeout_util.TimeoutDecorator(cros_constants.VERIFY_TIMEOUT_SEC)
958    def verify(self, host):
959        try:
960            helper = mac_address_helper.MacAddressHelper()
961            helper.update_if_needed(host)
962        except Exception as e:
963            logging.debug('(Not critical) %s', e)
964            raise hosts.AutoservNonCriticalVerifyError(
965                    'Fail to verify/update servo NIC mac address for host.')
966
967    def _is_applicable(self, host):
968        if host.servo:
969            return True
970        return False
971
972    @property
973    def description(self):
974        return 'Verify and update cached NIC mac address.'
975
976
977class _ResetRepairAction(hosts.RepairAction):
978    """Common handling for repair actions that reset a DUT."""
979
980    def _collect_logs(self, host):
981        """Collect logs from a successfully repaired DUT."""
982        dirname = 'after_%s' % self.tag
983        local_log_dir = crashcollect.get_crashinfo_dir(host, dirname)
984        # Collect crash info.
985        crashcollect.get_crashinfo(host, None)
986
987    def _check_reset_success(self, host):
988        """Check whether reset succeeded, and gather logs if possible."""
989        # Waiting to boot device after repair action.
990        if host.wait_up(host.BOOT_TIMEOUT):
991            if host.get_verifier_state('ssh') == hosts.VERIFY_SUCCESS:
992                logging.debug(
993                        'Skip collection logs due DUT was sshable before')
994                return
995            try:
996                # Collect logs once we regain ssh access before
997                # clobbering them.
998                self._collect_logs(host)
999            except Exception:
1000                # If the DUT is up, we want to declare success, even if
1001                # log gathering fails for some reason.  So, if there's
1002                # a failure, just log it and move on.
1003                logging.exception('Non-critical failure in log '
1004                                  'collection during %s.',
1005                                  self.tag)
1006            return
1007        raise hosts.AutoservRepairError(
1008                'Host %s is offline after %s.' % (host.hostname, self.tag),
1009                'failed_to_boot_after_' + self.tag)
1010
1011
1012class ServoSysRqRepair(_ResetRepairAction):
1013    """
1014    Repair a Chrome device by sending a system request to the kernel.
1015
1016    Sending 3 times the Alt+VolUp+x key combination (aka sysrq-x)
1017    will ask the kernel to panic itself and reboot while conserving
1018    the kernel logs in console ramoops.
1019    """
1020
1021    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1022    def repair(self, host):
1023        # pylint: disable=missing-docstring
1024        repair_utils.require_servo(host, ignore_state=True)
1025        # Press 3 times Alt+VolUp+X
1026        # no checking DUT health between each press as
1027        # killing Chrome is not really likely to fix the DUT SSH.
1028        for _ in range(3):
1029            try:
1030                host.servo.sysrq_x()
1031            except error.TestFail as ex:
1032                raise hosts.AutoservRepairError(
1033                      'cannot press sysrq-x: %s.' % str(ex),
1034                      'cannot_press_sysrq_x')
1035            # less than 5 seconds between presses.
1036            time.sleep(2.0)
1037        self._check_reset_success(host)
1038
1039    @property
1040    def description(self):
1041        # pylint: disable=missing-docstring
1042        return 'Reset the DUT via keyboard sysrq-x'
1043
1044
1045class ServoResetRepair(_ResetRepairAction):
1046    """Repair a Chrome device by resetting it with servo."""
1047
1048    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1049    def repair(self, host):
1050        # pylint: disable=missing-docstring
1051        repair_utils.require_servo(host, ignore_state=True)
1052        host.servo.get_power_state_controller().reset()
1053        self._check_reset_success(host)
1054
1055    def _is_applicable(self, host):
1056        if host.servo:
1057            return True
1058        return False
1059
1060    @property
1061    def description(self):
1062        # pylint: disable=missing-docstring
1063        return 'Reset the DUT via servo'
1064
1065
1066class ServoCr50RebootRepair(_ResetRepairAction):
1067    """
1068    Repair a Chrome device by resetting cr50 by servo.
1069
1070    Reset cr50 which is ec+ccd reset.
1071    """
1072
1073    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1074    def repair(self, host):
1075        # pylint: disable=missing-docstring
1076        try:
1077            host.servo.get_power_state_controller().cr50_reset()
1078            self._check_reset_success(host)
1079        finally:
1080            # cr50 reset will clear some some init like `ccd testlab open`
1081            # so we want to re-initialize servo after cr50 reset if the main
1082            # device uses cr50 console commands.
1083            if host.servo.main_device_uses_gsc_drv():
1084                host.servo.initialize_dut()
1085
1086    def _is_applicable(self, host):
1087        if host.servo:
1088            if host.servo.has_control('cr50_reboot'):
1089                return True
1090        return False
1091
1092    @property
1093    def description(self):
1094        # pylint: disable=missing-docstring
1095        return 'Reset(cr50) the DUT via servo'
1096
1097
1098class DevDefaultBootRepair(hosts.RepairAction):
1099    """Repair a CrOS target by setting dev_default_boot to 'disk'"""
1100
1101    @timeout_util.TimeoutDecorator(cros_constants.SHORT_REPAIR_TIMEOUT_SEC)
1102    def repair(self, host):
1103        # pylint: disable=missing-docstring
1104        host.run('crossystem dev_default_boot=disk', ignore_status=True)
1105
1106    @property
1107    def description(self):
1108        # pylint: disable=missing-docstring
1109        return "Set dev_default_boot to 'disk'"
1110
1111
1112class CrosRebootRepair(repair_utils.RebootRepair):
1113    """Repair a CrOS target by clearing dev mode and rebooting it."""
1114
1115    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1116    def repair(self, host):
1117        # pylint: disable=missing-docstring
1118        # N.B. We need to reboot regardless of whether clearing
1119        # dev_mode succeeds or fails.
1120        host.run('/usr/share/vboot/bin/set_gbb_flags.sh 0',
1121                 ignore_status=True)
1122        host.run('crossystem disable_dev_request=1',
1123                 ignore_status=True)
1124        super(CrosRebootRepair, self).repair(host)
1125
1126    @property
1127    def description(self):
1128        # pylint: disable=missing-docstring
1129        return 'Reset GBB flags and Reboot the host'
1130
1131
1132class ProvisioningLabelsRepair(hosts.RepairAction):
1133    """Repair issue with provisioning labels for the host.
1134
1135    The repair is doing simple clean up of labels as next provisioning will
1136    re-generate required fields.
1137    """
1138
1139    @timeout_util.TimeoutDecorator(cros_constants.SHORT_REPAIR_TIMEOUT_SEC)
1140    def repair(self, host):
1141        afe_utils.clean_provision_labels(host)
1142
1143    @property
1144    def description(self):
1145        # pylint: disable=missing-docstring
1146        return 'Cleanup provisioning labels for the host'
1147
1148
1149class EnrollmentCleanupRepair(hosts.RepairAction):
1150    """Cleanup enrollment state on ChromeOS device"""
1151
1152    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1153    def repair(self, host):
1154        # Reset VPD enrollment state.
1155        host.run('/usr/sbin/update_rw_vpd check_enrollment 0')
1156
1157        # Clear TPM Owner state.
1158        tpm_utils.ClearTPMOwnerRequest(host, wait_for_ready=True,
1159                                       timeout=host.BOOT_TIMEOUT)
1160
1161    def _is_applicable(self, host):
1162        info = host.host_info_store.get()
1163        # if os type is missing from host_info, then we assume it's cros.
1164        return getattr(info, 'os', 'cros') in ('', 'cros')
1165
1166    @property
1167    def description(self):
1168        # pylint: disable=missing-docstring
1169        return 'Cleanup enrollment state and reboot the host'
1170
1171
1172class ProvisionRepair(hosts.RepairAction):
1173    """
1174    Repair by re-installing a test image using quick provision.
1175
1176    Try to install the DUT's designated "stable test image" using the
1177    standard procedure for installing a new test image via quick provision.
1178    """
1179
1180    @timeout_util.TimeoutDecorator(cros_constants.LONG_REPAIR_TIMEOUT_SEC)
1181    def repair(self, host):
1182        # pylint: disable=missing-docstring
1183        image_name = host.get_cros_repair_image_name()
1184        logging.info('Staging build for provision: %s', image_name)
1185        devserver = dev_server.ImageServer.resolve(image_name, host.hostname)
1186        devserver.trigger_download(image_name, synchronous=False)
1187        update_url = tools.image_url_pattern() % (
1188                devserver.url(), image_name)
1189        afe_utils.machine_install_and_update_labels(host, update_url)
1190
1191    @property
1192    def description(self):
1193        # pylint: disable=missing-docstring
1194        return 'Re-install the stable build on the host'
1195
1196
1197class PowerWashRepair(ProvisionRepair):
1198    """
1199    Powerwash the DUT, then re-install using quick provision.
1200
1201    Powerwash the DUT, then attempt to re-install a stable test image as
1202    for `ProvisionRepair`.
1203    """
1204
1205    @timeout_util.TimeoutDecorator(cros_constants.LONG_REPAIR_TIMEOUT_SEC)
1206    def repair(self, host):
1207        # pylint: disable=missing-docstring
1208        host.run('echo "fast safe" > '
1209                 '/mnt/stateful_partition/factory_install_reset')
1210        host.reboot(timeout=host.POWERWASH_BOOT_TIMEOUT, wait=True)
1211        super(PowerWashRepair, self).repair(host)
1212
1213    @property
1214    def description(self):
1215        # pylint: disable=missing-docstring
1216        return 'Powerwash and then re-install the stable build on the host'
1217
1218
1219class ServoInstallRepair(hosts.RepairAction):
1220    """
1221    Reinstall a test image from USB using servo.
1222
1223    Use servo to re-install the DUT's designated "stable test image"
1224    from servo-attached USB storage.
1225    """
1226
1227    # Timeout value for this repair action is specially configured as we need
1228    # stage image to usb drive, install chromeos image.
1229    @timeout_util.TimeoutDecorator(60 * 60)
1230    def repair(self, host):
1231        self.boot_in_recovery = False
1232        # pylint: disable=missing-docstring
1233        repair_utils.require_servo(host, ignore_state=True)
1234        image_name = host.get_cros_repair_image_name()
1235        image_name_on_usb = host._servo_host.validate_image_usbkey()
1236        if image_name_on_usb == image_name:
1237            logging.info(
1238                    'Required image %s is already on usbkey,'
1239                    ' skipping download.', image_name)
1240            need_update_image = False
1241        else:
1242            logging.info('Required image is not on usbkey.')
1243            need_update_image = True
1244
1245        # Verify if we want to force re-image the USB.
1246        if not need_update_image and host.health_profile:
1247            repair_failed_count = host.health_profile.get_repair_fail_count()
1248            # try to re-image USB when previous attempt failed
1249            if (repair_failed_count > 0 and
1250                (repair_failed_count == 1 or repair_failed_count % 10 == 0)):
1251                logging.info(
1252                        'Required re-download image to usbkey as'
1253                        ' a previous repair failed. Fail count: %s',
1254                        repair_failed_count)
1255                need_update_image = True
1256
1257        update_url = None
1258        if need_update_image:
1259            logging.info('Staging image: %s on caching server.', image_name)
1260            _, update_url = host.stage_image_for_servo()
1261        afe_utils.clean_provision_labels(host)
1262        # Start process to install new image from USB
1263        need_snk = host.require_snk_mode_in_recovery()
1264
1265        host.servo.get_power_state_controller().power_off()
1266        if update_url:
1267            try:
1268                host.install_image_to_servo_usb(image_url=update_url)
1269            except Exception as e:
1270                # Format USB-storage as incorrect download image can cause
1271                # false believe that image downloaded.
1272                self._format_usb_storage(host)
1273                # Powering DUT on as if leave it in off mode can cause issue
1274                # with detecting ccd_cr50 on the board.
1275                host.servo.get_power_state_controller().power_on()
1276                six.reraise(error.AutotestError, str(e), sys.exc_info()[2])
1277        else:
1278            # Give the DUT some time to power_off if we skip
1279            # download image to usb. (crbug.com/982993)
1280            time.sleep(10)
1281
1282        host.boot_in_recovery_mode(need_snk=need_snk)
1283        # Note that device successful booted from USB
1284        # That mean fw RO is good.
1285        self.boot_in_recovery = True
1286        host.run_install_image(install_timeout=host.ADMIN_INSTALL_TIMEOUT * 2,
1287                               need_snk=need_snk,
1288                               is_repair=True)
1289        afe_utils.add_provision_labels(host, host.VERSION_PREFIX, image_name)
1290        # Collect info which USB-key used for successful re-image.
1291        host_info = host.host_info_store.get()
1292        if host_info:
1293            usb_state = host_info.get_label_value(
1294                    audit_const.SERVO_USB_STATE_PREFIX)
1295            metrics_data = {'host': host.hostname, 'usb_state': usb_state}
1296            metrics.Counter('chromeos/autotest/usbkey_install_success'
1297                            ).increment(fields=metrics_data)
1298
1299    def _format_usb_storage(self, host):
1300        """Format USB-storage connected to servo."""
1301        try:
1302            # Format USB-storage to prevent corrupted image to be
1303            # counted as good image.
1304            usb_path = host.servo.probe_host_usb_dev()
1305            logging.info('Formating %s', usb_path)
1306            cmd = 'mkfs.ext4 -F %s' % usb_path
1307            host._servo_host.run(cmd, ignore_status=True)
1308        except Exception as e:
1309            logging.info('(Not critical) fail to format USB-storage: %s', e)
1310
1311    @property
1312    def description(self):
1313        # pylint: disable=missing-docstring
1314        return 'Reinstall from USB using servo'
1315
1316
1317class ServoResetAfterUSBRepair(_ResetRepairAction):
1318    """Repair a host by resetting it with servo.
1319
1320    This is follow up action for cases when device fail to boot as part of
1321    USB-install. The repair will be applicable only if device was successful
1322    booted from USB-key.
1323    """
1324
1325    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1326    def repair(self, host):
1327        # pylint: disable=missing-docstring
1328        host.servo.get_power_state_controller().reset()
1329        self._check_reset_success(host)
1330
1331    def _is_applicable(self, host):
1332        if not host.servo:
1333            return False
1334        if host.is_marked_for_replacement():
1335            logging.debug('The device marked for replacement.'
1336                          ' Skip the action.')
1337            return False
1338        usb_install = host.get_repair_strategy_node('usb')
1339        if not usb_install:
1340            logging.debug('Strategy node not found! Skip repair action.')
1341            return False
1342        if not getattr(usb_install, 'boot_in_recovery', False):
1343            logging.debug('Device did not boot in recovery mode.'
1344                          ' Skip repair action.')
1345            return False
1346        return True
1347
1348    @property
1349    def description(self):
1350        # pylint: disable=missing-docstring
1351        return 'Reset the DUT via servo after USB-install'
1352
1353
1354class RecoverFwAfterUSBRepair(_ResetRepairAction):
1355    """Recover FW on the host when host can boot in recovery mode.
1356
1357    This is follow up action for cases when device fail to boot as part of
1358    USB-install but successful booted in recovery mode.
1359
1360    If host can boot in recovery mode but fail boot in default mode then
1361    probably we have corrupted firmware. The repair try to recover firmware
1362    on the host by booting from USB-key.
1363    """
1364
1365    # Command to update firmware located on host
1366    _FW_UPDATE_CMD = 'chromeos-firmwareupdate --mode=recovery'
1367
1368    @timeout_util.TimeoutDecorator(cros_constants.LONG_REPAIR_TIMEOUT_SEC)
1369    def repair(self, host):
1370        # pylint: disable=missing-docstring
1371        # Switch USB_key to servo to wake up it as sometimes it can show
1372        # USB-key direction to DUT but it is not yet seeing by DUT.
1373        host.servo.switch_usbkey('host')
1374        time.sleep(host.servo.USB_DETECTION_DELAY)
1375        # Power off the DUT as in this case the host will boot
1376        # in recovery mode with higher chance.
1377        host.servo.get_power_state_controller().power_off()
1378        # Give the DUT some time to power_off if we skip
1379        # download image to usb. (crbug.com/982993)
1380        time.sleep(10)
1381
1382        # Boot host in recovery mode as it is working and verified
1383        # by another repair action.
1384        need_snk = host.require_snk_mode_in_recovery()
1385        try:
1386            host.boot_in_recovery_mode(need_snk=need_snk)
1387            logging.debug('Host booted in recovery mode')
1388
1389            result = host.run(self._FW_UPDATE_CMD, ignore_status=True)
1390            if result.exit_status != 0:
1391                logging.error('chromeos-firmwareupdate failed: %s',
1392                              result.stdout.strip())
1393            host.halt()
1394        finally:
1395            # We need reset the DUT no matter success or not,
1396            # as we don't want leave the DUT in boot from usb state.
1397            # N.B. The Servo API requires that we use power_on() here
1398            # for two reasons:
1399            #  1) After turning on a DUT in recovery mode, you must turn
1400            #     it off and then on with power_on() once more to
1401            #     disable recovery mode (this is a Parrot specific
1402            #     requirement).
1403            #  2) After power_off(), the only way to turn on is with
1404            #     power_on() (this is a Storm specific requirement).
1405            logging.debug('Power cycling DUT through servo.')
1406            host.servo.get_power_state_controller().power_off()
1407            host.servo.switch_usbkey('off')
1408            if need_snk:
1409                # Attempt to restore servo_v4 role to 'src' mode.
1410                host.servo.set_servo_v4_role('src')
1411            # Use cold-reset instead 'on' to increase the chance to boot DUT
1412            host.servo.get_power_state_controller().reset()
1413        self._check_reset_success(host)
1414
1415    def _is_applicable(self, host):
1416        if not host.servo:
1417            return False
1418        if host.is_marked_for_replacement():
1419            logging.debug('The device marked for replacement.'
1420                          ' Skip the action.')
1421            return False
1422        usb_install = host.get_repair_strategy_node('usb')
1423        if not usb_install:
1424            logging.debug('Strategy node not found! Skip repair action.')
1425            return False
1426        if not getattr(usb_install, 'boot_in_recovery', False):
1427            logging.debug('Device did not boot in recovery mode.'
1428                          ' Skip repair action.')
1429            return False
1430        dhp = host.health_profile
1431        if not dhp:
1432            logging.info('Device health profile is not available, cannot'
1433                         ' determine if firmware repair is needed.')
1434            return False
1435        if dhp.get_failed_repair_action(self.tag) > 2:
1436            logging.info('Firmware recovery has been attempted and failed 3'
1437                         ' times, no need to retry.')
1438            return False
1439        return True
1440
1441    @property
1442    def description(self):
1443        # pylint: disable=missing-docstring
1444        return 'Recover FW on the host after USB-install'
1445
1446
1447class RecoverACPowerRepair(_ResetRepairAction):
1448    """Recover AC detection if AC is not detected.
1449
1450    The fix based on toggle PD negotiating on EC level of DUT.
1451    Repair works only for the DUT which has EC and battery.
1452    """
1453
1454    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1455    def repair(self, host):
1456        # pylint: disable=missing-docstring
1457        repair_utils.require_servo(host, ignore_state=True)
1458        # Verify that EC is available and we can interact with that.
1459        # Do not put it in '_is_applicable' to avoid extra DUT reset.
1460        try:
1461            host.servo.get_ec_board()
1462        except Exception as e:
1463            logging.debug('(Not critical) %s', e)
1464            # if EC is off it will fail to execute any EC command
1465            # to wake it up we do cold-reboot then we will have active ec
1466            # connection for ~30 seconds
1467            host.servo.get_power_state_controller().reset()
1468        try:
1469            if host.servo.get('battery_is_charging'):
1470                # device is changing.
1471                return
1472        except Exception as e:
1473            logging.debug('(Not critical) %s', e)
1474            raise hosts.AutoservRepairError(
1475                    'Fail to read battery metrics from EC')
1476        # Simple off-on not always working stable in all cases as source-sink
1477        # not working too in another cases. To cover more cases here we do
1478        # both toggle to recover PD negotiation.
1479        # Source/sink switching CC lines to make DUT work as supplying or
1480        # consuming power (between Rp and Rd).
1481        self._set_pd_dualrole(host, 'off')
1482        self._set_pd_dualrole(host, 'on')
1483        self._set_pd_dualrole(host, 'source')
1484        self._set_pd_dualrole(host, 'sink')
1485        # wait to reinitialize PD negotiation and charge a little bit
1486        time.sleep(120)
1487        # Recommended to reset EC after manipulation with PD
1488        host.servo.get_power_state_controller().reset()
1489        # Verify if repair well done.
1490        if not host.servo.get('battery_is_charging'):
1491            raise hosts.AutoservRepairError(
1492                    'Fail recovery AC detection fo the DUT.',
1493                    'failed_recover_usb_pd_ac')
1494        self._check_reset_success(host)
1495
1496    def _set_pd_dualrole(self, host, role):
1497        host.servo.set_nocheck('ec_uart_flush', 'off')
1498        host.servo.set_nocheck('ec_uart_cmd', 'pd dualrole %s' % role)
1499        host.servo.set_nocheck('ec_uart_flush', 'on')
1500        time.sleep(1)
1501
1502    def _is_applicable(self, host):
1503        if not host._servo_host.is_ec_supported():
1504            logging.info('The board not support EC')
1505            return False
1506        host_info = host.host_info_store.get()
1507        if host_info.get_label_value('power') != 'battery':
1508            logging.info('The board does not have battery')
1509            return False
1510        return True
1511
1512    @property
1513    def description(self):
1514        # pylint: disable=missing-docstring
1515        return 'Recovery AC of DUT'
1516
1517
1518class JetstreamTpmRepair(hosts.RepairAction):
1519    """Repair by resetting TPM and rebooting."""
1520
1521    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1522    def repair(self, host):
1523        # pylint: disable=missing-docstring
1524        host.run('rm -f /var/cache/ap/setup-network', ignore_status=True)
1525        host.run('rm -f /home/chronos/.oobe_completed', ignore_status=True)
1526        host.run('rm -f /home/.shadow/.can_attempt_ownership',
1527                 ignore_status=True)
1528        host.run('crossystem clear_tpm_owner_request=1', ignore_status=True)
1529        host.reboot()
1530
1531    @property
1532    def description(self):
1533        # pylint: disable=missing-docstring
1534        return 'Reset TPM and reboot'
1535
1536
1537class JetstreamServiceRepair(hosts.RepairAction):
1538    """Repair by restarting Jetstream services."""
1539
1540    @timeout_util.TimeoutDecorator(cros_constants.REPAIR_TIMEOUT_SEC)
1541    def repair(self, host):
1542        # pylint: disable=missing-docstring
1543        host.cleanup_services()
1544
1545    @property
1546    def description(self):
1547        # pylint: disable=missing-docstring
1548        return 'Restart Jetstream services'
1549
1550
1551def _cros_verify_dag():
1552    """Return the verification DAG for a `CrosHost`."""
1553    return _cros_verify_base_dag() + _cros_verify_extended_dag()
1554
1555
1556def _cros_verify_base_dag():
1557    """Return the base verification DAG for a `CrosHost`."""
1558    FirmwareStatusVerifier = cros_firmware.FirmwareStatusVerifier
1559    FirmwareVersionVerifier = cros_firmware.FirmwareVersionVerifier
1560    verify_dag = (
1561            (repair_utils.PingVerifier, 'ping', ()),
1562            (repair_utils.SshVerifier, 'ssh', ('ping', )),
1563            (ServoUSBDriveVerifier, 'usb_drive', ()),
1564            (DevDefaultBootVerifier, 'dev_default_boot', ('ssh', )),
1565            (DevModeVerifier, 'devmode', ('ssh', )),
1566            (EnrollmentStateVerifier, 'enrollment_state', ('ssh', )),
1567            (HWIDVerifier, 'hwid', ('ssh', )),
1568            (ACPowerVerifier, 'power', ('ssh', )),
1569            (EXT4fsErrorVerifier, 'ext4', ('ssh', )),
1570            (WritableVerifier, 'writable', ('ssh', )),
1571            (TPMStatusVerifier, 'tpm', ('ssh', )),
1572            (UpdateSuccessVerifier, 'good_provision', ('ssh', )),
1573            (FirmwareTpmVerifier, 'faft_tpm', ('ssh', )),
1574            (FirmwareStatusVerifier, 'fwstatus', ('ssh', )),
1575            (FirmwareVersionVerifier, 'rwfw', ('ssh', )),
1576            (PythonVerifier, 'python', ('ssh', )),
1577            (repair_utils.LegacyHostVerifier, 'cros', ('ssh', )),
1578            (ProvisioningLabelsVerifier, 'provisioning_labels', ('ssh', )),
1579    )
1580    return verify_dag
1581
1582
1583def _cros_verify_extended_dag():
1584    """Return the extended verification DAG for a `CrosHost`."""
1585    return (
1586            (StopStartUIVerifier, 'stop_start_ui', ('ssh', )),
1587            (DUTStorageVerifier, 'storage', ('ssh', )),
1588            (AuditBattery, 'audit_battery', ()),
1589            (GscToolPresentVerifier, 'dut_gsctool', ('ssh', )),
1590            (ServoKeyboardMapVerifier, 'dut_servo_keyboard', ('ssh', )),
1591            (ServoMacAddressVerifier, 'dut_servo_macaddr', ('ssh', )),
1592    )
1593
1594
1595def _cros_basic_repair_actions(
1596    servo_reset_trigger=DEFAULT_SERVO_RESET_TRIGGER
1597):
1598    """Return the basic repair actions for a `CrosHost`
1599
1600    @param servo_reset_trigger: sequence of verifiers that trigger servo reset
1601    and servo cr50 reboot repair.
1602    """
1603    repair_actions = (
1604            # RPM cycling must precede Servo reset:  if the DUT has a dead
1605            # battery, we need to reattach AC power before we reset via servo.
1606            (repair_utils.RPMCycleRepair, 'rpm', (), (
1607                    'ping',
1608                    'ssh',
1609                    'power',
1610            )),
1611            (ServoResetRepair, 'servoreset', (), servo_reset_trigger),
1612            (ServoCr50RebootRepair, 'cr50_reset', (), servo_reset_trigger),
1613            (ServoSysRqRepair, 'sysrq', (), (
1614                    'ping',
1615                    'ssh',
1616            )),
1617            (ProvisioningLabelsRepair, 'provisioning_labels_repair', ('ssh', ),
1618             ('provisioning_labels', )),
1619
1620            # N.B. FaftFirmwareRepair can't fix a 'good_provision' failure
1621            # directly, because it doesn't remove the flag file that triggers
1622            # the failure.  We include it as a repair trigger because it's
1623            # possible the the last update failed because of the firmware,
1624            # and we want the repair steps below to be able to trust the
1625            # firmware.
1626            (cros_firmware.FaftFirmwareRepair, 'faft_firmware_repair', (), (
1627                    'ping',
1628                    'ssh',
1629                    'fwstatus',
1630                    'good_provision',
1631            )),
1632            (DevDefaultBootRepair, 'set_default_boot', ('ssh', ),
1633             ('dev_default_boot', )),
1634            (CrosRebootRepair, 'reboot', ('ssh', ), (
1635                    'devmode',
1636                    'writable',
1637            )),
1638            (EnrollmentCleanupRepair, 'cleanup_enrollment', ('ssh', ),
1639             ('enrollment_state', )),
1640    )
1641    return repair_actions
1642
1643
1644def _cros_extended_repair_actions(provision_triggers=_CROS_PROVISION_TRIGGERS,
1645                                  powerwash_triggers=_CROS_POWERWASH_TRIGGERS,
1646                                  usb_triggers=_CROS_USB_TRIGGERS,
1647                                  usb_dependencies=_CROS_USB_DEPENDENCIES):
1648    """Return the extended repair actions for a `CrosHost`"""
1649
1650    # The dependencies and triggers for the 'provision', 'powerwash', and 'usb'
1651    # repair actions stack up:  Each one is able to repair progressively
1652    # more verifiers than the one before.  The 'triggers' lists specify
1653    # the progression.
1654
1655    repair_actions = (
1656            (ProvisionRepair, 'provision', usb_triggers + powerwash_triggers,
1657             provision_triggers),
1658            (PowerWashRepair, 'powerwash', usb_triggers,
1659             powerwash_triggers + provision_triggers),
1660            (
1661                    ServoInstallRepair,
1662                    'usb',
1663                    usb_dependencies,
1664                    # faft_tpm is a trigger of usb repair action but should not be
1665                    # dependence of provision and powerwash repair action, due to
1666                    # restriction of current structure, we hardcode it here instead
1667                    # of put it into _CROS_USB_TRIGGERS. TODO(xianuowang@) refactor
1668                    # the logic to create action/verifier DAG for different host
1669                    # type after we decouple infra from test autotest repo.
1670                    usb_triggers + powerwash_triggers + provision_triggers +
1671                    ('faft_tpm', )),
1672    )
1673    return repair_actions
1674
1675
1676def _cros_repair_actions():
1677    """Return the repair actions for a `CrosHost`."""
1678
1679    servo_reset_trigger = DEFAULT_SERVO_RESET_TRIGGER
1680    firmware_triggers = _CROS_FIRMWARE_TRIGGERS
1681    ac_triggers = _CROS_AC_TRIGGERS
1682    usb_dependencies = _CROS_USB_DEPENDENCIES
1683    provision_triggers = _CROS_PROVISION_TRIGGERS + (
1684            'stop_start_ui',
1685            'dut_gsctool',
1686    )
1687    powerwash_triggers = _CROS_POWERWASH_TRIGGERS
1688    usb_triggers = _CROS_USB_TRIGGERS
1689
1690    repair_actions = (
1691            # RPM cycling must precede Servo reset:  if the DUT has a dead
1692            # battery, we need to reattach AC power before we reset via servo.
1693            (repair_utils.RPMCycleRepair, 'rpm', (), (
1694                    'ping',
1695                    'ssh',
1696                    'power',
1697            )),
1698            (ServoResetRepair, 'servoreset', (), servo_reset_trigger),
1699            (ServoCr50RebootRepair, 'cr50_reset', (), servo_reset_trigger),
1700            (ServoSysRqRepair, 'sysrq', (), (
1701                    'ping',
1702                    'ssh',
1703            )),
1704            (ProvisioningLabelsRepair, 'provisioning_labels_repair', ('ssh', ),
1705             ('provisioning_labels', )),
1706
1707            # N.B. FaftFirmwareRepair can't fix a 'good_provision' failure
1708            # directly, because it doesn't remove the flag file that triggers
1709            # the failure.  We include it as a repair trigger because it's
1710            # possible the the last update failed because of the firmware,
1711            # and we want the repair steps below to be able to trust the
1712            # firmware.
1713            (cros_firmware.FaftFirmwareRepair, 'faft_firmware_repair', (), (
1714                    'ping',
1715                    'ssh',
1716                    'fwstatus',
1717                    'good_provision',
1718            )),
1719            (DevDefaultBootRepair, 'set_default_boot', ('ssh', ),
1720             ('dev_default_boot', )),
1721            (CrosRebootRepair, 'reboot', ('ssh', ), (
1722                    'devmode',
1723                    'writable',
1724            )),
1725            (EnrollmentCleanupRepair, 'cleanup_enrollment', ('ssh', ),
1726             ('enrollment_state', )),
1727            (cros_firmware.GeneralFirmwareRepair, 'general_firmware',
1728             usb_dependencies, firmware_triggers),
1729            (RecoverACPowerRepair, 'ac_recover', (), ac_triggers),
1730            (ProvisionRepair, 'provision', usb_triggers + powerwash_triggers,
1731             provision_triggers),
1732            (PowerWashRepair, 'powerwash', usb_triggers,
1733             powerwash_triggers + provision_triggers),
1734            (
1735                    ServoInstallRepair,
1736                    'usb',
1737                    usb_dependencies,
1738                    # faft_tpm is a trigger of usb repair action but should
1739                    # not be dependence of provision and powerwash repair
1740                    # action, due to restriction of current structure, we
1741                    # hardcode it here instead of put it into
1742                    # _CROS_USB_TRIGGERS. TODO(xianuowang@) refactor the logic
1743                    # to create action/verifier DAG for different host type
1744                    # after we decouple infra from test autotest repo.
1745                    usb_triggers + powerwash_triggers + provision_triggers +
1746                    ('faft_tpm', )),
1747            (ServoResetAfterUSBRepair, 'servo_reset_after_usb',
1748             (usb_dependencies), (
1749                     'ping',
1750                     'ssh',
1751             )),
1752            (RecoverFwAfterUSBRepair, 'recover_fw_after_usb',
1753             (usb_dependencies), (
1754                     'ping',
1755                     'ssh',
1756             )),
1757    )
1758    return repair_actions
1759
1760
1761def create_cros_repair_strategy():
1762    """Return a `RepairStrategy` for a `CrosHost`."""
1763    verify_dag = _cros_verify_dag()
1764    repair_actions = _cros_repair_actions()
1765    return hosts.RepairStrategy(verify_dag, repair_actions, 'cros')
1766
1767
1768def _moblab_verify_dag():
1769    """Return the verification DAG for a `MoblabHost`."""
1770    verify_dag = (
1771        (repair_utils.SshVerifier,        'ssh',     ()),
1772        (ACPowerVerifier,                 'power',   ('ssh',)),
1773        (PythonVerifier,                  'python',  ('ssh',)),
1774        (repair_utils.LegacyHostVerifier, 'cros',    ('ssh',)),
1775    )
1776    return verify_dag
1777
1778
1779def _moblab_repair_actions():
1780    """Return the repair actions for a `MoblabHost`."""
1781    repair_actions = (
1782        (repair_utils.RPMCycleRepair, 'rpm', (), ('ssh', 'power',)),
1783        (ProvisionRepair, 'provision', ('ssh',), ('power', 'python', 'cros')),
1784    )
1785    return repair_actions
1786
1787
1788def create_moblab_repair_strategy():
1789    """
1790    Return a `RepairStrategy` for a `MoblabHost`.
1791
1792    Moblab is a subset of the CrOS verify and repair.  Several pieces
1793    are removed because they're not expected to be meaningful.  Some
1794    others are removed for more specific reasons:
1795
1796    'tpm':  Moblab DUTs don't run the tests that matter to this
1797        verifier.  TODO(jrbarnette)  This assertion is unproven.
1798
1799    'good_provision':  This verifier can't pass, because the Moblab provision
1800        procedure doesn't properly delete the PROVISION_FAILED file.
1801        TODO(jrbarnette) We should refactor ChromiumOSProvisioner so
1802        that it can be different for Moblab.
1803
1804    'firmware':  Moblab DUTs shouldn't be in FAFT pools, so we don't try
1805        this.
1806
1807    'powerwash':  Powerwash on Moblab causes trouble with deleting the
1808        DHCP leases file, so we skip it.
1809    """
1810    verify_dag = _moblab_verify_dag()
1811    repair_actions = _moblab_repair_actions()
1812    return hosts.RepairStrategy(verify_dag, repair_actions, 'moblab')
1813
1814
1815def _jetstream_repair_actions():
1816    """Return the repair actions for a `JetstreamHost`."""
1817    provision_triggers = _CROS_PROVISION_TRIGGERS
1818    jetstream_tpm_triggers = ('jetstream_tpm', 'jetstream_attestation')
1819    jetstream_service_triggers = (jetstream_tpm_triggers +
1820                                  ('jetstream_services',))
1821    base_actions = _cros_basic_repair_actions(servo_reset_trigger=(
1822            'ping',
1823            'ssh',
1824    ))
1825    custom_actions = (
1826            (JetstreamTpmRepair, 'jetstream_tpm_repair',
1827             _JETSTREAM_USB_TRIGGERS + _CROS_POWERWASH_TRIGGERS,
1828             provision_triggers + jetstream_tpm_triggers),
1829            (JetstreamServiceRepair, 'jetstream_service_repair',
1830             _JETSTREAM_USB_TRIGGERS + _CROS_POWERWASH_TRIGGERS +
1831             ('jetstream_tpm', 'jetstream_attestation'),
1832             provision_triggers + jetstream_service_triggers),
1833    )
1834    extend_actions = _cros_extended_repair_actions(
1835            provision_triggers=provision_triggers + jetstream_service_triggers,
1836            usb_triggers=_JETSTREAM_USB_TRIGGERS)
1837    return base_actions + custom_actions + extend_actions
1838
1839
1840def _jetstream_verify_dag():
1841    """Return the verification DAG for a `JetstreamHost`."""
1842    verify_dag = _cros_verify_base_dag() + (
1843        (JetstreamTpmVerifier, 'jetstream_tpm', ('ssh',)),
1844        (JetstreamAttestationVerifier, 'jetstream_attestation', ('ssh',)),
1845        (JetstreamServicesVerifier, 'jetstream_services', ('ssh',)),
1846    )
1847    return verify_dag
1848
1849
1850def create_jetstream_repair_strategy():
1851    """
1852    Return a `RepairStrategy` for a `JetstreamHost`.
1853
1854    The Jetstream repair strategy is based on the CrOS verify and repair,
1855    but adds the JetstreamServicesVerifier.
1856    """
1857    verify_dag = _jetstream_verify_dag()
1858    repair_actions = _jetstream_repair_actions()
1859    return hosts.RepairStrategy(verify_dag, repair_actions, 'jetstream')
1860
1861
1862# TODO(pprabhu) Move this to a better place. I have no idea what that place
1863# would be.
1864def _is_virtual_machine(host):
1865    """Determine whether the given |host| is a virtual machine.
1866
1867    @param host: a hosts.Host object.
1868    @returns True if the host is a virtual machine, False otherwise.
1869    """
1870    output = host.run('cat /proc/cpuinfo | grep "model name"',
1871                      ignore_status=True)
1872    return (output.exit_status == 0 and output.stdout and
1873            'qemu' in output.stdout.lower())
1874
1875
1876class TpmStatus(dict):
1877    """Wrapper for getting cryptohome status from a host."""
1878
1879    def __init__(self, host):
1880        super(TpmStatus, self).__init__()
1881        self.update(_get_tpm_status(host))
1882
1883    @property
1884    def tpm_enabled(self):
1885        # pylint: disable=missing-docstring
1886        return self.get('is_enabled') == True
1887
1888    @property
1889    def tpm_owned(self):
1890        # pylint: disable=missing-docstring
1891        return self.get('is_owned') == True
1892
1893    @property
1894    def tpm_can_load_srk(self):
1895        # pylint: disable=missing-docstring
1896        return self.tpm_owned and self.get('is_srk_default_auth') == True
1897
1898    @property
1899    def tpm_can_load_srk_pubkey(self):
1900        # pylint: disable=missing-docstring
1901        return self.tpm_owned and self.get('is_srk_default_auth') == True
1902
1903
1904def _get_tpm_status(host):
1905    """Returns a dictionary containing the TPM status.
1906
1907    @param host: a hosts.Host object.
1908    @returns A dictionary containing the TPM status.
1909    @raises AutoservVerifyError: if the output could not be parsed or the TPM
1910       status is missing.
1911    @raises hosts.AutoservRunError: if the cryptohome command failed.
1912    """
1913    try:
1914        output = host.run(
1915                'tpm_manager_client status --nonsensitive').stdout.strip()
1916        lines = output.split('\n')[1:-1]
1917        status = {}
1918        for item in lines:
1919            item = item.split(':')
1920            if not item[0]:
1921                continue
1922            if len(item) == 1:
1923                item.append('')
1924            item = [x.strip() for x in item]
1925            item[1] = True if item[1] == 'true' else item[1]
1926            item[1] = False if item[1] == 'false' else item[1]
1927            status[item[0]] = item[1]
1928        if status['status'] != 'STATUS_SUCCESS':
1929            raise hosts.AutoservVerifyError('TPM status is missing')
1930        return status
1931    except ValueError:
1932        raise hosts.AutoservVerifyError('Unable to parse cryptohome status')
1933