1 # Copyright (c) 2014 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 7 from autotest_lib.client.common_lib import error 8 from autotest_lib.client.common_lib.cros import servo_afe_board_map 9 from autotest_lib.server import test 10 from autotest_lib.server.cros.servo import servo 11 12 13 def _successful(result_value): 14 return result_value and not isinstance(result_value, Exception) 15 16 17 class _DiagnosticTest(object): 18 """Data needed to handle one diagnostic test on a Servo host. 19 20 The class encapsulates two basic elements: 21 1. A pre-requisite test that must have passed. The 22 pre-requisite is recorded as a key in the results 23 dictionary. 24 2. A function that performs the actual diagnostic test. 25 26 All tests have the implicit pre-requisite that the servo host 27 can be reached on the network via ping. 28 29 Pre-requisites are meant to capture relationships of the form 30 "if test X cant't pass, test Y will always fail". Typically, 31 that means that test X tests a capability used by test Y. 32 33 This implementation is a bit naive: It assumes only a single 34 pre-requisite, and it assumes the only outcome is a simple 35 pass/fail. The design also doesn't account for relationships 36 of the form "if test X fails, run test Y to try and distinguish 37 possible causes". 38 39 """ 40 41 def __init__(self, prerequisite, get_result): 42 self._prerequisite = prerequisite 43 self._get_result = get_result 44 45 def can_run(self, results): 46 """Return whether this test's pre-requisite is satisfied. 47 48 @param results The results dictionary with the status of 49 this test's pre-requisite. 50 51 """ 52 if self._prerequisite is None: 53 return True 54 return _successful(results[self._prerequisite]) 55 56 def run_diagnostic(self, servo_host, servod): 57 """Run the diagnostic test, and return the result. 58 59 The test receives ServoHost and Servo objects to be tested; 60 typically a single test uses one or the other, but not both. 61 62 @param servo_host A ServoHost object to be the target of the 63 test. 64 @param servod A Servo object to be the target of the 65 test. 66 @return If the test returns normally, return its result. If 67 the test raises an exception, return the exception. 68 69 """ 70 try: 71 return self._get_result(servo_host, servod) 72 except Exception as e: 73 return e 74 75 76 def _ssh_test(servo_host, servod): 77 """Test whether the servo host answers to ssh. 78 79 This test serves as a basic pre-requisite for tests that 80 use ssh to test other conditions. 81 82 Pre-requisite: There are no pre-requisites for this test aside 83 from the implicit pre-requisite that the host answer to ping. 84 85 @param servo_host The ServoHost object to talk to via ssh. 86 @param servod Ignored. 87 88 """ 89 return servo_host.is_up() 90 91 92 def _servod_connect(servo_host, servod): 93 """Test whether connection to servod succeeds. 94 95 This tests the connection to the target servod with a simple 96 method call. As a side-effect, all hardware signals are 97 initialized to default values. 98 99 This function always returns success. The test can only fail if 100 the underlying call to servo raises an exception. 101 102 Pre-requisite: There are no pre-requisites for this test aside 103 from the implicit pre-requisite that the host answer to ping. 104 105 @return `True` 106 107 """ 108 # TODO(jrbarnette) We need to protect this call so that it 109 # will time out if servod doesn't respond. 110 servod.initialize_dut() 111 return True 112 113 114 def _pwr_button_test(servo_host, servod): 115 """Test whether the 'pwr_button' signal is correct. 116 117 This tests whether the state of the 'pwr_button' signal is 118 'release'. When the servo flex cable is not attached, the 119 signal will be stuck at 'press'. 120 121 Pre-requisite: This test depends on successful initialization 122 of servod. 123 124 Rationale: The initialization step sets 'pwr_button' to 125 'release', which is required to justify the expectations of this 126 test. Also, if initialization fails, we can reasonably expect 127 that all communication with servod will fail. 128 129 @param servo_host Ignored. 130 @param servod The Servo object to be tested. 131 132 """ 133 return servod.get('pwr_button') == 'release' 134 135 136 def _lid_test(servo_host, servod): 137 """Test whether the 'lid_open' signal is correct. 138 139 This tests whether the state of the 'lid_open' signal has a 140 correct value. There is a manual switch on the servo board; if 141 that switch is set wrong, the signal will be stuck at 'no'. 142 Working units may return a setting of 'yes' (meaning the lid is 143 open) or 'not_applicable' (meaning the device has no lid). 144 145 Pre-requisite: This test depends on the 'pwr_button' test. 146 147 Rationale: If the 'pwr_button' test fails, the flex cable may 148 be disconnected, which means any servo operation to read a 149 hardware signal will fail. 150 151 @param servo_host Ignored. 152 @param servod The Servo object to be tested. 153 154 """ 155 return servod.get('lid_open') != 'no' 156 157 158 def _command_test(servo_host, command): 159 """Utility to return the output of a command on a servo host. 160 161 The command is expected to produce at most one line of 162 output. A trailing newline, if any, is stripped. 163 164 @return Output from the command with the trailing newline 165 removed. 166 167 """ 168 return servo_host.run(command).stdout.strip('\n') 169 170 171 def _brillo_test(servo_host, servod): 172 """Get the version of Brillo running on the servo host. 173 174 Reads the setting of CHROMEOS_RELEASE_VERSION from 175 /etc/lsb-release on the servo host. An empty string will 176 returned if there is no such setting. 177 178 Pre-requisite: This test depends on the ssh test. 179 180 @param servo_host The ServoHost object to be queried. 181 @param servod Ignored. 182 183 @return Returns a Brillo version number or an empty string. 184 185 """ 186 command = ('sed "s/CHROMEOS_RELEASE_VERSION=//p ; d" ' 187 '/etc/lsb-release') 188 return _command_test(servo_host, command) 189 190 191 def _board_test(servo_host, servod): 192 """Get the board for which the servo is configured. 193 194 Reads the setting of BOARD from /var/lib/servod/config. An 195 empty string is returned if the board is unconfigured. 196 197 Pre-requisite: This test depends on the brillo version test. 198 199 Rationale: The /var/lib/servod/config file is used by the servod 200 upstart job, which is specific to Brillo servo builds. This 201 test has no meaning if the target servo host isn't running 202 Brillo. 203 204 @param servo_host The ServoHost object to be queried. 205 @param servod Ignored. 206 207 @return The confgured board or an empty string. 208 209 """ 210 command = ('CONFIG=/var/lib/servod/config\n' 211 '[ -f $CONFIG ] && . $CONFIG && echo $BOARD') 212 return _command_test(servo_host, command) 213 214 215 def _servod_test(servo_host, servod): 216 """Get the status of the servod upstart job. 217 218 Ask upstart for the status of the 'servod' job. Return whether 219 the job is reported running. 220 221 Pre-requisite: This test depends on the brillo version test. 222 223 Rationale: The servod upstart job is specific to Brillo servo 224 builds. This test has no meaning if the target servo host isn't 225 running Brillo. 226 227 @param servo_host The ServoHost object to be queried. 228 @param servod Ignored. 229 230 @return `True` if the job is running, or `False` otherwise. 231 232 """ 233 command = 'status servod | sed "s/,.*//"' 234 return _command_test(servo_host, command) == 'servod start/running' 235 236 237 _DIAGNOSTICS_LIST = [ 238 ('ssh_responds', 239 _DiagnosticTest(None, _ssh_test)), 240 ('servod_connect', 241 _DiagnosticTest(None, _servod_connect)), 242 ('pwr_button', 243 _DiagnosticTest('servod_connect', _pwr_button_test)), 244 ('lid_open', 245 _DiagnosticTest('pwr_button', _lid_test)), 246 ('brillo_version', 247 _DiagnosticTest('ssh_responds', _brillo_test)), 248 ('board', 249 _DiagnosticTest('brillo_version', _board_test)), 250 ('servod', 251 _DiagnosticTest('brillo_version', _servod_test)), 252 ] 253 254 255 class infra_ServoDiagnosis(test.test): 256 """Test a servo and diagnose common failures.""" 257 258 version = 1 259 260 def _run_results(self, servo_host, servod): 261 results = {} 262 for key, tester in _DIAGNOSTICS_LIST: 263 if tester.can_run(results): 264 results[key] = tester.run_diagnostic(servo_host, servod) 265 logging.info('Test %s result %s', key, results[key]) 266 else: 267 results[key] = None 268 logging.info('Skipping %s', key) 269 return results 270 271 def run_once(self, host): 272 """Test and diagnose the servo for the given host. 273 274 @param host Host object for a DUT with Servo. 275 276 """ 277 # TODO(jrbarnette): Need to handle ping diagnoses: 278 # + Specifically report if servo host isn't a lab host. 279 # + Specifically report if servo host is in lab but 280 # doesn't respond to ping. 281 servo_host = host._servo_host 282 servod = host.servo 283 if servod is None: 284 servod = servo.Servo(servo_host) 285 results = self._run_results(servo_host, servod) 286 287 if not _successful(results['ssh_responds']): 288 raise error.TestFail('ssh connection to %s failed' % 289 servo_host.hostname) 290 291 if not _successful(results['brillo_version']): 292 raise error.TestFail('Servo host %s is not running Brillo' % 293 servo_host.hostname) 294 295 # Make sure servo board matches DUT label 296 board = host._get_board_from_afe() 297 board = servo_afe_board_map.map_afe_board_to_servo_board(board) 298 if (board and results['board'] is not None and 299 board != results['board']): 300 logging.info('AFE says board should be %s', board) 301 if results['servod']: 302 servo_host.run('stop servod', ignore_status=True) 303 servo_host.run('start servod BOARD=%s' % board) 304 results = self._run_results(servo_host, servod) 305 306 # TODO(jrbarnette): The brillo update check currently 307 # lives in ServoHost; it needs to move here. 308 309 # Repair actions: 310 # if servod is dead or running but not working 311 # reboot and re-run results 312 313 if (not _successful(results['servod']) or 314 not _successful(results['servod_connect'])): 315 # TODO(jrbarnette): For now, allow reboot failures to 316 # raise their exceptions up the stack. This event 317 # shouldn't happen, so smarter handling should wait 318 # until we have a use case to guide the requirements. 319 servo_host.reboot() 320 results = self._run_results(servo_host, servod) 321 if not _successful(results['servod']): 322 # write result value to log 323 raise error.TestFail('servod failed to start on %s' % 324 servo_host.hostname) 325 326 if not _successful(results['servod_connect']): 327 raise error.TestFail('Servo failure on %s' % 328 servo_host.hostname) 329 330 if not _successful(results['pwr_button']): 331 raise error.TestFail('Stuck power button on %s' % 332 servo_host.hostname) 333 334 if not _successful(results['lid_open']): 335 raise error.TestFail('Lid stuck closed on %s' % 336 servo_host.hostname) 337