autotest: Perform cold reboot on test device to enable VM

There's an issue where some devices need to undergo a cold reboot in
order to reset the EC so a bit can get flipped to enable the VM. This
checks if the device supports VM functionality by testing the existence
of /usr/bin/vm_concierge and then if the cold reboot is needed by the
lack of /dev/kvm. If both those are true it uses ectool to do a cold
reboot.

BUG=chromium:892782
TEST=Manually verified on locked DUT

Change-Id: I5d1994d896814e874f6f5659263d90b63e08d970
Reviewed-on: https://chromium-review.googlesource.com/1262395
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Jeffrey Kardatzke <jkardatzke@google.com>
Reviewed-by: Jeffrey Kardatzke <jkardatzke@google.com>
diff --git a/server/hosts/cros_repair.py b/server/hosts/cros_repair.py
index 85f3e12..a09e3d3 100644
--- a/server/hosts/cros_repair.py
+++ b/server/hosts/cros_repair.py
@@ -405,6 +405,22 @@
         return 'Jetstream services must be running'
 
 
+class KvmExistsVerifier(hosts.Verifier):
+    """Verify that /dev/kvm exists if it should be there"""
+
+    def verify(self, host):
+        # pylint: disable=missing-docstring
+        result = host.run('[ ! -e /dev/kvm -a -f /usr/bin/vm_concierge ]',
+                          ignore_status=True)
+        if result.exit_status == 0:
+            raise hosts.AutoservVerifyError('/dev/kvm is missing')
+
+    @property
+    def description(self):
+        # pylint: disable=missing-docstring
+        return '/dev/kvm should exist if device supports Linux VMs'
+
+
 class _ResetRepairAction(hosts.RepairAction):
     """Common handling for repair actions that reset a DUT."""
 
@@ -565,6 +581,24 @@
         return 'Reinstall from USB using servo'
 
 
+class ColdRebootRepair(_ResetRepairAction):
+    """
+    Repair a Chrome device by performing a cold reboot that resets the EC.
+
+    Use ectool to perform a cold reboot which will reset the EC.
+    """
+
+    def repair(self, host):
+        # pylint: disable=missing-docstring
+        host.reboot(reboot_cmd='ectool reboot_ec cold')
+        self._check_reset_success(host)
+
+    @property
+    def description(self):
+        # pylint: disable=missing-docstring
+        return 'Reset the DUT via cold reboot with ectool'
+
+
 class JetstreamTpmRepair(hosts.RepairAction):
     """Repair by resetting TPM and rebooting."""
 
@@ -613,6 +647,7 @@
         (FirmwareVersionVerifier,         'rwfw',     ('ssh',)),
         (PythonVerifier,                  'python',   ('ssh',)),
         (repair_utils.LegacyHostVerifier, 'cros',     ('ssh',)),
+        (KvmExistsVerifier,               'ec_reset', ('ssh',)),
     )
     return verify_dag
 
@@ -636,6 +671,8 @@
         (FirmwareRepair, 'firmware', (), ('ssh', 'fwstatus', 'good_au',)),
 
         (CrosRebootRepair, 'reboot', ('ssh',), ('devmode', 'writable',)),
+
+        (ColdRebootRepair, 'coldboot', ('ssh',), ('ec_reset',)),
     )
     return repair_actions
 
diff --git a/server/hosts/cros_repair_unittest.py b/server/hosts/cros_repair_unittest.py
index a538bbd..0cdbff5 100755
--- a/server/hosts/cros_repair_unittest.py
+++ b/server/hosts/cros_repair_unittest.py
@@ -29,6 +29,7 @@
     (cros_firmware.FirmwareVersionVerifier, 'rwfw', ('ssh',)),
     (cros_repair.PythonVerifier, 'python', ('ssh',)),
     (repair_utils.LegacyHostVerifier, 'cros', ('ssh',)),
+    (cros_repair.KvmExistsVerifier, 'ec_reset', ('ssh',)),
 )
 
 CROS_REPAIR_ACTIONS = (
@@ -39,6 +40,8 @@
      'firmware', (), ('ssh', 'fwstatus', 'good_au')),
     (cros_repair.CrosRebootRepair,
      'reboot', ('ssh',), ('devmode', 'writable',)),
+    (cros_repair.ColdRebootRepair,
+     'coldboot', ('ssh',), ('ec_reset',)),
     (cros_repair.AutoUpdateRepair,
      'au',
      ('ssh', 'writable', 'tpm', 'good_au', 'ext4'),
@@ -81,6 +84,7 @@
     (cros_firmware.FirmwareVersionVerifier, 'rwfw', ('ssh',)),
     (cros_repair.PythonVerifier, 'python', ('ssh',)),
     (repair_utils.LegacyHostVerifier, 'cros', ('ssh',)),
+    (cros_repair.KvmExistsVerifier, 'ec_reset', ('ssh',)),
     (cros_repair.JetstreamTpmVerifier, 'jetstream_tpm', ('ssh',)),
     (cros_repair.JetstreamAttestationVerifier, 'jetstream_attestation',
      ('ssh',)),
@@ -95,6 +99,8 @@
      'firmware', (), ('ssh', 'fwstatus', 'good_au')),
     (cros_repair.CrosRebootRepair,
      'reboot', ('ssh',), ('devmode', 'writable',)),
+    (cros_repair.ColdRebootRepair,
+     'coldboot', ('ssh',), ('ec_reset',)),
     (cros_repair.JetstreamTpmRepair,
      'jetstream_tpm_repair',
      ('ssh', 'writable', 'tpm', 'good_au', 'ext4'),