355 lines
12 KiB
Python
355 lines
12 KiB
Python
# Copyright 2016 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import logging
|
|
import time
|
|
|
|
import common
|
|
from autotest_lib.client.common_lib import hosts
|
|
from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
|
|
from autotest_lib.server.hosts import repair
|
|
|
|
|
|
class _UpdateVerifier(hosts.Verifier):
|
|
"""
|
|
Verifier to trigger a servo host update, if necessary.
|
|
|
|
The operation doesn't wait for the update to complete and is
|
|
considered a success whether or not the servo is currently
|
|
up-to-date.
|
|
"""
|
|
|
|
def verify(self, host):
|
|
# First, only run this verifier if the host is in the physical lab.
|
|
# Secondly, skip if the test is being run by test_that, because subnet
|
|
# restrictions can cause the update to fail.
|
|
if host.is_in_lab() and host.job and host.job.in_lab:
|
|
host.update_image(wait_for_update=False)
|
|
|
|
@property
|
|
def description(self):
|
|
return 'servo host software is up-to-date'
|
|
|
|
|
|
class _ConfigVerifier(hosts.Verifier):
|
|
"""
|
|
Base verifier for the servo config file verifiers.
|
|
"""
|
|
|
|
CONFIG_FILE = '/var/lib/servod/config'
|
|
ATTR = ''
|
|
|
|
@staticmethod
|
|
def _get_config_val(host, config_file, attr):
|
|
"""
|
|
Get the `attr` for `host` from `config_file`.
|
|
|
|
@param host Host to be checked for `config_file`.
|
|
@param config_file Path to the config file to be tested.
|
|
@param attr Attribute to get from config file.
|
|
|
|
@return The attr val as set in the config file, or `None` if
|
|
the file was absent.
|
|
"""
|
|
getboard = ('CONFIG=%s ; [ -f $CONFIG ] && '
|
|
'. $CONFIG && echo $%s' % (config_file, attr))
|
|
attr_val = host.run(getboard, ignore_status=True).stdout
|
|
return attr_val.strip('\n') if attr_val else None
|
|
|
|
@staticmethod
|
|
def _validate_attr(host, val, expected_val, attr, config_file):
|
|
"""
|
|
Check that the attr setting is valid for the host.
|
|
|
|
This presupposes that a valid config file was found. Raise an
|
|
execption if:
|
|
* There was no attr setting from the file (i.e. the setting
|
|
is an empty string), or
|
|
* The attr setting is valid, the attr is known,
|
|
and the setting doesn't match the DUT.
|
|
|
|
@param host Host to be checked for `config_file`.
|
|
@param val Value to be tested.
|
|
@param expected_val Expected value.
|
|
@param attr Attribute we're validating.
|
|
@param config_file Path to the config file to be tested.
|
|
"""
|
|
if not val:
|
|
raise hosts.AutoservVerifyError(
|
|
'config file %s exists, but %s '
|
|
'is not set' % (attr, config_file))
|
|
if expected_val is not None and val != expected_val:
|
|
raise hosts.AutoservVerifyError(
|
|
'%s is %s; it should be %s' % (attr, val, expected_val))
|
|
|
|
|
|
def _get_configs(self, host):
|
|
"""
|
|
Return all the config files to check.
|
|
|
|
@param host Host object.
|
|
|
|
@return The list of config files to check.
|
|
"""
|
|
# TODO(jrbarnette): Testing `CONFIG_FILE` without a port number
|
|
# is a legacy. Ideally, we would force all servos in the lab to
|
|
# update, and then remove this case.
|
|
config_list = ['%s_%d' % (self.CONFIG_FILE, host.servo_port)]
|
|
if host.servo_port == host.DEFAULT_PORT:
|
|
config_list.append(self.CONFIG_FILE)
|
|
return config_list
|
|
|
|
@property
|
|
def description(self):
|
|
return 'servo %s setting is correct' % self.ATTR
|
|
|
|
|
|
class _SerialConfigVerifier(_ConfigVerifier):
|
|
"""
|
|
Verifier for the servo SERIAL configuration.
|
|
"""
|
|
|
|
ATTR = 'SERIAL'
|
|
|
|
def verify(self, host):
|
|
"""
|
|
Test whether the `host` has a `SERIAL` setting configured.
|
|
|
|
This tests the config file names used by the `servod` upstart
|
|
job for a valid setting of the `SERIAL` variable. The following
|
|
conditions raise errors:
|
|
* The SERIAL setting doesn't match the DUT's entry in the AFE
|
|
database.
|
|
* There is no config file.
|
|
"""
|
|
if not host.is_cros_host():
|
|
return
|
|
# Not all servo hosts will have a servo serial so don't verify if it's
|
|
# not set.
|
|
if host.servo_serial is None:
|
|
return
|
|
for config in self._get_configs(host):
|
|
serialval = self._get_config_val(host, config, self.ATTR)
|
|
if serialval is not None:
|
|
self._validate_attr(host, serialval, host.servo_serial,
|
|
self.ATTR, config)
|
|
return
|
|
msg = 'Servo serial is unconfigured; should be %s' % host.servo_serial
|
|
raise hosts.AutoservVerifyError(msg)
|
|
|
|
|
|
|
|
class _BoardConfigVerifier(_ConfigVerifier):
|
|
"""
|
|
Verifier for the servo BOARD configuration.
|
|
"""
|
|
|
|
ATTR = 'BOARD'
|
|
|
|
def verify(self, host):
|
|
"""
|
|
Test whether the `host` has a `BOARD` setting configured.
|
|
|
|
This tests the config file names used by the `servod` upstart
|
|
job for a valid setting of the `BOARD` variable. The following
|
|
conditions raise errors:
|
|
* A config file exists, but the content contains no setting
|
|
for BOARD.
|
|
* The BOARD setting doesn't match the DUT's entry in the AFE
|
|
database.
|
|
* There is no config file.
|
|
"""
|
|
if not host.is_cros_host():
|
|
return
|
|
for config in self._get_configs(host):
|
|
boardval = self._get_config_val(host, config, self.ATTR)
|
|
if boardval is not None:
|
|
self._validate_attr(host, boardval, host.servo_board, self.ATTR,
|
|
config)
|
|
return
|
|
msg = 'Servo board is unconfigured'
|
|
if host.servo_board is not None:
|
|
msg += '; should be %s' % host.servo_board
|
|
raise hosts.AutoservVerifyError(msg)
|
|
|
|
|
|
class _ServodJobVerifier(hosts.Verifier):
|
|
"""
|
|
Verifier to check that the `servod` upstart job is running.
|
|
"""
|
|
|
|
def verify(self, host):
|
|
if not host.is_cros_host():
|
|
return
|
|
status_cmd = 'status servod PORT=%d' % host.servo_port
|
|
job_status = host.run(status_cmd, ignore_status=True).stdout
|
|
if 'start/running' not in job_status:
|
|
raise hosts.AutoservVerifyError(
|
|
'servod not running on %s port %d' %
|
|
(host.hostname, host.servo_port))
|
|
|
|
@property
|
|
def description(self):
|
|
return 'servod upstart job is running'
|
|
|
|
|
|
class _ServodConnectionVerifier(hosts.Verifier):
|
|
"""
|
|
Verifier to check that we can connect to `servod`.
|
|
|
|
This tests the connection to the target servod service with a simple
|
|
method call. As a side-effect, all servo signals are initialized to
|
|
default values.
|
|
|
|
N.B. Initializing servo signals is necessary because the power
|
|
button and lid switch verifiers both test against expected initial
|
|
values.
|
|
"""
|
|
|
|
def verify(self, host):
|
|
host.connect_servo()
|
|
|
|
@property
|
|
def description(self):
|
|
return 'servod service is taking calls'
|
|
|
|
|
|
class _PowerButtonVerifier(hosts.Verifier):
|
|
"""
|
|
Verifier to check sanity of the `pwr_button` signal.
|
|
|
|
Tests that the `pwr_button` signal shows the power button has been
|
|
released. When `pwr_button` is stuck at `press`, it commonly
|
|
indicates that the ribbon cable is disconnected.
|
|
"""
|
|
# TODO (crbug.com/646593) - Remove list below once servo has been updated
|
|
# with a dummy pwr_button signal.
|
|
_BOARDS_WO_PWR_BUTTON = ['arkham', 'storm', 'whirlwind', 'gale']
|
|
|
|
def verify(self, host):
|
|
if host.servo_board in self._BOARDS_WO_PWR_BUTTON:
|
|
return
|
|
button = host.get_servo().get('pwr_button')
|
|
if button != 'release':
|
|
raise hosts.AutoservVerifyError(
|
|
'Check ribbon cable: \'pwr_button\' is stuck')
|
|
|
|
@property
|
|
def description(self):
|
|
return 'pwr_button control is normal'
|
|
|
|
|
|
class _LidVerifier(hosts.Verifier):
|
|
"""
|
|
Verifier to check sanity of the `lid_open` signal.
|
|
"""
|
|
|
|
def verify(self, host):
|
|
lid_open = host.get_servo().get('lid_open')
|
|
if lid_open != 'yes' and lid_open != 'not_applicable':
|
|
raise hosts.AutoservVerifyError(
|
|
'Check lid switch: lid_open is %s' % lid_open)
|
|
|
|
@property
|
|
def description(self):
|
|
return 'lid_open control is normal'
|
|
|
|
|
|
class _RestartServod(hosts.RepairAction):
|
|
"""Restart `servod` with the proper BOARD setting."""
|
|
|
|
def repair(self, host):
|
|
if not host.is_cros_host():
|
|
raise hosts.AutoservRepairError(
|
|
'Can\'t restart servod: not running '
|
|
'embedded Chrome OS.')
|
|
host.run('stop servod PORT=%d || true' % host.servo_port)
|
|
serial = 'SERIAL=%s' % host.servo_serial if host.servo_serial else ''
|
|
if host.servo_board:
|
|
host.run('start servod BOARD=%s PORT=%d %s' %
|
|
(host.servo_board, host.servo_port, serial))
|
|
else:
|
|
# TODO(jrbarnette): It remains to be seen whether
|
|
# this action is the right thing to do...
|
|
logging.warning('Board for DUT is unknown; starting '
|
|
'servod assuming a pre-configured '
|
|
'board.')
|
|
host.run('start servod PORT=%d %s' % (host.servo_port, serial))
|
|
# There's a lag between when `start servod` completes and when
|
|
# the _ServodConnectionVerifier trigger can actually succeed.
|
|
# The call to time.sleep() below gives time to make sure that
|
|
# the trigger won't fail after we return.
|
|
#
|
|
# The delay selection was based on empirical testing against
|
|
# servo V3 on a desktop:
|
|
# + 10 seconds was usually too slow; 11 seconds was
|
|
# usually fast enough.
|
|
# + So, the 20 second delay is about double what we
|
|
# expect to need.
|
|
time.sleep(20)
|
|
|
|
|
|
@property
|
|
def description(self):
|
|
return 'Start servod with the proper config settings.'
|
|
|
|
|
|
class _ServoRebootRepair(repair.RebootRepair):
|
|
"""
|
|
Reboot repair action that also waits for an update.
|
|
|
|
This is the same as the standard `RebootRepair`, but for
|
|
a servo host, if there's a pending update, we wait for that
|
|
to complete before rebooting. This should ensure that the
|
|
servo is up-to-date after reboot.
|
|
"""
|
|
|
|
def repair(self, host):
|
|
if host.is_localhost() or not host.is_cros_host():
|
|
raise hosts.AutoservRepairError(
|
|
'Target servo is not a test lab servo')
|
|
host.update_image(wait_for_update=True)
|
|
afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
|
|
dut_list = host.get_attached_duts(afe)
|
|
if len(dut_list) > 1:
|
|
host.schedule_synchronized_reboot(dut_list, afe, force_reboot=True)
|
|
else:
|
|
super(_ServoRebootRepair, self).repair(host)
|
|
|
|
@property
|
|
def description(self):
|
|
return 'Wait for update, then reboot servo host.'
|
|
|
|
|
|
def create_servo_repair_strategy():
|
|
"""
|
|
Return a `RepairStrategy` for a `ServoHost`.
|
|
"""
|
|
config = ['brd_config', 'ser_config']
|
|
verify_dag = [
|
|
(repair.SshVerifier, 'servo_ssh', []),
|
|
(_UpdateVerifier, 'update', ['servo_ssh']),
|
|
(_BoardConfigVerifier, 'brd_config', ['servo_ssh']),
|
|
(_SerialConfigVerifier, 'ser_config', ['servo_ssh']),
|
|
(_ServodJobVerifier, 'job', config),
|
|
(_ServodConnectionVerifier, 'servod', ['job']),
|
|
(_PowerButtonVerifier, 'pwr_button', ['servod']),
|
|
(_LidVerifier, 'lid_open', ['servod']),
|
|
# TODO(jrbarnette): We want a verifier for whether there's
|
|
# a working USB stick plugged into the servo. However,
|
|
# although we always want to log USB stick problems, we don't
|
|
# want to fail the servo because we don't want a missing USB
|
|
# stick to prevent, say, power cycling the DUT.
|
|
#
|
|
# So, it may be that the right fix is to put diagnosis into
|
|
# ServoInstallRepair rather than add a verifier.
|
|
]
|
|
|
|
servod_deps = ['job', 'servod', 'pwr_button', 'lid_open']
|
|
repair_actions = [
|
|
(repair.RPMCycleRepair, 'rpm', [], ['servo_ssh']),
|
|
(_RestartServod, 'restart', ['servo_ssh'], config + servod_deps),
|
|
(_ServoRebootRepair, 'reboot', ['servo_ssh'], servod_deps),
|
|
]
|
|
return hosts.RepairStrategy(verify_dag, repair_actions)
|