blob: aaccb2cbb8f0cdedfc080a07597f5726cdd74b83 [file] [log] [blame]
Fang Deng5d518f42013-08-02 14:04:32 -07001# Copyright (c) 2013 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# Expects to be run in an environment with sudo and no interactive password
6# prompt, such as within the Chromium OS development chroot.
7
8
9"""This file provides core logic for servo verify/repair process."""
10
11
12import httplib
13import logging
14import socket
15import time
16import xmlrpclib
17
18from autotest_lib.client.bin import utils
19from autotest_lib.client.common_lib import error
beeps5e8c45a2013-12-17 22:05:11 -080020from autotest_lib.client.common_lib import global_config
21from autotest_lib.client.common_lib.cros import autoupdater
22from autotest_lib.client.common_lib.cros import dev_server
Fang Deng5d518f42013-08-02 14:04:32 -070023from autotest_lib.client.common_lib.cros import retry
Michael Liangda8c60a2014-06-03 13:24:51 -070024from autotest_lib.client.common_lib.cros.graphite import stats
beeps5e8c45a2013-12-17 22:05:11 -080025from autotest_lib.server import site_utils as server_site_utils
Fang Deng5d518f42013-08-02 14:04:32 -070026from autotest_lib.server.cros.servo import servo
27from autotest_lib.server.hosts import ssh_host
Fang Dengd4fe7392013-09-20 12:18:21 -070028from autotest_lib.site_utils.rpm_control_system import rpm_client
Fang Deng5d518f42013-08-02 14:04:32 -070029
30
31class ServoHostException(error.AutoservError):
32 """This is the base class for exceptions raised by ServoHost."""
33 pass
34
35
36class ServoHostVerifyFailure(ServoHostException):
37 """Raised when servo verification fails."""
38 pass
39
40
Fang Dengd4fe7392013-09-20 12:18:21 -070041class ServoHostRepairFailure(ServoHostException):
42 """Raised when a repair method fails to repair a servo host."""
43 pass
44
45
Fang Dengf0ea6142013-10-10 21:43:16 -070046class ServoHostRepairMethodNA(ServoHostException):
47 """Raised when a repair method is not applicable."""
48 pass
49
50
Fang Deng5d518f42013-08-02 14:04:32 -070051class ServoHostRepairTotalFailure(ServoHostException):
52 """Raised if all attempts to repair a servo host fail."""
53 pass
54
55
56def make_servo_hostname(dut_hostname):
57 """Given a DUT's hostname, return the hostname of its servo.
58
59 @param dut_hostname: hostname of a DUT.
60
61 @return hostname of the DUT's servo.
62
63 """
64 host_parts = dut_hostname.split('.')
65 host_parts[0] = host_parts[0] + '-servo'
66 return '.'.join(host_parts)
67
68
69class ServoHost(ssh_host.SSHHost):
70 """Host class for a host that controls a servo, e.g. beaglebone."""
71
72 # Timeout for getting the value of 'pwr_button'.
73 PWR_BUTTON_CMD_TIMEOUT_SECS = 15
74 # Timeout for rebooting servo host.
75 REBOOT_TIMEOUT_SECS = 90
76 HOST_DOWN_TIMEOUT_SECS = 60
77 # Delay after rebooting for servod to become fully functional.
78 REBOOT_DELAY_SECS = 20
79 # Servod process name.
80 SERVOD_PROCESS = 'servod'
81
Fang Dengd4fe7392013-09-20 12:18:21 -070082 _MAX_POWER_CYCLE_ATTEMPTS = 3
beeps5e8c45a2013-12-17 22:05:11 -080083 _timer = stats.Timer('servo_host')
Fang Dengd4fe7392013-09-20 12:18:21 -070084
Fang Deng5d518f42013-08-02 14:04:32 -070085
86 def _initialize(self, servo_host='localhost', servo_port=9999,
Dan Shi4d478522014-02-14 13:46:32 -080087 required_by_test=True, is_in_lab=None, *args, **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -070088 """Initialize a ServoHost instance.
89
90 A ServoHost instance represents a host that controls a servo.
91
92 @param servo_host: Name of the host where the servod process
93 is running.
94 @param servo_port: Port the servod process is listening on.
Dan Shi4d478522014-02-14 13:46:32 -080095 @param required_by_test: True if servo is required by test.
96 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
97 to None, for which utils.host_is_in_lab_zone will be
98 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -070099
100 """
101 super(ServoHost, self)._initialize(hostname=servo_host,
102 *args, **dargs)
Dan Shi4d478522014-02-14 13:46:32 -0800103 if is_in_lab is None:
104 self._is_in_lab = utils.host_is_in_lab_zone(self.hostname)
105 else:
106 self._is_in_lab = is_in_lab
Fang Deng5d518f42013-08-02 14:04:32 -0700107 self._is_localhost = (self.hostname == 'localhost')
108 remote = 'http://%s:%s' % (self.hostname, servo_port)
109 self._servod_server = xmlrpclib.ServerProxy(remote)
110 # Commands on the servo host must be run by the superuser. Our account
111 # on Beaglebone is root, but locally we might be running as a
112 # different user. If so - `sudo ' will have to be added to the
113 # commands.
114 if self._is_localhost:
115 self._sudo_required = utils.system_output('id -u') != '0'
116 else:
117 self._sudo_required = False
Dan Shi4d478522014-02-14 13:46:32 -0800118 # Create a cache of Servo object. This must be called at the end of
119 # _initialize to make sure all attributes are set.
120 self._servo = None
121 try:
122 self.verify()
Alex Millercc589692014-04-21 18:00:22 -0700123 except Exception:
Dan Shibbb0cb62014-03-24 17:50:57 -0700124 if required_by_test:
125 if not self.is_in_lab():
126 raise
127 else:
128 self.repair_full()
Fang Deng5d518f42013-08-02 14:04:32 -0700129
130
131 def is_in_lab(self):
132 """Check whether the servo host is a lab device.
133
134 @returns: True if the servo host is in Cros Lab, otherwise False.
135
136 """
137 return self._is_in_lab
138
139
140 def is_localhost(self):
141 """Checks whether the servo host points to localhost.
142
143 @returns: True if it points to localhost, otherwise False.
144
145 """
146 return self._is_localhost
147
148
149 def get_servod_server_proxy(self):
150 """Return a proxy that can be used to communicate with servod server.
151
152 @returns: An xmlrpclib.ServerProxy that is connected to the servod
153 server on the host.
154
155 """
156 return self._servod_server
157
158
159 def get_wait_up_processes(self):
160 """Get the list of local processes to wait for in wait_up.
161
162 Override get_wait_up_processes in
163 autotest_lib.client.common_lib.hosts.base_classes.Host.
164 Wait for servod process to go up. Called by base class when
165 rebooting the device.
166
167 """
168 processes = [self.SERVOD_PROCESS]
169 return processes
170
171
beeps5e8c45a2013-12-17 22:05:11 -0800172 def _is_cros_host(self):
173 """Check if a servo host is running chromeos.
174
175 @return: True if the servo host is running chromeos.
176 False if it isn't, or we don't have enough information.
177 """
178 try:
179 result = self.run('grep -q CHROMEOS /etc/lsb-release',
180 ignore_status=True, timeout=10)
181 except (error.AutoservRunError, error.AutoservSSHTimeout):
182 return False
183 return result.exit_status == 0
184
185
Fang Deng5d518f42013-08-02 14:04:32 -0700186 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
187 connect_timeout=None, alive_interval=None):
188 """Override default make_ssh_command to use tuned options.
189
190 Tuning changes:
191 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
192 connection failure. Consistency with remote_access.py.
193
194 - ServerAliveInterval=180; which causes SSH to ping connection every
195 180 seconds. In conjunction with ServerAliveCountMax ensures
196 that if the connection dies, Autotest will bail out quickly.
197
198 - ServerAliveCountMax=3; consistency with remote_access.py.
199
200 - ConnectAttempts=4; reduce flakiness in connection errors;
201 consistency with remote_access.py.
202
203 - UserKnownHostsFile=/dev/null; we don't care about the keys.
204
205 - SSH protocol forced to 2; needed for ServerAliveInterval.
206
207 @param user User name to use for the ssh connection.
208 @param port Port on the target host to use for ssh connection.
209 @param opts Additional options to the ssh command.
210 @param hosts_file Ignored.
211 @param connect_timeout Ignored.
212 @param alive_interval Ignored.
213
214 @returns: An ssh command with the requested settings.
215
216 """
217 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
218 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
219 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
220 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
221 ' -o Protocol=2 -l %s -p %d')
222 return base_command % (opts, user, port)
223
224
225 def _make_scp_cmd(self, sources, dest):
226 """Format scp command.
227
228 Given a list of source paths and a destination path, produces the
229 appropriate scp command for encoding it. Remote paths must be
230 pre-encoded. Overrides _make_scp_cmd in AbstractSSHHost
231 to allow additional ssh options.
232
233 @param sources: A list of source paths to copy from.
234 @param dest: Destination path to copy to.
235
236 @returns: An scp command that copies |sources| on local machine to
237 |dest| on the remote servo host.
238
239 """
240 command = ('scp -rq %s -o BatchMode=yes -o StrictHostKeyChecking=no '
241 '-o UserKnownHostsFile=/dev/null -P %d %s "%s"')
242 return command % (self.master_ssh_option,
243 self.port, ' '.join(sources), dest)
244
245
246 def run(self, command, timeout=3600, ignore_status=False,
247 stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS,
248 connect_timeout=30, options='', stdin=None, verbose=True, args=()):
249 """Run a command on the servo host.
250
251 Extends method `run` in SSHHost. If the servo host is a remote device,
252 it will call `run` in SSHost without changing anything.
253 If the servo host is 'localhost', it will call utils.system_output.
254
255 @param command: The command line string.
256 @param timeout: Time limit in seconds before attempting to
257 kill the running process. The run() function
258 will take a few seconds longer than 'timeout'
259 to complete if it has to kill the process.
260 @param ignore_status: Do not raise an exception, no matter
261 what the exit code of the command is.
262 @param stdout_tee/stderr_tee: Where to tee the stdout/stderr.
263 @param connect_timeout: SSH connection timeout (in seconds)
264 Ignored if host is 'localhost'.
265 @param options: String with additional ssh command options
266 Ignored if host is 'localhost'.
267 @param stdin: Stdin to pass (a string) to the executed command.
268 @param verbose: Log the commands.
269 @param args: Sequence of strings to pass as arguments to command by
270 quoting them in " and escaping their contents if necessary.
271
272 @returns: A utils.CmdResult object.
273
274 @raises AutoservRunError if the command failed.
275 @raises AutoservSSHTimeout SSH connection has timed out. Only applies
276 when servo host is not 'localhost'.
277
278 """
279 run_args = {'command': command, 'timeout': timeout,
280 'ignore_status': ignore_status, 'stdout_tee': stdout_tee,
281 'stderr_tee': stderr_tee, 'stdin': stdin,
282 'verbose': verbose, 'args': args}
283 if self.is_localhost():
284 if self._sudo_required:
285 run_args['command'] = 'sudo -n %s' % command
286 try:
287 return utils.run(**run_args)
288 except error.CmdError as e:
289 logging.error(e)
290 raise error.AutoservRunError('command execution error',
291 e.result_obj)
292 else:
293 run_args['connect_timeout'] = connect_timeout
294 run_args['options'] = options
295 return super(ServoHost, self).run(**run_args)
296
297
Dan Shi33412a82014-06-10 15:12:27 -0700298 @_timer.decorate
Fang Deng5d518f42013-08-02 14:04:32 -0700299 def _check_servod(self):
300 """A sanity check of the servod state."""
301 msg_prefix = 'Servod error: %s'
302 error_msg = None
303 try:
304 timeout, _ = retry.timeout(
305 self._servod_server.get, args=('pwr_button', ),
306 timeout_sec=self.PWR_BUTTON_CMD_TIMEOUT_SECS)
307 if timeout:
308 error_msg = msg_prefix % 'Request timed out.'
309 except (socket.error, xmlrpclib.Error, httplib.BadStatusLine) as e:
310 error_msg = msg_prefix % e
311 if error_msg:
312 raise ServoHostVerifyFailure(error_msg)
313
314
315 def _check_servo_host_usb(self):
316 """A sanity check of the USB device.
317
318 Sometimes the usb gets wedged due to a kernel bug on the beaglebone.
319 A symptom is the presence of /dev/sda without /dev/sda1. The check
320 here ensures that if /dev/sda exists, /dev/sda1 must also exist.
321 See crbug.com/225932.
322
323 @raises ServoHostVerifyFailure if /dev/sda exists without /dev/sda1 on
324 the beaglebone.
325
326 """
327 try:
328 # The following test exits with a non-zero code
329 # and raises AutoserverRunError if error is detected.
330 self.run('test ! -b /dev/sda -o -b /dev/sda1')
331 except (error.AutoservRunError, error.AutoservSSHTimeout) as e:
332 raise ServoHostVerifyFailure(
333 'USB sanity check on %s failed: %s' % (self.hostname, e))
334
335
Dan Shi33412a82014-06-10 15:12:27 -0700336 def _check_servo_config(self):
337 """Check if config file exists for servod.
338
339 If servod config file does not exist, there is no need to verify if
340 servo is working. The servo could be attached to a board not supported
341 yet.
342
343 @raises ServoHostVerifyFailure if /var/lib/servod/config does not exist.
344
345 """
346 try:
347 self.run('test -f /var/lib/servod/config')
348 except (error.AutoservRunError, error.AutoservSSHTimeout) as e:
Ricky Liang86b80182014-06-13 14:39:42 +0800349 if not self._is_cros_host():
350 logging.info('Ignoring servo config check failure, either %s '
351 'is not running chromeos or we cannot find enough '
352 'information about the host.', self.hostname)
353 return
Dan Shi33412a82014-06-10 15:12:27 -0700354 raise ServoHostVerifyFailure(
355 'Servo config file check failed for %s: %s' %
356 (self.hostname, e))
357
358
beeps5e8c45a2013-12-17 22:05:11 -0800359 @_timer.decorate
360 def _update_image(self):
361 """Update the image on the servo host, if needed.
362
363 This method does nothing for servo hosts that are not running chromeos.
364 If the host is running chromeos, and a newer image is available on the
365 devserver, trigger a download and apply it in the background. If an
366 update has already been downloaded and applied, reboot the servo host
367 into the new image. If update_engine_client is in the process of
368 applying an update that was triggered on a previous invocation, do
369 nothing.
370
371 @raises dev_server.DevServerException: If all the devservers are down.
372 @raises site_utils.ParseBuildNameException: If the devserver returns
373 an invalid build name.
374 @raises autoupdater.ChromiumOSError: If something goes wrong in the
375 checking update engine client status or applying an update.
376 @raises AutoservRunError: If the update_engine_client isn't present on
377 the host, and the host is a cros_host.
378 """
379 #TODO(beeps): Remove this check once all servo hosts are using chromeos.
380 if not self._is_cros_host():
381 logging.info('Not attempting an update, either %s is not running '
382 'chromeos or we cannot find enough information about '
383 'the host.', self.hostname)
384 return
385
386 update_branch = global_config.global_config.get_config_value(
387 'CROS', 'servo_builder')
388 ds = dev_server.ImageServer.resolve(self.hostname)
389 latest_build = ds.get_latest_build_in_server(target=update_branch)
390
391 # We might have just purged all the beaglebone builds on the devserver
392 # after having triggered a download the last time we verified this
393 # beaglebone, so we still need to reboot if necessary.
394 if latest_build is None:
395 logging.debug('Could not find any builds for %s on %s',
396 update_branch, ds.url())
397 url = ds.url()
398 latest_build_number = None
399 else:
400 latest_build = '%s/%s' % (update_branch, latest_build)
401 latest_build_number = server_site_utils.ParseBuildName(
402 latest_build)[3]
403 url = ds.get_update_url(latest_build)
404
405 updater = autoupdater.ChromiumOSUpdater(update_url=url, host=self)
406 current_build_number = updater.get_build_id()
407 status = updater.check_update_status()
408
409 if status == autoupdater.UPDATER_NEED_REBOOT:
410 logging.info('Rebooting beaglebone host %s with build %s',
411 self.hostname, current_build_number)
412 kwargs = {
413 'reboot_cmd': ('((reboot & sleep 10; reboot -f &) '
414 '</dev/null >/dev/null 2>&1 &)'),
415 'fastsync': True,
416 'label': None,
Dan Shiddd7a0e2014-04-29 11:55:34 -0700417 'wait': False,
beeps5e8c45a2013-12-17 22:05:11 -0800418 }
Dan Shiddd7a0e2014-04-29 11:55:34 -0700419 # Do not wait for reboot to complete. Otherwise, self.reboot call
420 # will log reboot failure if servo does not come back. The logged
421 # reboot failure will lead to test job failure. If the test does not
422 # require servo, we don't want servo failure to fail the test with
423 # error: `Host did not return from reboot` in status.log
424 # If servo does not come back after reboot, exception needs to be
425 # raised, so test requires servo should fail.
beeps5e8c45a2013-12-17 22:05:11 -0800426 self.reboot(**kwargs)
Dan Shiddd7a0e2014-04-29 11:55:34 -0700427 if self.wait_up(timeout=120):
428 current_build_number = updater.get_build_id()
429 logging.info('servo host %s back from reboot, with build %s',
430 self.hostname, current_build_number)
431 else:
432 raise error.AutoservHostError(
433 'servo host %s failed to come back from reboot.' %
434 self.hostname)
beeps5e8c45a2013-12-17 22:05:11 -0800435
436 if status in autoupdater.UPDATER_PROCESSING_UPDATE:
437 logging.info('servo host %s already processing an update, update '
438 'engine client status=%s', self.hostname, status)
439 elif (latest_build_number and
440 current_build_number != latest_build_number):
441 logging.info('Using devserver url: %s to trigger update on '
442 'servo host %s, from %s to %s', url, self.hostname,
443 current_build_number, latest_build_number)
444 try:
445 updater.trigger_update()
446 except autoupdater.RootFSUpdateError as e:
447 trigger_download_status = 'failed with %s' % str(e)
448 stats.Counter('servo_host.RootFSUpdateError').increment()
449 else:
450 trigger_download_status = 'passed'
451 logging.info('Triggered download and update %s for %s, '
452 'update engine currently in status %s',
453 trigger_download_status, self.hostname,
454 updater.check_update_status())
455 else:
456 logging.info('servo host %s does not require an update.',
457 self.hostname)
458
459
Fang Deng5d518f42013-08-02 14:04:32 -0700460 def verify_software(self):
beeps5e8c45a2013-12-17 22:05:11 -0800461 """Update the servo host and verify it's in a good state.
Fang Deng5d518f42013-08-02 14:04:32 -0700462
463 It overrides the base class function for verify_software.
beeps5e8c45a2013-12-17 22:05:11 -0800464 If an update is available, downloads and applies it. Then verifies:
Fang Deng5d518f42013-08-02 14:04:32 -0700465 1) Whether basic servo command can run successfully.
466 2) Whether USB is in a good state. crbug.com/225932
467
468 @raises ServoHostVerifyFailure if servo host does not pass the checks.
469
470 """
beeps5e8c45a2013-12-17 22:05:11 -0800471 logging.info('Applying an update to the servo host, if necessary.')
472 self._update_image()
473
Dan Shi33412a82014-06-10 15:12:27 -0700474 logging.info('Verifying if servo config file exists.')
475 self._check_servo_config()
476
Fang Deng5d518f42013-08-02 14:04:32 -0700477 logging.info('Verifying servo host %s with sanity checks.',
478 self.hostname)
Fang Deng5d518f42013-08-02 14:04:32 -0700479 self._check_servo_host_usb()
Dan Shi33412a82014-06-10 15:12:27 -0700480
Dan Shi4d478522014-02-14 13:46:32 -0800481 # If servo is already initialized, we don't need to do it again, call
482 # _check_servod should be enough.
483 if self._servo:
484 self._check_servod()
485 else:
486 self._servo = servo.Servo(servo_host=self)
487
Fang Deng5d518f42013-08-02 14:04:32 -0700488 logging.info('Sanity checks pass on servo host %s', self.hostname)
489
490
491 def _repair_with_sysrq_reboot(self):
492 """Reboot with magic SysRq key."""
493 self.reboot(timeout=self.REBOOT_TIMEOUT_SECS,
494 down_timeout=self.HOST_DOWN_TIMEOUT_SECS,
495 reboot_cmd='echo "b" > /proc/sysrq-trigger &',
496 fastsync=True)
497 time.sleep(self.REBOOT_DELAY_SECS)
498
499
Fang Dengd4fe7392013-09-20 12:18:21 -0700500 def has_power(self):
501 """Return whether or not the servo host is powered by PoE."""
502 # TODO(fdeng): See crbug.com/302791
503 # For now, assume all servo hosts in the lab have power.
504 return self.is_in_lab()
505
506
507 def power_cycle(self):
508 """Cycle power to this host via PoE if it is a lab device.
509
510 @raises ServoHostRepairFailure if it fails to power cycle the
511 servo host.
512
513 """
514 if self.has_power():
515 try:
516 rpm_client.set_power(self.hostname, 'CYCLE')
517 except (socket.error, xmlrpclib.Error,
518 httplib.BadStatusLine,
519 rpm_client.RemotePowerException) as e:
520 raise ServoHostRepairFailure(
521 'Power cycling %s failed: %s' % (self.hostname, e))
522 else:
523 logging.info('Skipping power cycling, not a lab device.')
524
525
Fang Deng5d518f42013-08-02 14:04:32 -0700526 def _powercycle_to_repair(self):
Fang Dengd4fe7392013-09-20 12:18:21 -0700527 """Power cycle the servo host using PoE.
528
529 @raises ServoHostRepairFailure if it fails to fix the servo host.
Fang Dengf0ea6142013-10-10 21:43:16 -0700530 @raises ServoHostRepairMethodNA if it does not support power.
Fang Dengd4fe7392013-09-20 12:18:21 -0700531
532 """
533 if not self.has_power():
Fang Dengf0ea6142013-10-10 21:43:16 -0700534 raise ServoHostRepairMethodNA('%s does not support power.' %
535 self.hostname)
Fang Dengd4fe7392013-09-20 12:18:21 -0700536 logging.info('Attempting repair via PoE powercycle.')
537 failed_cycles = 0
538 self.power_cycle()
539 while not self.wait_up(timeout=self.REBOOT_TIMEOUT_SECS):
540 failed_cycles += 1
541 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
542 raise ServoHostRepairFailure(
543 'Powercycled host %s %d times; device did not come back'
544 ' online.' % (self.hostname, failed_cycles))
545 self.power_cycle()
546 logging.info('Powercycling was successful after %d failures.',
547 failed_cycles)
548 # Allow some time for servod to get started.
549 time.sleep(self.REBOOT_DELAY_SECS)
Fang Deng5d518f42013-08-02 14:04:32 -0700550
551
552 def repair_full(self):
553 """Attempt to repair servo host.
554
555 This overrides the base class function for repair.
556 Note if the host is not in Cros Lab, the repair procedure
557 will be skipped.
558
559 @raises ServoHostRepairTotalFailure if all attempts fail.
560
561 """
562 if not self.is_in_lab():
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700563 logging.warning('Skip repairing servo host %s: Not a lab device.',
Fang Deng5d518f42013-08-02 14:04:32 -0700564 self.hostname)
565 return
566 logging.info('Attempting to repair servo host %s.', self.hostname)
Dan Shi4d478522014-02-14 13:46:32 -0800567 # Reset the cache to guarantee servo initialization being called later.
568 self._servo = None
Dan Shi0cf92c82014-02-20 15:45:01 -0800569 # TODO(dshi): add self._powercycle_to_repair back to repair_funcs
570 # after crbug.com/336606 is fixed.
571 repair_funcs = [self._repair_with_sysrq_reboot,]
Fang Deng5d518f42013-08-02 14:04:32 -0700572 errors = []
573 for repair_func in repair_funcs:
Fang Dengf0ea6142013-10-10 21:43:16 -0700574 counter_prefix = 'servo_host_repair.%s.' % repair_func.__name__
Fang Deng5d518f42013-08-02 14:04:32 -0700575 try:
576 repair_func()
577 self.verify()
Fang Dengf0ea6142013-10-10 21:43:16 -0700578 stats.Counter(counter_prefix + 'SUCCEEDED').increment()
Fang Deng5d518f42013-08-02 14:04:32 -0700579 return
Fang Dengf0ea6142013-10-10 21:43:16 -0700580 except ServoHostRepairMethodNA as e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700581 logging.warning('Repair method NA: %s', e)
Fang Dengf0ea6142013-10-10 21:43:16 -0700582 stats.Counter(counter_prefix + 'RepairNA').increment()
583 errors.append(str(e))
Fang Deng5d518f42013-08-02 14:04:32 -0700584 except Exception as e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700585 logging.warning('Failed to repair servo: %s', e)
Fang Dengf0ea6142013-10-10 21:43:16 -0700586 stats.Counter(counter_prefix + 'FAILED').increment()
Fang Deng5d518f42013-08-02 14:04:32 -0700587 errors.append(str(e))
Fang Dengf0ea6142013-10-10 21:43:16 -0700588 stats.Counter('servo_host_repair.Full_Repair_Failed').increment()
Fang Deng5d518f42013-08-02 14:04:32 -0700589 raise ServoHostRepairTotalFailure(
590 'All attempts at repairing the servo failed:\n%s' %
591 '\n'.join(errors))
592
593
Dan Shi4d478522014-02-14 13:46:32 -0800594 def get_servo(self):
595 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700596
Dan Shi4d478522014-02-14 13:46:32 -0800597 @return: a servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700598 """
Dan Shi4d478522014-02-14 13:46:32 -0800599 return self._servo
600
601
602def create_servo_host(dut, servo_args):
603 """Create a ServoHost object.
604
605 There three possible cases:
606 1) If the DUT is in Cros Lab and has a beaglebone and a servo, then
607 create a ServoHost object pointing to the beaglebone. servo_args
608 is ignored.
609 2) If not case 1) and servo_args is neither None nor empty, then
610 create a ServoHost object using servo_args.
611 3) If neither case 1) or 2) applies, return None.
612 When the servo_args is not None, we assume the servo is required by the
613 test. If servo failed to be verified, we will attempt to repair it. If servo
614 is not required, we will initialize ServoHost without initializing a servo
615 object.
616
617 @param dut: host name of the host that servo connects. It can be used to
618 lookup the servo in test lab using naming convention.
619 @param servo_args: A dictionary that contains args for creating
620 a ServoHost object,
621 e.g. {'servo_host': '172.11.11.111',
622 'servo_port': 9999}.
623 See comments above.
624
625 @returns: A ServoHost object or None. See comments above.
626
627 """
628 lab_servo_hostname = make_servo_hostname(dut)
629 is_in_lab = utils.host_is_in_lab_zone(lab_servo_hostname)
630
631 if is_in_lab:
632 return ServoHost(servo_host=lab_servo_hostname, is_in_lab=is_in_lab,
633 required_by_test=(servo_args is not None))
634 elif servo_args is not None:
635 return ServoHost(required_by_test=True, is_in_lab=False, **servo_args)
636 else:
Dan Shibbb0cb62014-03-24 17:50:57 -0700637 return None