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 logging 6import time 7 8import common 9from autotest_lib.client.common_lib import hosts 10from autotest_lib.server.cros.dynamic_suite import frontend_wrappers 11from autotest_lib.server.hosts import repair_utils 12 13 14class _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 35class _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 102class _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 139class _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 173class _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 193class _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 214class _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 239class _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 255class _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 296class _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 327class _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 349def 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