Home | History | Annotate | Download | only in hosts
      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 
      5 import logging
      6 import time
      7 
      8 import common
      9 from autotest_lib.client.common_lib import hosts
     10 from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
     11 from autotest_lib.server.hosts import repair_utils
     12 
     13 
     14 class _UpdateVerifier(hosts.Verifier):
     15     """
     16     Verifier to trigger a servo host update, if necessary.
     17 
     18     The operation doesn't wait for the update to complete and is
     19     considered a success whether or not the servo is currently
     20     up-to-date.
     21     """
     22 
     23     def verify(self, host):
     24         # First, only run this verifier if the host is in the physical lab.
     25         # Secondly, skip if the test is being run by test_that, because subnet
     26         # restrictions can cause the update to fail.
     27         if host.is_in_lab() and host.job and host.job.in_lab:
     28             host.update_image(wait_for_update=False)
     29 
     30     @property
     31     def description(self):
     32         return 'servo host software is up-to-date'
     33 
     34 
     35 class _ConfigVerifier(hosts.Verifier):
     36     """
     37     Base verifier for the servo config file verifiers.
     38     """
     39 
     40     CONFIG_FILE = '/var/lib/servod/config'
     41     ATTR = ''
     42 
     43     @staticmethod
     44     def _get_config_val(host, config_file, attr):
     45         """
     46         Get the `attr` for `host` from `config_file`.
     47 
     48         @param host         Host to be checked for `config_file`.
     49         @param config_file  Path to the config file to be tested.
     50         @param attr         Attribute to get from config file.
     51 
     52         @return The attr val as set in the config file, or `None` if
     53                 the file was absent.
     54         """
     55         getboard = ('CONFIG=%s ; [ -f $CONFIG ] && '
     56                     '. $CONFIG && echo $%s' % (config_file, attr))
     57         attr_val = host.run(getboard, ignore_status=True).stdout
     58         return attr_val.strip('\n') if attr_val else None
     59 
     60     @staticmethod
     61     def _validate_attr(host, val, expected_val, attr, config_file):
     62         """
     63         Check that the attr setting is valid for the host.
     64 
     65         This presupposes that a valid config file was found.  Raise an
     66         execption if:
     67           * There was no attr setting from the file (i.e. the setting
     68             is an empty string), or
     69           * The attr setting is valid, the attr is known,
     70             and the setting doesn't match the DUT.
     71 
     72         @param host         Host to be checked for `config_file`.
     73         @param val          Value to be tested.
     74         @param expected_val Expected value.
     75         @param attr         Attribute we're validating.
     76         @param config_file  Path to the config file to be tested.
     77         """
     78         if not val:
     79             raise hosts.AutoservVerifyError(
     80                     'config file %s exists, but %s '
     81                     'is not set' % (attr, config_file))
     82         if expected_val is not None and val != expected_val:
     83             raise hosts.AutoservVerifyError(
     84                     '%s is %s; it should be %s' % (attr, val, expected_val))
     85 
     86 
     87     def _get_config(self, host):
     88         """
     89         Return the config file to check.
     90 
     91         @param host     Host object.
     92 
     93         @return The config file to check.
     94         """
     95         return '%s_%d' % (self.CONFIG_FILE, host.servo_port)
     96 
     97     @property
     98     def description(self):
     99         return 'servo %s setting is correct' % self.ATTR
    100 
    101 
    102 class _SerialConfigVerifier(_ConfigVerifier):
    103     """
    104     Verifier for the servo SERIAL configuration.
    105     """
    106 
    107     ATTR = 'SERIAL'
    108 
    109     def verify(self, host):
    110         """
    111         Test whether the `host` has a `SERIAL` setting configured.
    112 
    113         This tests the config file names used by the `servod` upstart
    114         job for a valid setting of the `SERIAL` variable.  The following
    115         conditions raise errors:
    116           * The SERIAL setting doesn't match the DUT's entry in the AFE
    117             database.
    118           * There is no config file.
    119         """
    120         if not host.is_cros_host():
    121             return
    122         # Not all servo hosts will have a servo serial so don't verify if it's
    123         # not set.
    124         if host.servo_serial is None:
    125             return
    126         config = self._get_config(host)
    127         serialval = self._get_config_val(host, config, self.ATTR)
    128         if serialval is None:
    129             raise hosts.AutoservVerifyError(
    130                     'Servo serial is unconfigured; should be %s'
    131                     % host.servo_serial
    132             )
    133 
    134         self._validate_attr(host, serialval, host.servo_serial, self.ATTR,
    135                             config)
    136 
    137 
    138 
    139 class _BoardConfigVerifier(_ConfigVerifier):
    140     """
    141     Verifier for the servo BOARD configuration.
    142     """
    143 
    144     ATTR = 'BOARD'
    145 
    146     def verify(self, host):
    147         """
    148         Test whether the `host` has a `BOARD` setting configured.
    149 
    150         This tests the config file names used by the `servod` upstart
    151         job for a valid setting of the `BOARD` variable.  The following
    152         conditions raise errors:
    153           * A config file exists, but the content contains no setting
    154             for BOARD.
    155           * The BOARD setting doesn't match the DUT's entry in the AFE
    156             database.
    157           * There is no config file.
    158         """
    159         if not host.is_cros_host():
    160             return
    161         config = self._get_config(host)
    162         boardval = self._get_config_val(host, config, self.ATTR)
    163         if boardval is None:
    164             msg = 'Servo board is unconfigured'
    165             if host.servo_board is not None:
    166                 msg += '; should be %s' % host.servo_board
    167             raise hosts.AutoservVerifyError(msg)
    168 
    169         self._validate_attr(host, boardval, host.servo_board, self.ATTR,
    170                             config)
    171 
    172 
    173 class _ServodJobVerifier(hosts.Verifier):
    174     """
    175     Verifier to check that the `servod` upstart job is running.
    176     """
    177 
    178     def verify(self, host):
    179         if not host.is_cros_host():
    180             return
    181         status_cmd = 'status servod PORT=%d' % host.servo_port
    182         job_status = host.run(status_cmd, ignore_status=True).stdout
    183         if 'start/running' not in job_status:
    184             raise hosts.AutoservVerifyError(
    185                     'servod not running on %s port %d' %
    186                     (host.hostname, host.servo_port))
    187 
    188     @property
    189     def description(self):
    190         return 'servod upstart job is running'
    191 
    192 
    193 class _ServodConnectionVerifier(hosts.Verifier):
    194     """
    195     Verifier to check that we can connect to `servod`.
    196 
    197     This tests the connection to the target servod service with a simple
    198     method call.  As a side-effect, all servo signals are initialized to
    199     default values.
    200 
    201     N.B. Initializing servo signals is necessary because the power
    202     button and lid switch verifiers both test against expected initial
    203     values.
    204     """
    205 
    206     def verify(self, host):
    207         host.connect_servo()
    208 
    209     @property
    210     def description(self):
    211         return 'servod service is taking calls'
    212 
    213 
    214 class _PowerButtonVerifier(hosts.Verifier):
    215     """
    216     Verifier to check sanity of the `pwr_button` signal.
    217 
    218     Tests that the `pwr_button` signal shows the power button has been
    219     released.  When `pwr_button` is stuck at `press`, it commonly
    220     indicates that the ribbon cable is disconnected.
    221     """
    222     # TODO (crbug.com/646593) - Remove list below once servo has been updated
    223     # with a dummy pwr_button signal.
    224     _BOARDS_WO_PWR_BUTTON = ['arkham', 'gale', 'mistral', 'storm', 'whirlwind']
    225 
    226     def verify(self, host):
    227         if host.servo_board in self._BOARDS_WO_PWR_BUTTON:
    228             return
    229         button = host.get_servo().get('pwr_button')
    230         if button != 'release':
    231             raise hosts.AutoservVerifyError(
    232                     'Check ribbon cable: \'pwr_button\' is stuck')
    233 
    234     @property
    235     def description(self):
    236         return 'pwr_button control is normal'
    237 
    238 
    239 class _LidVerifier(hosts.Verifier):
    240     """
    241     Verifier to check sanity of the `lid_open` signal.
    242     """
    243 
    244     def verify(self, host):
    245         lid_open = host.get_servo().get('lid_open')
    246         if lid_open != 'yes' and lid_open != 'not_applicable':
    247             raise hosts.AutoservVerifyError(
    248                     'Check lid switch: lid_open is %s' % lid_open)
    249 
    250     @property
    251     def description(self):
    252         return 'lid_open control is normal'
    253 
    254 
    255 class _RestartServod(hosts.RepairAction):
    256     """Restart `servod` with the proper BOARD setting."""
    257 
    258     def repair(self, host):
    259         if not host.is_cros_host():
    260             raise hosts.AutoservRepairError(
    261                     'Can\'t restart servod: not running '
    262                     'embedded Chrome OS.',
    263                     'servo_not_applicable_to_non_cros_host')
    264         host.run('stop servod PORT=%d || true' % host.servo_port)
    265         serial = 'SERIAL=%s' % host.servo_serial if host.servo_serial else ''
    266         model = 'MODEL=%s' % host.servo_model if host.servo_model else ''
    267         if host.servo_board:
    268             host.run('start servod BOARD=%s %s PORT=%d %s' %
    269                      (host.servo_board, model, host.servo_port, serial))
    270         else:
    271             # TODO(jrbarnette):  It remains to be seen whether
    272             # this action is the right thing to do...
    273             logging.warning('Board for DUT is unknown; starting '
    274                             'servod assuming a pre-configured '
    275                             'board.')
    276             host.run('start servod PORT=%d %s' % (host.servo_port, serial))
    277         # There's a lag between when `start servod` completes and when
    278         # the _ServodConnectionVerifier trigger can actually succeed.
    279         # The call to time.sleep() below gives time to make sure that
    280         # the trigger won't fail after we return.
    281         #
    282         # The delay selection was based on empirical testing against
    283         # servo V3 on a desktop:
    284         #   + 10 seconds was usually too slow; 11 seconds was
    285         #     usually fast enough.
    286         #   + So, the 20 second delay is about double what we
    287         #     expect to need.
    288         time.sleep(20)
    289 
    290 
    291     @property
    292     def description(self):
    293         return 'Start servod with the proper config settings.'
    294 
    295 
    296 class _ServoRebootRepair(repair_utils.RebootRepair):
    297     """
    298     Reboot repair action that also waits for an update.
    299 
    300     This is the same as the standard `RebootRepair`, but for
    301     a servo host, if there's a pending update, we wait for that
    302     to complete before rebooting.  This should ensure that the
    303     servo is up-to-date after reboot.
    304     """
    305 
    306     def repair(self, host):
    307         if host.is_localhost() or not host.is_cros_host():
    308             raise hosts.AutoservRepairError(
    309                 'Target servo is not a test lab servo',
    310                 'servo_not_applicable_to_host_outside_lab')
    311         host.update_image(wait_for_update=True)
    312         afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
    313         dut_list = host.get_attached_duts(afe)
    314         if len(dut_list) > 1:
    315             raise hosts.AutoservRepairError(
    316                     'Repairing labstation with > 1 host not supported.'
    317                     ' See crbug.com/843358',
    318                     'can_not_repair_labstation_with_multiple_hosts')
    319         else:
    320             super(_ServoRebootRepair, self).repair(host)
    321 
    322     @property
    323     def description(self):
    324         return 'Wait for update, then reboot servo host.'
    325 
    326 
    327 class _DutRebootRepair(hosts.RepairAction):
    328     """
    329     Reboot DUT to recover some servo controls depending on EC console.
    330 
    331     Some servo controls, like lid_open, requires communicating with DUT through
    332     EC UART console. Failure of this kinds of controls can be recovered by
    333     rebooting the DUT.
    334     """
    335 
    336     def repair(self, host):
    337         host.get_servo().get_power_state_controller().reset()
    338         # Get the lid_open value which requires EC console.
    339         lid_open = host.get_servo().get('lid_open')
    340         if lid_open != 'yes' and lid_open != 'not_applicable':
    341             raise hosts.AutoservVerifyError(
    342                     'Still fail to contact EC console after rebooting DUT')
    343 
    344     @property
    345     def description(self):
    346         return 'Reset the DUT via servo'
    347 
    348 
    349 def create_servo_repair_strategy():
    350     """
    351     Return a `RepairStrategy` for a `ServoHost`.
    352     """
    353     config = ['brd_config', 'ser_config']
    354     verify_dag = [
    355         (repair_utils.SshVerifier,   'servo_ssh',   []),
    356         (_UpdateVerifier,            'update',      ['servo_ssh']),
    357         (_BoardConfigVerifier,       'brd_config',  ['servo_ssh']),
    358         (_SerialConfigVerifier,      'ser_config',  ['servo_ssh']),
    359         (_ServodJobVerifier,         'job',         config),
    360         (_ServodConnectionVerifier,  'servod',      ['job']),
    361         (_PowerButtonVerifier,       'pwr_button',  ['servod']),
    362         (_LidVerifier,               'lid_open',    ['servod']),
    363         # TODO(jrbarnette):  We want a verifier for whether there's
    364         # a working USB stick plugged into the servo.  However,
    365         # although we always want to log USB stick problems, we don't
    366         # want to fail the servo because we don't want a missing USB
    367         # stick to prevent, say, power cycling the DUT.
    368         #
    369         # So, it may be that the right fix is to put diagnosis into
    370         # ServoInstallRepair rather than add a verifier.
    371     ]
    372 
    373     servod_deps = ['job', 'servod', 'pwr_button']
    374     repair_actions = [
    375         (repair_utils.RPMCycleRepair, 'rpm', [], ['servo_ssh']),
    376         (_RestartServod, 'restart', ['servo_ssh'], config + servod_deps),
    377         (_ServoRebootRepair, 'servo_reboot', ['servo_ssh'], servod_deps),
    378         (_DutRebootRepair, 'dut_reboot', ['servod'], ['lid_open']),
    379     ]
    380     return hosts.RepairStrategy(verify_dag, repair_actions, 'servo')
    381