blob: ef71648b146f10b60c026cb4187641766688cf7e [file] [log] [blame]
Richard Barnette9a26ad62016-06-10 12:03:08 -07001# 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
Mengqi Guo51d5bea2020-02-03 18:34:45 -08005import functools
Richard Barnette9a26ad62016-06-10 12:03:08 -07006import logging
Richard Barnette9a26ad62016-06-10 12:03:08 -07007
8import common
9from autotest_lib.client.common_lib import hosts
Mengqi Guo51d5bea2020-02-03 18:34:45 -080010from autotest_lib.server.cros.servo import servo
Richard Barnetted72eeeb2018-09-05 10:08:33 -070011from autotest_lib.server.hosts import repair_utils
Richard Barnette9a26ad62016-06-10 12:03:08 -070012
13
Mengqi Guo51d5bea2020-02-03 18:34:45 -080014def ignore_exception_for_non_cros_host(func):
15 """
16 Decorator to ignore ControlUnavailableError if servo host is not cros host.
17 When using test_that command on a workstation, this enables usage of
18 additional servo devices such as servo micro and Sweetberry. This shall not
19 change any lab behavior.
20 """
21 @functools.wraps(func)
22 def wrapper(self, host):
23 """
24 Wrapper around func.
25 """
26 try:
27 func(self, host)
28 except servo.ControlUnavailableError as e:
29 if host.is_cros_host():
30 raise
31 logging.warning("Servo host is not cros host, ignore %s: %s",
32 type(e).__name__, e)
33 return wrapper
34
35
Richard Barnette9a26ad62016-06-10 12:03:08 -070036class _UpdateVerifier(hosts.Verifier):
37 """
38 Verifier to trigger a servo host update, if necessary.
39
40 The operation doesn't wait for the update to complete and is
41 considered a success whether or not the servo is currently
42 up-to-date.
43 """
44
45 def verify(self, host):
Simran Basi10ad79b2017-01-31 12:05:24 -080046 # First, only run this verifier if the host is in the physical lab.
47 # Secondly, skip if the test is being run by test_that, because subnet
48 # restrictions can cause the update to fail.
Garry Wang3388ffd2019-12-03 12:58:41 +000049 try:
50 if host.is_in_lab() and host.job and host.job.in_lab:
51 host.update_image(wait_for_update=False)
52 # We don't want failure from update block DUT repair action.
53 # See crbug.com/1029950.
54 except Exception as e:
55 logging.error('Failed to update servohost image: %s', e)
Richard Barnette9a26ad62016-06-10 12:03:08 -070056
57 @property
58 def description(self):
59 return 'servo host software is up-to-date'
60
61
Kevin Cheng643ce8a2016-09-15 15:42:12 -070062class _ConfigVerifier(hosts.Verifier):
63 """
64 Base verifier for the servo config file verifiers.
65 """
66
67 CONFIG_FILE = '/var/lib/servod/config'
68 ATTR = ''
69
70 @staticmethod
71 def _get_config_val(host, config_file, attr):
72 """
73 Get the `attr` for `host` from `config_file`.
74
75 @param host Host to be checked for `config_file`.
76 @param config_file Path to the config file to be tested.
77 @param attr Attribute to get from config file.
78
79 @return The attr val as set in the config file, or `None` if
80 the file was absent.
81 """
82 getboard = ('CONFIG=%s ; [ -f $CONFIG ] && '
83 '. $CONFIG && echo $%s' % (config_file, attr))
84 attr_val = host.run(getboard, ignore_status=True).stdout
85 return attr_val.strip('\n') if attr_val else None
86
87 @staticmethod
88 def _validate_attr(host, val, expected_val, attr, config_file):
89 """
90 Check that the attr setting is valid for the host.
91
92 This presupposes that a valid config file was found. Raise an
93 execption if:
94 * There was no attr setting from the file (i.e. the setting
95 is an empty string), or
96 * The attr setting is valid, the attr is known,
97 and the setting doesn't match the DUT.
98
99 @param host Host to be checked for `config_file`.
100 @param val Value to be tested.
101 @param expected_val Expected value.
102 @param attr Attribute we're validating.
103 @param config_file Path to the config file to be tested.
104 """
105 if not val:
106 raise hosts.AutoservVerifyError(
107 'config file %s exists, but %s '
108 'is not set' % (attr, config_file))
109 if expected_val is not None and val != expected_val:
110 raise hosts.AutoservVerifyError(
111 '%s is %s; it should be %s' % (attr, val, expected_val))
112
113
Prathmesh Prabhuadd00302018-12-27 10:38:54 -0800114 def _get_config(self, host):
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700115 """
Prathmesh Prabhuadd00302018-12-27 10:38:54 -0800116 Return the config file to check.
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700117
118 @param host Host object.
119
Prathmesh Prabhuadd00302018-12-27 10:38:54 -0800120 @return The config file to check.
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700121 """
Prathmesh Prabhuadd00302018-12-27 10:38:54 -0800122 return '%s_%d' % (self.CONFIG_FILE, host.servo_port)
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700123
124 @property
125 def description(self):
126 return 'servo %s setting is correct' % self.ATTR
127
128
129class _SerialConfigVerifier(_ConfigVerifier):
130 """
131 Verifier for the servo SERIAL configuration.
132 """
133
134 ATTR = 'SERIAL'
135
136 def verify(self, host):
137 """
138 Test whether the `host` has a `SERIAL` setting configured.
139
140 This tests the config file names used by the `servod` upstart
141 job for a valid setting of the `SERIAL` variable. The following
142 conditions raise errors:
143 * The SERIAL setting doesn't match the DUT's entry in the AFE
144 database.
145 * There is no config file.
146 """
147 if not host.is_cros_host():
148 return
149 # Not all servo hosts will have a servo serial so don't verify if it's
150 # not set.
151 if host.servo_serial is None:
152 return
Prathmesh Prabhuadd00302018-12-27 10:38:54 -0800153 config = self._get_config(host)
154 serialval = self._get_config_val(host, config, self.ATTR)
Prathmesh Prabhud593d612018-12-27 10:41:49 -0800155 if serialval is None:
156 raise hosts.AutoservVerifyError(
157 'Servo serial is unconfigured; should be %s'
158 % host.servo_serial
159 )
160
161 self._validate_attr(host, serialval, host.servo_serial, self.ATTR,
162 config)
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700163
164
165
166class _BoardConfigVerifier(_ConfigVerifier):
Richard Barnette9a26ad62016-06-10 12:03:08 -0700167 """
168 Verifier for the servo BOARD configuration.
169 """
170
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700171 ATTR = 'BOARD'
Richard Barnette9a26ad62016-06-10 12:03:08 -0700172
173 def verify(self, host):
174 """
175 Test whether the `host` has a `BOARD` setting configured.
176
177 This tests the config file names used by the `servod` upstart
178 job for a valid setting of the `BOARD` variable. The following
179 conditions raise errors:
180 * A config file exists, but the content contains no setting
181 for BOARD.
182 * The BOARD setting doesn't match the DUT's entry in the AFE
183 database.
184 * There is no config file.
185 """
186 if not host.is_cros_host():
187 return
Prathmesh Prabhuadd00302018-12-27 10:38:54 -0800188 config = self._get_config(host)
189 boardval = self._get_config_val(host, config, self.ATTR)
Prathmesh Prabhud593d612018-12-27 10:41:49 -0800190 if boardval is None:
191 msg = 'Servo board is unconfigured'
192 if host.servo_board is not None:
193 msg += '; should be %s' % host.servo_board
194 raise hosts.AutoservVerifyError(msg)
195
196 self._validate_attr(host, boardval, host.servo_board, self.ATTR,
197 config)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700198
Richard Barnette9a26ad62016-06-10 12:03:08 -0700199
200class _ServodJobVerifier(hosts.Verifier):
201 """
202 Verifier to check that the `servod` upstart job is running.
203 """
204
205 def verify(self, host):
206 if not host.is_cros_host():
207 return
208 status_cmd = 'status servod PORT=%d' % host.servo_port
209 job_status = host.run(status_cmd, ignore_status=True).stdout
210 if 'start/running' not in job_status:
211 raise hosts.AutoservVerifyError(
212 'servod not running on %s port %d' %
213 (host.hostname, host.servo_port))
214
215 @property
216 def description(self):
217 return 'servod upstart job is running'
218
219
Garry Wangc042b7e2020-01-13 15:16:42 -0800220class _DiskSpaceVerifier(hosts.Verifier):
Dana Goyette97a76182019-11-25 19:07:31 -0800221 """
Garry Wangc042b7e2020-01-13 15:16:42 -0800222 Verifier to make sure there is enough disk space left on servohost.
Dana Goyette97a76182019-11-25 19:07:31 -0800223 """
Dana Goyette97a76182019-11-25 19:07:31 -0800224
225 def verify(self, host):
Garry Wangc042b7e2020-01-13 15:16:42 -0800226 host.check_diskspace('/mnt/stateful_partition', 0.1)
Dana Goyette97a76182019-11-25 19:07:31 -0800227
228 @property
229 def description(self):
Garry Wangc042b7e2020-01-13 15:16:42 -0800230 return 'servohost has enough disk space.'
Dana Goyette97a76182019-11-25 19:07:31 -0800231
232
Richard Barnette9a26ad62016-06-10 12:03:08 -0700233class _ServodConnectionVerifier(hosts.Verifier):
234 """
235 Verifier to check that we can connect to `servod`.
236
237 This tests the connection to the target servod service with a simple
238 method call. As a side-effect, all servo signals are initialized to
239 default values.
240
241 N.B. Initializing servo signals is necessary because the power
242 button and lid switch verifiers both test against expected initial
243 values.
244 """
245
246 def verify(self, host):
247 host.connect_servo()
248
249 @property
250 def description(self):
251 return 'servod service is taking calls'
252
253
254class _PowerButtonVerifier(hosts.Verifier):
255 """
256 Verifier to check sanity of the `pwr_button` signal.
257
258 Tests that the `pwr_button` signal shows the power button has been
259 released. When `pwr_button` is stuck at `press`, it commonly
260 indicates that the ribbon cable is disconnected.
261 """
Simran Basi04a5c242016-09-21 17:35:49 -0700262 # TODO (crbug.com/646593) - Remove list below once servo has been updated
263 # with a dummy pwr_button signal.
Laurence Goodbya12b5d72019-02-01 17:42:08 -0800264 _BOARDS_WO_PWR_BUTTON = ['arkham', 'gale', 'mistral', 'storm', 'whirlwind']
Richard Barnette9a26ad62016-06-10 12:03:08 -0700265
Mengqi Guo51d5bea2020-02-03 18:34:45 -0800266 @ignore_exception_for_non_cros_host
Richard Barnette9a26ad62016-06-10 12:03:08 -0700267 def verify(self, host):
Simran Basi04a5c242016-09-21 17:35:49 -0700268 if host.servo_board in self._BOARDS_WO_PWR_BUTTON:
269 return
Richard Barnette9a26ad62016-06-10 12:03:08 -0700270 button = host.get_servo().get('pwr_button')
271 if button != 'release':
272 raise hosts.AutoservVerifyError(
273 'Check ribbon cable: \'pwr_button\' is stuck')
274
Mengqi Guo51d5bea2020-02-03 18:34:45 -0800275
Richard Barnette9a26ad62016-06-10 12:03:08 -0700276 @property
277 def description(self):
278 return 'pwr_button control is normal'
279
280
281class _LidVerifier(hosts.Verifier):
282 """
283 Verifier to check sanity of the `lid_open` signal.
284 """
285
Mengqi Guo51d5bea2020-02-03 18:34:45 -0800286 @ignore_exception_for_non_cros_host
Richard Barnette9a26ad62016-06-10 12:03:08 -0700287 def verify(self, host):
288 lid_open = host.get_servo().get('lid_open')
289 if lid_open != 'yes' and lid_open != 'not_applicable':
290 raise hosts.AutoservVerifyError(
291 'Check lid switch: lid_open is %s' % lid_open)
292
293 @property
294 def description(self):
295 return 'lid_open control is normal'
296
297
298class _RestartServod(hosts.RepairAction):
299 """Restart `servod` with the proper BOARD setting."""
300
301 def repair(self, host):
302 if not host.is_cros_host():
303 raise hosts.AutoservRepairError(
304 'Can\'t restart servod: not running '
Garry Wang954f8382019-01-23 13:49:29 -0800305 'embedded Chrome OS.',
306 'servo_not_applicable_to_non_cros_host')
Garry Wangc1288cf2019-12-17 14:58:00 -0800307 host.restart_servod()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700308
309 @property
310 def description(self):
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700311 return 'Start servod with the proper config settings.'
Richard Barnette9a26ad62016-06-10 12:03:08 -0700312
313
Richard Barnetted72eeeb2018-09-05 10:08:33 -0700314class _ServoRebootRepair(repair_utils.RebootRepair):
Richard Barnette9a26ad62016-06-10 12:03:08 -0700315 """
316 Reboot repair action that also waits for an update.
317
318 This is the same as the standard `RebootRepair`, but for
Garry Wangebc015b2019-06-06 17:45:06 -0700319 a non-multi-DUTs servo host, if there's a pending update,
320 we wait for that to complete before rebooting. This should
321 ensure that the servo_v3 is up-to-date after reboot. Labstation
322 reboot and update is handled by labstation host class.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700323 """
324
325 def repair(self, host):
326 if host.is_localhost() or not host.is_cros_host():
327 raise hosts.AutoservRepairError(
Garry Wang954f8382019-01-23 13:49:29 -0800328 'Target servo is not a test lab servo',
329 'servo_not_applicable_to_host_outside_lab')
Garry Wangebc015b2019-06-06 17:45:06 -0700330 if host.is_labstation():
Garry Wang79e9af62019-06-12 15:19:19 -0700331 host.request_reboot()
332 logging.warning('Reboot labstation requested, it will be '
333 'handled by labstation administrative task.')
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700334 else:
Garry Wang3388ffd2019-12-03 12:58:41 +0000335 try:
336 host.update_image(wait_for_update=True)
337 # We don't want failure from update block DUT repair action.
338 # See crbug.com/1029950.
339 except Exception as e:
340 logging.error('Failed to update servohost image: %s', e)
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700341 super(_ServoRebootRepair, self).repair(host)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700342
343 @property
344 def description(self):
345 return 'Wait for update, then reboot servo host.'
346
347
Wai-Hong Tambf6f23c2017-08-16 11:10:42 -0700348class _DutRebootRepair(hosts.RepairAction):
349 """
350 Reboot DUT to recover some servo controls depending on EC console.
351
352 Some servo controls, like lid_open, requires communicating with DUT through
353 EC UART console. Failure of this kinds of controls can be recovered by
354 rebooting the DUT.
355 """
356
357 def repair(self, host):
358 host.get_servo().get_power_state_controller().reset()
359 # Get the lid_open value which requires EC console.
360 lid_open = host.get_servo().get('lid_open')
361 if lid_open != 'yes' and lid_open != 'not_applicable':
362 raise hosts.AutoservVerifyError(
363 'Still fail to contact EC console after rebooting DUT')
364
365 @property
366 def description(self):
367 return 'Reset the DUT via servo'
368
369
Garry Wangc042b7e2020-01-13 15:16:42 -0800370class _DiskCleanupRepair(hosts.RepairAction):
371 """
372 Remove old logs/metrics/crash_dumps on servohost to free up disk space.
373 """
374 KEEP_LOGS_MAX_DAYS = 5
375
376 FILE_TO_REMOVE = ['/var/lib/metrics/uma-events',
377 '/var/spool/crash/*']
378
379 def repair(self, host):
380 if host.is_localhost():
381 # we don't want to remove anything from local testing.
382 return
383
384 # Remove old servod logs.
385 host.run('/usr/bin/find /var/log/servod_* -mtime +%d -print -delete'
386 % self.KEEP_LOGS_MAX_DAYS, ignore_status=True)
387
388 # Remove pre-defined metrics and crash dumps.
389 for path in self.FILE_TO_REMOVE:
390 host.run('rm %s' % path, ignore_status=True)
391
392 @property
393 def description(self):
394 return 'Clean up old logs/metrics on servohost to free up disk space.'
395
396
Richard Barnette9a26ad62016-06-10 12:03:08 -0700397def create_servo_repair_strategy():
398 """
399 Return a `RepairStrategy` for a `ServoHost`.
400 """
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700401 config = ['brd_config', 'ser_config']
Richard Barnette9a26ad62016-06-10 12:03:08 -0700402 verify_dag = [
Richard Barnetted72eeeb2018-09-05 10:08:33 -0700403 (repair_utils.SshVerifier, 'servo_ssh', []),
Garry Wangc042b7e2020-01-13 15:16:42 -0800404 (_DiskSpaceVerifier, 'disk_space', ['servo_ssh']),
Richard Barnette0f1ffc42017-03-23 13:51:24 -0700405 (_UpdateVerifier, 'update', ['servo_ssh']),
406 (_BoardConfigVerifier, 'brd_config', ['servo_ssh']),
407 (_SerialConfigVerifier, 'ser_config', ['servo_ssh']),
Garry Wangc042b7e2020-01-13 15:16:42 -0800408 (_ServodJobVerifier, 'job', config + ['disk_space']),
Richard Barnette9a26ad62016-06-10 12:03:08 -0700409 (_ServodConnectionVerifier, 'servod', ['job']),
410 (_PowerButtonVerifier, 'pwr_button', ['servod']),
411 (_LidVerifier, 'lid_open', ['servod']),
412 # TODO(jrbarnette): We want a verifier for whether there's
413 # a working USB stick plugged into the servo. However,
414 # although we always want to log USB stick problems, we don't
415 # want to fail the servo because we don't want a missing USB
416 # stick to prevent, say, power cycling the DUT.
417 #
418 # So, it may be that the right fix is to put diagnosis into
419 # ServoInstallRepair rather than add a verifier.
420 ]
421
Wai-Hong Tambf6f23c2017-08-16 11:10:42 -0700422 servod_deps = ['job', 'servod', 'pwr_button']
Richard Barnette9a26ad62016-06-10 12:03:08 -0700423 repair_actions = [
Garry Wangc042b7e2020-01-13 15:16:42 -0800424 (_DiskCleanupRepair, 'disk_cleanup', ['servo_ssh'], ['disk_space']),
Richard Barnette0f1ffc42017-03-23 13:51:24 -0700425 (_RestartServod, 'restart', ['servo_ssh'], config + servod_deps),
Wai-Hong Tambf6f23c2017-08-16 11:10:42 -0700426 (_ServoRebootRepair, 'servo_reboot', ['servo_ssh'], servod_deps),
427 (_DutRebootRepair, 'dut_reboot', ['servod'], ['lid_open']),
Richard Barnette9a26ad62016-06-10 12:03:08 -0700428 ]
Garry Wang67538912018-12-06 14:45:04 -0800429 return hosts.RepairStrategy(verify_dag, repair_actions, 'servo')