• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import json
6import logging
7import os
8import time
9
10import common
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib import global_config
13from autotest_lib.client.common_lib import hosts
14from autotest_lib.client.common_lib.cros import retry
15from autotest_lib.server import afe_utils
16from autotest_lib.server import crashcollect
17from autotest_lib.server.hosts import repair
18from autotest_lib.server.hosts import cros_firmware
19
20# _DEV_MODE_ALLOW_POOLS - The set of pools that are allowed to be
21# in dev mode (usually, those should be unmanaged devices)
22#
23_DEV_MODE_ALLOWED_POOLS = set(
24    global_config.global_config.get_config_value(
25            'CROS',
26            'pools_dev_mode_allowed',
27            type=str,
28            default='',
29            allow_blank=True).split(','))
30
31# Setting to suppress dev mode check; primarily used for moblab where all
32# DUT's are in dev mode.
33_DEV_MODE_ALWAYS_ALLOWED = global_config.global_config.get_config_value(
34            'CROS',
35            'dev_mode_allowed',
36            type=bool,
37            default=False)
38
39# Triggers for the 'au', 'powerwash', and 'usb' repair actions.
40# These are also used as dependencies in the `CrosHost` repair
41# sequence, as follows:
42#
43# au:
44#   - triggers: _CROS_AU_TRIGGERS
45#   - depends on: _CROS_USB_TRIGGERS + _CROS_POWERWASH_TRIGGERS
46#
47# powerwash:
48#   - triggers: _CROS_POWERWASH_TRIGGERS + _CROS_AU_TRIGGERS
49#   - depends on: _CROS_USB_TRIGGERS
50#
51# usb:
52#   - triggers: _CROS_USB_TRIGGERS + _CROS_POWERWASH_TRIGGERS +
53#               _CROS_AU_TRIGGERS
54#   - no dependencies
55#
56# N.B. AC power detection depends on software on the DUT, and there
57# have been bugs where detection failed even though the DUT really
58# did have power.  So, we make the 'power' verifier a trigger for
59# reinstall repair actions, too.
60#
61# TODO(jrbarnette):  AU repair can't fix all problems reported by
62# the 'cros' verifier; it's listed as an AU trigger as a
63# simplification.  The ultimate fix is to split the 'cros' verifier
64# into smaller individual verifiers.
65_CROS_AU_TRIGGERS = ('power', 'rwfw', 'python', 'cros',)
66_CROS_POWERWASH_TRIGGERS = ('tpm', 'good_au', 'ext4',)
67_CROS_USB_TRIGGERS = ('ssh', 'writable',)
68
69
70class ACPowerVerifier(hosts.Verifier):
71    """Check for AC power and a reasonable battery charge."""
72
73    def verify(self, host):
74        # Temporarily work around a problem caused by some old FSI
75        # builds that don't have the power_supply_info command by
76        # ignoring failures.  The repair triggers believe that this
77        # verifier can't be fixed by re-installing, which means if a DUT
78        # gets stuck with one of those old builds, it can't be repaired.
79        #
80        # TODO(jrbarnette): This is for crbug.com/599158; we need a
81        # better solution.
82        try:
83            info = host.get_power_supply_info()
84        except:
85            logging.exception('get_power_supply_info() failed')
86            return
87        try:
88            if info['Line Power']['online'] != 'yes':
89                raise hosts.AutoservVerifyError(
90                        'AC power is not plugged in')
91        except KeyError:
92            logging.info('Cannot determine AC power status - '
93                         'skipping check.')
94        try:
95            if float(info['Battery']['percentage']) < 50.0:
96                raise hosts.AutoservVerifyError(
97                        'Battery is less than 50%')
98        except KeyError:
99            logging.info('Cannot determine battery status - '
100                         'skipping check.')
101
102    @property
103    def description(self):
104        return 'The DUT is plugged in to AC power'
105
106
107class WritableVerifier(hosts.Verifier):
108    """
109    Confirm the stateful file systems are writable.
110
111    The standard linux response to certain unexpected file system errors
112    (including hardware errors in block devices) is to change the file
113    system status to read-only.  This checks that that hasn't happened.
114
115    The test covers the two file systems that need to be writable for
116    critical operations like AU:
117      * The (unencrypted) stateful system which includes
118        /mnt/stateful_partition.
119      * The encrypted stateful partition, which includes /var.
120
121    The test doesn't check various bind mounts; those are expected to
122    fail the same way as their underlying main mounts.  Whether the
123    Linux kernel can guarantee that is untested...
124    """
125
126    # N.B. Order matters here:  Encrypted stateful is loop-mounted from
127    # a file in unencrypted stateful, so we don't test for errors in
128    # encrypted stateful if unencrypted fails.
129    _TEST_DIRECTORIES = ['/mnt/stateful_partition', '/var/tmp']
130
131    def verify(self, host):
132        # This deliberately stops looking after the first error.
133        # See above for the details.
134        for testdir in self._TEST_DIRECTORIES:
135            filename = os.path.join(testdir, 'writable_test')
136            command = 'touch %s && rm %s' % (filename, filename)
137            rv = host.run(command=command, ignore_status=True)
138            if rv.exit_status != 0:
139                msg = 'Can\'t create a file in %s' % testdir
140                raise hosts.AutoservVerifyError(msg)
141
142    @property
143    def description(self):
144        return 'The stateful filesystems are writable'
145
146
147class EXT4fsErrorVerifier(hosts.Verifier):
148    """
149    Confirm we have not seen critical file system kernel errors.
150    """
151    def verify(self, host):
152        # grep for stateful FS errors of the type "EXT4-fs error (device sda1):"
153        command = ("dmesg | grep -E \"EXT4-fs error \(device "
154                   "$(cut -d ' ' -f 5,9 /proc/$$/mountinfo | "
155                   "grep -e '^/mnt/stateful_partition ' | "
156                   "cut -d ' ' -f 2 | cut -d '/' -f 3)\):\"")
157        output = host.run(command=command, ignore_status=True).stdout
158        if output:
159            sample = output.splitlines()[0]
160            message = 'Saw file system error: %s' % sample
161            raise hosts.AutoservVerifyError(message)
162        # Check for other critical FS errors.
163        command = 'dmesg | grep "This should not happen!!  Data will be lost"'
164        output = host.run(command=command, ignore_status=True).stdout
165        if output:
166            message = 'Saw file system error: Data will be lost'
167            raise hosts.AutoservVerifyError(message)
168        else:
169            logging.error('Could not determine stateful mount.')
170
171    @property
172    def description(self):
173        return 'Did not find critical file system errors'
174
175
176class UpdateSuccessVerifier(hosts.Verifier):
177    """
178    Checks that the DUT successfully finished its last provision job.
179
180    At the start of any update (e.g. for a Provision job), the code
181    creates a marker file named `host.PROVISION_FAILED`.  The file is
182    located in a part of the stateful partition that will be removed if
183    an update finishes successfully.  Thus, the presence of the file
184    indicates that a prior update failed.
185
186    The verifier tests for the existence of the marker file and fails if
187    it still exists.
188    """
189    def verify(self, host):
190        result = host.run('test -f %s' % host.PROVISION_FAILED,
191                          ignore_status=True)
192        if result.exit_status == 0:
193            raise hosts.AutoservVerifyError(
194                    'Last AU on this DUT failed')
195
196    @property
197    def description(self):
198        return 'The most recent AU attempt on this DUT succeeded'
199
200
201class TPMStatusVerifier(hosts.Verifier):
202    """Verify that the host's TPM is in a good state."""
203
204    def verify(self, host):
205        if _is_virual_machine(host):
206            # We do not forward host TPM / emulated TPM to qemu VMs, so skip
207            # this verification step.
208            logging.debug('Skipped verification %s on VM', self)
209            return
210
211        # This cryptohome command emits status information in JSON format. It
212        # looks something like this:
213        # {
214        #    "installattrs": {
215        #       ...
216        #    },
217        #    "mounts": [ {
218        #       ...
219        #    } ],
220        #    "tpm": {
221        #       "being_owned": false,
222        #       "can_connect": true,
223        #       "can_decrypt": false,
224        #       "can_encrypt": false,
225        #       "can_load_srk": true,
226        #       "can_load_srk_pubkey": true,
227        #       "enabled": true,
228        #       "has_context": true,
229        #       "has_cryptohome_key": false,
230        #       "has_key_handle": false,
231        #       "last_error": 0,
232        #       "owned": true
233        #    }
234        # }
235        output = host.run('cryptohome --action=status').stdout.strip()
236        try:
237            status = json.loads(output)
238        except ValueError:
239            logging.info('Cannot determine the Crytohome valid status - '
240                         'skipping check.')
241            return
242        try:
243            tpm = status['tpm']
244            if not tpm['enabled']:
245                raise hosts.AutoservVerifyError(
246                        'TPM is not enabled -- Hardware is not working.')
247            if not tpm['can_connect']:
248                raise hosts.AutoservVerifyError(
249                        ('TPM connect failed -- '
250                         'last_error=%d.' % tpm['last_error']))
251            if tpm['owned'] and not tpm['can_load_srk']:
252                raise hosts.AutoservVerifyError(
253                        'Cannot load the TPM SRK')
254            if tpm['can_load_srk'] and not tpm['can_load_srk_pubkey']:
255                raise hosts.AutoservVerifyError(
256                        'Cannot load the TPM SRK public key')
257        except KeyError:
258            logging.info('Cannot determine the Crytohome valid status - '
259                         'skipping check.')
260
261    @property
262    def description(self):
263        return 'The host\'s TPM is available and working'
264
265
266class PythonVerifier(hosts.Verifier):
267    """Confirm the presence of a working Python interpreter."""
268
269    def verify(self, host):
270        result = host.run('python -c "import cPickle"',
271                          ignore_status=True)
272        if result.exit_status != 0:
273            message = 'The python interpreter is broken'
274            if result.exit_status == 127:
275                search = host.run('which python', ignore_status=True)
276                if search.exit_status != 0 or not search.stdout:
277                    message = ('Python is missing; may be caused by '
278                               'powerwash')
279            raise hosts.AutoservVerifyError(message)
280
281    @property
282    def description(self):
283        return 'Python on the host is installed and working'
284
285
286class DevModeVerifier(hosts.Verifier):
287    """Verify that the host is not in dev mode."""
288
289    def verify(self, host):
290        # Some pools are allowed to be in dev mode
291        info = host.host_info_store.get()
292        if (_DEV_MODE_ALWAYS_ALLOWED or
293                bool(info.pools & _DEV_MODE_ALLOWED_POOLS)):
294            return
295
296        result = host.run('crossystem devsw_boot', ignore_status=True).stdout
297        if result != '0':
298            raise hosts.AutoservVerifyError('The host is in dev mode')
299
300    @property
301    def description(self):
302        return 'The host should not be in dev mode'
303
304
305class HWIDVerifier(hosts.Verifier):
306    """Verify that the host has HWID & serial number."""
307
308    def verify(self, host):
309        try:
310            info = host.host_info_store.get()
311
312            hwid = host.run('crossystem hwid', ignore_status=True).stdout
313            if hwid:
314                info.attributes['HWID'] = hwid
315
316            serial_number = host.run('vpd -g serial_number',
317                                     ignore_status=True).stdout
318            if serial_number:
319                info.attributes['serial_number'] = serial_number
320
321            if info != host.host_info_store.get():
322                host.host_info_store.commit(info)
323        except Exception as e:
324            logging.exception('Failed to get HWID & Serial Number for host ',
325                              '%s: %s', host.hostname, str(e))
326
327    @property
328    def description(self):
329        return 'The host should have valid HWID and Serial Number'
330
331
332class JetstreamServicesVerifier(hosts.Verifier):
333    """Verify that Jetstream services are running."""
334
335    # Retry for b/62576902
336    @retry.retry(error.AutoservError, timeout_min=1, delay_sec=10)
337    def verify(self, host):
338        try:
339            if not host.upstart_status('ap-controller'):
340                raise hosts.AutoservVerifyError(
341                    'ap-controller service is not running')
342        except error.AutoservRunError:
343            raise hosts.AutoservVerifyError(
344                'ap-controller service not found')
345
346        try:
347            host.run('pgrep ap-controller')
348        except error.AutoservRunError:
349            raise hosts.AutoservVerifyError(
350                'ap-controller process is not running')
351
352    @property
353    def description(self):
354        return 'Jetstream services must be running'
355
356
357class _ResetRepairAction(hosts.RepairAction):
358    """Common handling for repair actions that reset a DUT."""
359
360    def _collect_logs(self, host):
361        """Collect logs from a successfully repaired DUT."""
362        dirname = 'after_%s' % self.tag
363        local_log_dir = crashcollect.get_crashinfo_dir(host, dirname)
364        host.collect_logs('/var/log', local_log_dir, ignore_errors=True)
365        # Collect crash info.
366        crashcollect.get_crashinfo(host, None)
367
368    def _check_reset_success(self, host):
369        """Check whether reset succeeded, and gather logs if possible."""
370        if host.wait_up(host.BOOT_TIMEOUT):
371            try:
372                # Collect logs once we regain ssh access before
373                # clobbering them.
374                self._collect_logs(host)
375            except Exception:
376                # If the DUT is up, we want to declare success, even if
377                # log gathering fails for some reason.  So, if there's
378                # a failure, just log it and move on.
379                logging.exception('Unexpected failure in log '
380                                  'collection during %s.',
381                                  self.tag)
382            return
383        raise hosts.AutoservRepairError(
384                'Host %s is still offline after %s.' %
385                (host.hostname, self.tag))
386
387
388class ServoSysRqRepair(_ResetRepairAction):
389    """
390    Repair a Chrome device by sending a system request to the kernel.
391
392    Sending 3 times the Alt+VolUp+x key combination (aka sysrq-x)
393    will ask the kernel to panic itself and reboot while conserving
394    the kernel logs in console ramoops.
395    """
396
397    def repair(self, host):
398        if not host.servo:
399            raise hosts.AutoservRepairError(
400                    '%s has no servo support.' % host.hostname)
401        # Press 3 times Alt+VolUp+X
402        # no checking DUT health between each press as
403        # killing Chrome is not really likely to fix the DUT SSH.
404        for _ in range(3):
405            try:
406                host.servo.sysrq_x()
407            except error.TestFail, ex:
408                raise hosts.AutoservRepairError(
409                      'cannot press sysrq-x: %s.' % str(ex))
410            # less than 5 seconds between presses.
411            time.sleep(2.0)
412        self._check_reset_success(host)
413
414    @property
415    def description(self):
416        return 'Reset the DUT via keyboard sysrq-x'
417
418
419class ServoResetRepair(_ResetRepairAction):
420    """Repair a Chrome device by resetting it with servo."""
421
422    def repair(self, host):
423        if not host.servo:
424            raise hosts.AutoservRepairError(
425                    '%s has no servo support.' % host.hostname)
426        host.servo.get_power_state_controller().reset()
427        self._check_reset_success(host)
428
429    @property
430    def description(self):
431        return 'Reset the DUT via servo'
432
433
434class CrosRebootRepair(repair.RebootRepair):
435    """Repair a CrOS target by clearing dev mode and rebooting it."""
436
437    def repair(self, host):
438        # N.B. We need to reboot regardless of whether clearing
439        # dev_mode succeeds or fails.
440        host.run('/usr/share/vboot/bin/set_gbb_flags.sh 0',
441                 ignore_status=True)
442        host.run('crossystem disable_dev_request=1',
443                 ignore_status=True)
444        super(CrosRebootRepair, self).repair(host)
445
446    @property
447    def description(self):
448        return 'Reset GBB flags and Reboot the host'
449
450
451class AutoUpdateRepair(hosts.RepairAction):
452    """
453    Repair by re-installing a test image using autoupdate.
454
455    Try to install the DUT's designated "stable test image" using the
456    standard procedure for installing a new test image via autoupdate.
457    """
458
459    def repair(self, host):
460        afe_utils.machine_install_and_update_labels(host, repair=True)
461
462    @property
463    def description(self):
464        return 'Re-install the stable build via AU'
465
466
467class PowerWashRepair(AutoUpdateRepair):
468    """
469    Powerwash the DUT, then re-install using autoupdate.
470
471    Powerwash the DUT, then attempt to re-install a stable test image as
472    for `AutoUpdateRepair`.
473    """
474
475    def repair(self, host):
476        host.run('echo "fast safe" > '
477                 '/mnt/stateful_partition/factory_install_reset')
478        host.reboot(timeout=host.POWERWASH_BOOT_TIMEOUT, wait=True)
479        super(PowerWashRepair, self).repair(host)
480
481    @property
482    def description(self):
483        return 'Powerwash and then re-install the stable build via AU'
484
485
486class ServoInstallRepair(hosts.RepairAction):
487    """
488    Reinstall a test image from USB using servo.
489
490    Use servo to re-install the DUT's designated "stable test image"
491    from servo-attached USB storage.
492    """
493
494    def repair(self, host):
495        if not host.servo:
496            raise hosts.AutoservRepairError(
497                    '%s has no servo support.' % host.hostname)
498        host.servo_install(host.stage_image_for_servo())
499
500    @property
501    def description(self):
502        return 'Reinstall from USB using servo'
503
504
505class JetstreamRepair(hosts.RepairAction):
506    """Repair by restarting Jetstrem services."""
507
508    def repair(self, host):
509        host.cleanup_services()
510
511    @property
512    def description(self):
513        return 'Restart Jetstream services'
514
515
516def _cros_verify_dag():
517    """Return the verification DAG for a `CrosHost`."""
518    FirmwareStatusVerifier = cros_firmware.FirmwareStatusVerifier
519    FirmwareVersionVerifier = cros_firmware.FirmwareVersionVerifier
520    verify_dag = (
521        (repair.SshVerifier,         'ssh',      ()),
522        (DevModeVerifier,            'devmode',  ('ssh',)),
523        (HWIDVerifier,               'hwid',     ('ssh',)),
524        (ACPowerVerifier,            'power',    ('ssh',)),
525        (EXT4fsErrorVerifier,        'ext4',     ('ssh',)),
526        (WritableVerifier,           'writable', ('ssh',)),
527        (TPMStatusVerifier,          'tpm',      ('ssh',)),
528        (UpdateSuccessVerifier,      'good_au',  ('ssh',)),
529        (FirmwareStatusVerifier,     'fwstatus', ('ssh',)),
530        (FirmwareVersionVerifier,    'rwfw',     ('ssh',)),
531        (PythonVerifier,             'python',   ('ssh',)),
532        (repair.LegacyHostVerifier,  'cros',     ('ssh',)),
533    )
534    return verify_dag
535
536
537def _cros_basic_repair_actions():
538    """Return the basic repair actions for a `CrosHost`"""
539    FirmwareRepair = cros_firmware.FirmwareRepair
540    repair_actions = (
541        # RPM cycling must precede Servo reset:  if the DUT has a dead
542        # battery, we need to reattach AC power before we reset via servo.
543        (repair.RPMCycleRepair, 'rpm', (), ('ssh', 'power',)),
544        (ServoSysRqRepair, 'sysrq', (), ('ssh',)),
545        (ServoResetRepair, 'servoreset', (), ('ssh',)),
546
547        # N.B. FirmwareRepair can't fix a 'good_au' failure directly,
548        # because it doesn't remove the flag file that triggers the
549        # failure.  We include it as a repair trigger because it's
550        # possible the the last update failed because of the firmware,
551        # and we want the repair steps below to be able to trust the
552        # firmware.
553        (FirmwareRepair, 'firmware', (), ('ssh', 'fwstatus', 'good_au',)),
554
555        (CrosRebootRepair, 'reboot', ('ssh',), ('devmode', 'writable',)),
556    )
557    return repair_actions
558
559
560def _cros_extended_repair_actions(au_triggers=_CROS_AU_TRIGGERS,
561                                  powerwash_triggers=_CROS_POWERWASH_TRIGGERS,
562                                  usb_triggers=_CROS_USB_TRIGGERS):
563    """Return the extended repair actions for a `CrosHost`"""
564
565    # The dependencies and triggers for the 'au', 'powerwash', and 'usb'
566    # repair actions stack up:  Each one is able to repair progressively
567    # more verifiers than the one before.  The 'triggers' lists specify
568    # the progression.
569
570    repair_actions = (
571        (AutoUpdateRepair, 'au',
572                usb_triggers + powerwash_triggers, au_triggers),
573        (PowerWashRepair, 'powerwash',
574                usb_triggers, powerwash_triggers + au_triggers),
575        (ServoInstallRepair, 'usb',
576                (), usb_triggers + powerwash_triggers + au_triggers),
577    )
578    return repair_actions
579
580
581def _cros_repair_actions():
582    """Return the repair actions for a `CrosHost`."""
583    repair_actions = (_cros_basic_repair_actions() +
584                      _cros_extended_repair_actions())
585    return repair_actions
586
587
588def create_cros_repair_strategy():
589    """Return a `RepairStrategy` for a `CrosHost`."""
590    verify_dag = _cros_verify_dag()
591    repair_actions = _cros_repair_actions()
592    return hosts.RepairStrategy(verify_dag, repair_actions)
593
594
595def _moblab_verify_dag():
596    """Return the verification DAG for a `MoblabHost`."""
597    FirmwareVersionVerifier = cros_firmware.FirmwareVersionVerifier
598    verify_dag = (
599        (repair.SshVerifier,         'ssh',     ()),
600        (ACPowerVerifier,            'power',   ('ssh',)),
601        (FirmwareVersionVerifier,    'rwfw',    ('ssh',)),
602        (PythonVerifier,             'python',  ('ssh',)),
603        (repair.LegacyHostVerifier,  'cros',    ('ssh',)),
604    )
605    return verify_dag
606
607
608def _moblab_repair_actions():
609    """Return the repair actions for a `MoblabHost`."""
610    repair_actions = (
611        (repair.RPMCycleRepair, 'rpm', (), ('ssh', 'power',)),
612        (AutoUpdateRepair, 'au', ('ssh',), _CROS_AU_TRIGGERS),
613    )
614    return repair_actions
615
616
617def create_moblab_repair_strategy():
618    """
619    Return a `RepairStrategy` for a `MoblabHost`.
620
621    Moblab is a subset of the CrOS verify and repair.  Several pieces
622    are removed because they're not expected to be meaningful.  Some
623    others are removed for more specific reasons:
624
625    'tpm':  Moblab DUTs don't run the tests that matter to this
626        verifier.  TODO(jrbarnette)  This assertion is unproven.
627
628    'good_au':  This verifier can't pass, because the Moblab AU
629        procedure doesn't properly delete CrosHost.PROVISION_FAILED.
630        TODO(jrbarnette) We should refactor _machine_install() so that
631        it can be different for Moblab.
632
633    'firmware':  Moblab DUTs shouldn't be in FAFT pools, so we don't try
634        this.
635
636    'powerwash':  Powerwash on Moblab causes trouble with deleting the
637        DHCP leases file, so we skip it.
638    """
639    verify_dag = _moblab_verify_dag()
640    repair_actions = _moblab_repair_actions()
641    return hosts.RepairStrategy(verify_dag, repair_actions)
642
643
644def _jetstream_repair_actions():
645    """Return the repair actions for a `JetstreamHost`."""
646    au_triggers = _CROS_AU_TRIGGERS + ('jetstream_services',)
647    repair_actions = (
648        _cros_basic_repair_actions() +
649        (
650            (JetstreamRepair, 'jetstream_repair',
651             _CROS_USB_TRIGGERS + _CROS_POWERWASH_TRIGGERS, au_triggers),
652        ) +
653        _cros_extended_repair_actions(au_triggers=au_triggers))
654    return repair_actions
655
656
657def _jetstream_verify_dag():
658    """Return the verification DAG for a `JetstreamHost`."""
659    verify_dag = _cros_verify_dag() + (
660        (JetstreamServicesVerifier, 'jetstream_services', ('ssh',)),
661    )
662    return verify_dag
663
664
665def create_jetstream_repair_strategy():
666    """
667    Return a `RepairStrategy` for a `JetstreamHost`.
668
669    The Jetstream repair strategy is based on the CrOS verify and repair,
670    but adds the JetstreamServicesVerifier.
671    """
672    verify_dag = _jetstream_verify_dag()
673    repair_actions = _jetstream_repair_actions()
674    return hosts.RepairStrategy(verify_dag, repair_actions)
675
676
677# TODO(pprabhu) Move this to a better place. I have no idea what that place
678# would be.
679def _is_virual_machine(host):
680    """Determine whether the given |host| is a virtual machine.
681
682    @param host: a hosts.Host object.
683    @returns True if the host is a virtual machine, False otherwise.
684    """
685    output = host.run('cat /proc/cpuinfo | grep "model name"')
686    return output.stdout and 'qemu' in output.stdout.lower()
687