blob: 28e41865887374a47b8cc124235b902ca5d4fe5a [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
Fang Deng5d518f42013-08-02 14:04:32 -070015import xmlrpclib
16
17from autotest_lib.client.bin import utils
18from autotest_lib.client.common_lib import error
beeps5e8c45a2013-12-17 22:05:11 -080019from autotest_lib.client.common_lib import global_config
Richard Barnette9a26ad62016-06-10 12:03:08 -070020from autotest_lib.client.common_lib import hosts
Dan Shi0942b1d2015-03-31 11:07:00 -070021from autotest_lib.client.common_lib import lsbrelease_utils
beeps5e8c45a2013-12-17 22:05:11 -080022from autotest_lib.client.common_lib.cros import autoupdater
23from autotest_lib.client.common_lib.cros import dev_server
Fang Deng5d518f42013-08-02 14:04:32 -070024from autotest_lib.client.common_lib.cros import retry
Gabe Black1e1c41b2015-02-04 23:55:15 -080025from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Christopher Wileycef1f902014-06-19 11:11:23 -070026from autotest_lib.client.common_lib.cros.network import ping_runner
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000027from autotest_lib.client.cros import constants as client_constants
Richard Barnettee519dcd2016-08-15 17:37:17 -070028from autotest_lib.server import afe_utils
beeps5e8c45a2013-12-17 22:05:11 -080029from autotest_lib.server import site_utils as server_site_utils
Cheng-Yi Chiang22612862015-08-20 20:39:57 +080030from autotest_lib.server.cros import dnsname_mangler
Simran Basi0739d682015-02-25 16:22:56 -080031from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Richard Barnette9a26ad62016-06-10 12:03:08 -070032from autotest_lib.server.cros.servo import servo
33from autotest_lib.server.hosts import servo_repair
Fang Deng5d518f42013-08-02 14:04:32 -070034from autotest_lib.server.hosts import ssh_host
Fang Dengd4fe7392013-09-20 12:18:21 -070035from autotest_lib.site_utils.rpm_control_system import rpm_client
Fang Deng5d518f42013-08-02 14:04:32 -070036
37
Simran Basi0739d682015-02-25 16:22:56 -080038# Names of the host attributes in the database that represent the values for
39# the servo_host and servo_port for a servo connected to the DUT.
40SERVO_HOST_ATTR = 'servo_host'
41SERVO_PORT_ATTR = 'servo_port'
Richard Barnettee519dcd2016-08-15 17:37:17 -070042SERVO_BOARD_ATTR = 'servo_board'
Kevin Cheng643ce8a2016-09-15 15:42:12 -070043SERVO_SERIAL_ATTR = 'servo_serial'
Simran Basi0739d682015-02-25 16:22:56 -080044
Dan Shi3b2adf62015-09-02 17:46:54 -070045_CONFIG = global_config.global_config
xixuan6cf6d2f2016-01-29 15:29:00 -080046ENABLE_SSH_TUNNEL_FOR_SERVO = _CONFIG.get_config_value(
47 'CROS', 'enable_ssh_tunnel_for_servo', type=bool, default=False)
Simran Basi0739d682015-02-25 16:22:56 -080048
Fang Deng5d518f42013-08-02 14:04:32 -070049
Fang Deng5d518f42013-08-02 14:04:32 -070050class ServoHost(ssh_host.SSHHost):
51 """Host class for a host that controls a servo, e.g. beaglebone."""
52
Richard Barnette9a26ad62016-06-10 12:03:08 -070053 DEFAULT_PORT = 9999
54
Dan Shie5b3c512014-08-21 12:12:09 -070055 # Timeout for initializing servo signals.
56 INITIALIZE_SERVO_TIMEOUT_SECS = 30
Richard Barnette9a26ad62016-06-10 12:03:08 -070057
xixuan6cf6d2f2016-01-29 15:29:00 -080058 # Ready test function
59 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070060
Gabe Black1e1c41b2015-02-04 23:55:15 -080061 _timer = autotest_stats.Timer('servo_host')
Fang Dengd4fe7392013-09-20 12:18:21 -070062
Fang Deng5d518f42013-08-02 14:04:32 -070063
Richard Barnette17bfc6c2016-08-04 18:41:43 -070064 def _initialize(self, servo_host='localhost',
Richard Barnettee519dcd2016-08-15 17:37:17 -070065 servo_port=DEFAULT_PORT, servo_board=None,
Kevin Cheng643ce8a2016-09-15 15:42:12 -070066 servo_serial=None, is_in_lab=None, *args, **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -070067 """Initialize a ServoHost instance.
68
69 A ServoHost instance represents a host that controls a servo.
70
71 @param servo_host: Name of the host where the servod process
72 is running.
73 @param servo_port: Port the servod process is listening on.
Dan Shi4d478522014-02-14 13:46:32 -080074 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
75 to None, for which utils.host_is_in_lab_zone will be
76 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -070077
78 """
79 super(ServoHost, self)._initialize(hostname=servo_host,
80 *args, **dargs)
Richard Barnettee519dcd2016-08-15 17:37:17 -070081 self.servo_port = servo_port
82 self.servo_board = servo_board
Kevin Cheng643ce8a2016-09-15 15:42:12 -070083 self.servo_serial = servo_serial
Richard Barnettee519dcd2016-08-15 17:37:17 -070084 self._servo = None
Richard Barnette9a26ad62016-06-10 12:03:08 -070085 self._repair_strategy = (
86 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -070087 self._is_localhost = (self.hostname == 'localhost')
88 if self._is_localhost:
89 self._is_in_lab = False
90 elif is_in_lab is None:
Dan Shi4d478522014-02-14 13:46:32 -080091 self._is_in_lab = utils.host_is_in_lab_zone(self.hostname)
92 else:
93 self._is_in_lab = is_in_lab
xixuan6cf6d2f2016-01-29 15:29:00 -080094
Richard Barnettee519dcd2016-08-15 17:37:17 -070095 # Commands on the servo host must be run by the superuser.
96 # Our account on a remote host is root, but if our target is
97 # localhost then we might be running unprivileged. If so,
98 # `sudo` will have to be added to the commands.
Fang Deng5d518f42013-08-02 14:04:32 -070099 if self._is_localhost:
100 self._sudo_required = utils.system_output('id -u') != '0'
101 else:
102 self._sudo_required = False
Richard Barnettee519dcd2016-08-15 17:37:17 -0700103
Richard Barnette9a26ad62016-06-10 12:03:08 -0700104
105 def connect_servo(self):
106 """
107 Establish a connection to the servod server on this host.
108
109 Initializes `self._servo` and then verifies that all network
110 connections are working. This will create an ssh tunnel if
111 it's required.
112
113 As a side effect of testing the connection, all signals on the
114 target servo are reset to default values, and the USB stick is
115 set to the neutral (off) position.
116 """
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700117 servo_obj = servo.Servo(servo_host=self, servo_serial=self.servo_serial)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700118 timeout, _ = retry.timeout(
119 servo_obj.initialize_dut,
120 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
121 if timeout:
122 raise hosts.AutoservVerifyError(
123 'Servo initialize timed out.')
124 self._servo = servo_obj
125
126
127 def disconnect_servo(self):
128 """
129 Disconnect our servo if it exists.
130
131 If we've previously successfully connected to our servo,
132 disconnect any established ssh tunnel, and set `self._servo`
133 back to `None`.
134 """
135 if self._servo:
136 # N.B. This call is safe even without a tunnel:
137 # rpc_server_tracker.disconnect() silently ignores
138 # unknown ports.
139 self.rpc_server_tracker.disconnect(self.servo_port)
140 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700141
142
143 def is_in_lab(self):
144 """Check whether the servo host is a lab device.
145
146 @returns: True if the servo host is in Cros Lab, otherwise False.
147
148 """
149 return self._is_in_lab
150
151
152 def is_localhost(self):
153 """Checks whether the servo host points to localhost.
154
155 @returns: True if it points to localhost, otherwise False.
156
157 """
158 return self._is_localhost
159
160
161 def get_servod_server_proxy(self):
Richard Barnette9a26ad62016-06-10 12:03:08 -0700162 """
163 Return a proxy that can be used to communicate with servod server.
Fang Deng5d518f42013-08-02 14:04:32 -0700164
165 @returns: An xmlrpclib.ServerProxy that is connected to the servod
166 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700167 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700168 if ENABLE_SSH_TUNNEL_FOR_SERVO and not self.is_localhost():
169 return self.rpc_server_tracker.xmlrpc_connect(
170 None, self.servo_port,
171 ready_test_name=self.SERVO_READY_METHOD,
172 timeout_seconds=60)
173 else:
174 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
175 return xmlrpclib.ServerProxy(remote)
Fang Deng5d518f42013-08-02 14:04:32 -0700176
177
Richard Barnette9a26ad62016-06-10 12:03:08 -0700178 def is_cros_host(self):
beeps5e8c45a2013-12-17 22:05:11 -0800179 """Check if a servo host is running chromeos.
180
181 @return: True if the servo host is running chromeos.
182 False if it isn't, or we don't have enough information.
183 """
184 try:
185 result = self.run('grep -q CHROMEOS /etc/lsb-release',
186 ignore_status=True, timeout=10)
187 except (error.AutoservRunError, error.AutoservSSHTimeout):
188 return False
189 return result.exit_status == 0
190
191
Fang Deng5d518f42013-08-02 14:04:32 -0700192 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
193 connect_timeout=None, alive_interval=None):
194 """Override default make_ssh_command to use tuned options.
195
196 Tuning changes:
197 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
198 connection failure. Consistency with remote_access.py.
199
200 - ServerAliveInterval=180; which causes SSH to ping connection every
201 180 seconds. In conjunction with ServerAliveCountMax ensures
202 that if the connection dies, Autotest will bail out quickly.
203
204 - ServerAliveCountMax=3; consistency with remote_access.py.
205
206 - ConnectAttempts=4; reduce flakiness in connection errors;
207 consistency with remote_access.py.
208
209 - UserKnownHostsFile=/dev/null; we don't care about the keys.
210
211 - SSH protocol forced to 2; needed for ServerAliveInterval.
212
213 @param user User name to use for the ssh connection.
214 @param port Port on the target host to use for ssh connection.
215 @param opts Additional options to the ssh command.
216 @param hosts_file Ignored.
217 @param connect_timeout Ignored.
218 @param alive_interval Ignored.
219
220 @returns: An ssh command with the requested settings.
221
222 """
223 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
224 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
225 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
226 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
227 ' -o Protocol=2 -l %s -p %d')
228 return base_command % (opts, user, port)
229
230
231 def _make_scp_cmd(self, sources, dest):
232 """Format scp command.
233
234 Given a list of source paths and a destination path, produces the
235 appropriate scp command for encoding it. Remote paths must be
236 pre-encoded. Overrides _make_scp_cmd in AbstractSSHHost
237 to allow additional ssh options.
238
239 @param sources: A list of source paths to copy from.
240 @param dest: Destination path to copy to.
241
242 @returns: An scp command that copies |sources| on local machine to
243 |dest| on the remote servo host.
244
245 """
246 command = ('scp -rq %s -o BatchMode=yes -o StrictHostKeyChecking=no '
247 '-o UserKnownHostsFile=/dev/null -P %d %s "%s"')
248 return command % (self.master_ssh_option,
249 self.port, ' '.join(sources), dest)
250
251
252 def run(self, command, timeout=3600, ignore_status=False,
253 stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS,
254 connect_timeout=30, options='', stdin=None, verbose=True, args=()):
255 """Run a command on the servo host.
256
257 Extends method `run` in SSHHost. If the servo host is a remote device,
258 it will call `run` in SSHost without changing anything.
259 If the servo host is 'localhost', it will call utils.system_output.
260
261 @param command: The command line string.
262 @param timeout: Time limit in seconds before attempting to
263 kill the running process. The run() function
264 will take a few seconds longer than 'timeout'
265 to complete if it has to kill the process.
266 @param ignore_status: Do not raise an exception, no matter
267 what the exit code of the command is.
268 @param stdout_tee/stderr_tee: Where to tee the stdout/stderr.
269 @param connect_timeout: SSH connection timeout (in seconds)
270 Ignored if host is 'localhost'.
271 @param options: String with additional ssh command options
272 Ignored if host is 'localhost'.
273 @param stdin: Stdin to pass (a string) to the executed command.
274 @param verbose: Log the commands.
275 @param args: Sequence of strings to pass as arguments to command by
276 quoting them in " and escaping their contents if necessary.
277
278 @returns: A utils.CmdResult object.
279
280 @raises AutoservRunError if the command failed.
281 @raises AutoservSSHTimeout SSH connection has timed out. Only applies
282 when servo host is not 'localhost'.
283
284 """
285 run_args = {'command': command, 'timeout': timeout,
286 'ignore_status': ignore_status, 'stdout_tee': stdout_tee,
287 'stderr_tee': stderr_tee, 'stdin': stdin,
288 'verbose': verbose, 'args': args}
289 if self.is_localhost():
290 if self._sudo_required:
291 run_args['command'] = 'sudo -n %s' % command
292 try:
293 return utils.run(**run_args)
294 except error.CmdError as e:
295 logging.error(e)
296 raise error.AutoservRunError('command execution error',
297 e.result_obj)
298 else:
299 run_args['connect_timeout'] = connect_timeout
300 run_args['options'] = options
301 return super(ServoHost, self).run(**run_args)
302
303
Richard Barnette9a26ad62016-06-10 12:03:08 -0700304 def _get_release_version(self):
Dan Shi0942b1d2015-03-31 11:07:00 -0700305 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
306
307 @returns The version string in lsb-release, under attribute
308 CHROMEOS_RELEASE_VERSION.
309 """
310 lsb_release_content = self.run(
311 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
312 return lsbrelease_utils.get_chromeos_release_version(
313 lsb_release_content=lsb_release_content)
314
315
Richard Barnette3a7697f2016-04-20 11:33:27 -0700316 def _check_for_reboot(self, updater):
317 """
318 Reboot this servo host if an upgrade is waiting.
319
320 If the host has successfully downloaded and finalized a new
321 build, reboot.
322
323 @param updater: a ChromiumOSUpdater instance for checking
324 whether reboot is needed.
325 @return Return a (status, build) tuple reflecting the
326 update_engine status and current build of the host
327 at the end of the call.
328 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700329 current_build_number = self._get_release_version()
Richard Barnette3a7697f2016-04-20 11:33:27 -0700330 status = updater.check_update_status()
331 if status == autoupdater.UPDATER_NEED_REBOOT:
332 logging.info('Rebooting beaglebone host %s from build %s',
333 self.hostname, current_build_number)
334 # Tell the reboot() call not to wait for completion.
335 # Otherwise, the call will log reboot failure if servo does
336 # not come back. The logged reboot failure will lead to
337 # test job failure. If the test does not require servo, we
338 # don't want servo failure to fail the test with error:
339 # `Host did not return from reboot` in status.log.
340 reboot_cmd = 'sleep 1 ; reboot & sleep 10; reboot -f',
Richard Barnetteab9769f2016-06-01 15:01:44 -0700341 self.reboot(reboot_cmd=reboot_cmd, fastsync=True, wait=False)
Richard Barnette3a7697f2016-04-20 11:33:27 -0700342
343 # We told the reboot() call not to wait, but we need to wait
344 # for the reboot before we continue. Alas. The code from
345 # here below is basically a copy of Host.wait_for_restart(),
346 # with the logging bits ripped out, so that they can't cause
347 # the failure logging problem described above.
348 #
349 # The black stain that this has left on my soul can never be
350 # erased.
351 old_boot_id = self.get_boot_id()
352 if not self.wait_down(timeout=self.WAIT_DOWN_REBOOT_TIMEOUT,
353 warning_timer=self.WAIT_DOWN_REBOOT_WARNING,
354 old_boot_id=old_boot_id):
355 raise error.AutoservHostError(
356 'servo host %s failed to shut down.' %
357 self.hostname)
358 if self.wait_up(timeout=120):
Richard Barnette9a26ad62016-06-10 12:03:08 -0700359 current_build_number = self._get_release_version()
Richard Barnette3a7697f2016-04-20 11:33:27 -0700360 status = updater.check_update_status()
361 logging.info('servo host %s back from reboot, with build %s',
362 self.hostname, current_build_number)
363 else:
364 raise error.AutoservHostError(
365 'servo host %s failed to come back from reboot.' %
366 self.hostname)
367 return status, current_build_number
368
369
beeps5e8c45a2013-12-17 22:05:11 -0800370 @_timer.decorate
Richard Barnette3a7697f2016-04-20 11:33:27 -0700371 def update_image(self, wait_for_update=False):
beeps5e8c45a2013-12-17 22:05:11 -0800372 """Update the image on the servo host, if needed.
373
J. Richard Barnette84895392015-04-30 12:31:01 -0700374 This method recognizes the following cases:
375 * If the Host is not running Chrome OS, do nothing.
376 * If a previously triggered update is now complete, reboot
377 to the new version.
378 * If the host is processing a previously triggered update,
379 do nothing.
380 * If the host is running a version of Chrome OS different
381 from the default for servo Hosts, trigger an update, but
382 don't wait for it to complete.
beeps5e8c45a2013-12-17 22:05:11 -0800383
Richard Barnette3a7697f2016-04-20 11:33:27 -0700384 @param wait_for_update If an update needs to be applied and
385 this is true, then don't return until the update is
386 downloaded and finalized, and the host rebooted.
beeps5e8c45a2013-12-17 22:05:11 -0800387 @raises dev_server.DevServerException: If all the devservers are down.
388 @raises site_utils.ParseBuildNameException: If the devserver returns
389 an invalid build name.
390 @raises autoupdater.ChromiumOSError: If something goes wrong in the
391 checking update engine client status or applying an update.
392 @raises AutoservRunError: If the update_engine_client isn't present on
393 the host, and the host is a cros_host.
J. Richard Barnette84895392015-04-30 12:31:01 -0700394
beeps5e8c45a2013-12-17 22:05:11 -0800395 """
Dan Shib795b5a2015-09-24 13:26:35 -0700396 # servod could be running in a Ubuntu workstation.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700397 if not self.is_cros_host():
beeps5e8c45a2013-12-17 22:05:11 -0800398 logging.info('Not attempting an update, either %s is not running '
399 'chromeos or we cannot find enough information about '
400 'the host.', self.hostname)
401 return
402
Dan Shib795b5a2015-09-24 13:26:35 -0700403 if lsbrelease_utils.is_moblab():
404 logging.info('Not attempting an update, %s is running moblab.',
405 self.hostname)
406 return
407
Richard Barnette3a7697f2016-04-20 11:33:27 -0700408 board = _CONFIG.get_config_value('CROS', 'servo_board')
J. Richard Barnette84895392015-04-30 12:31:01 -0700409 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
410 target_version = afe.run('get_stable_version', board=board)
Dan Shi3b2adf62015-09-02 17:46:54 -0700411 build_pattern = _CONFIG.get_config_value(
J. Richard Barnette84895392015-04-30 12:31:01 -0700412 'CROS', 'stable_build_pattern')
413 target_build = build_pattern % (board, target_version)
414 target_build_number = server_site_utils.ParseBuildName(
415 target_build)[3]
beeps5e8c45a2013-12-17 22:05:11 -0800416 ds = dev_server.ImageServer.resolve(self.hostname)
J. Richard Barnette84895392015-04-30 12:31:01 -0700417 url = ds.get_update_url(target_build)
beeps5e8c45a2013-12-17 22:05:11 -0800418
419 updater = autoupdater.ChromiumOSUpdater(update_url=url, host=self)
Richard Barnette3a7697f2016-04-20 11:33:27 -0700420 status, current_build_number = self._check_for_reboot(updater)
421 update_pending = True
beeps5e8c45a2013-12-17 22:05:11 -0800422 if status in autoupdater.UPDATER_PROCESSING_UPDATE:
423 logging.info('servo host %s already processing an update, update '
424 'engine client status=%s', self.hostname, status)
J. Richard Barnette84895392015-04-30 12:31:01 -0700425 elif current_build_number != target_build_number:
beeps5e8c45a2013-12-17 22:05:11 -0800426 logging.info('Using devserver url: %s to trigger update on '
427 'servo host %s, from %s to %s', url, self.hostname,
J. Richard Barnette84895392015-04-30 12:31:01 -0700428 current_build_number, target_build_number)
beeps5e8c45a2013-12-17 22:05:11 -0800429 try:
J. Richard Barnette84895392015-04-30 12:31:01 -0700430 ds.stage_artifacts(target_build,
431 artifacts=['full_payload'])
432 except Exception as e:
433 logging.error('Staging artifacts failed: %s', str(e))
434 logging.error('Abandoning update for this cycle.')
beeps5e8c45a2013-12-17 22:05:11 -0800435 else:
J. Richard Barnette84895392015-04-30 12:31:01 -0700436 try:
Richard Barnette7e53aa02016-05-20 10:49:40 -0700437 # TODO(jrbarnette): This 'touch' is a gross hack
438 # to get us past crbug.com/613603. Once that
439 # bug is resolved, we should remove this code.
440 self.run('touch /home/chronos/.oobe_completed')
J. Richard Barnette84895392015-04-30 12:31:01 -0700441 updater.trigger_update()
442 except autoupdater.RootFSUpdateError as e:
443 trigger_download_status = 'failed with %s' % str(e)
444 autotest_stats.Counter(
445 'servo_host.RootFSUpdateError').increment()
446 else:
447 trigger_download_status = 'passed'
448 logging.info('Triggered download and update %s for %s, '
449 'update engine currently in status %s',
450 trigger_download_status, self.hostname,
451 updater.check_update_status())
beeps5e8c45a2013-12-17 22:05:11 -0800452 else:
453 logging.info('servo host %s does not require an update.',
454 self.hostname)
Richard Barnette3a7697f2016-04-20 11:33:27 -0700455 update_pending = False
456
457 if update_pending and wait_for_update:
458 logging.info('Waiting for servo update to complete.')
459 self.run('update_engine_client --follow', ignore_status=True)
beeps5e8c45a2013-12-17 22:05:11 -0800460
461
Richard Barnette9a26ad62016-06-10 12:03:08 -0700462 def verify(self):
463 """Update the servo host and verify it's in a good state."""
Richard Barnette79d78c42016-05-25 09:31:21 -0700464 # TODO(jrbarnette) Old versions of beaglebone_servo include
Richard Barnette9a26ad62016-06-10 12:03:08 -0700465 # the powerd package. If you touch the .oobe_completed file
466 # (as we do to work around an update_engine problem), then
467 # powerd will eventually shut down the beaglebone for lack
468 # of (apparent) activity. Current versions of
Richard Barnette79d78c42016-05-25 09:31:21 -0700469 # beaglebone_servo don't have powerd, but until we can purge
470 # the lab of the old images, we need to make sure powerd
471 # isn't running.
472 self.run('stop powerd', ignore_status=True)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700473 try:
474 self._repair_strategy.verify(self)
475 except:
476 self.disconnect_servo()
477 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700478
479
Richard Barnette9a26ad62016-06-10 12:03:08 -0700480 def repair(self):
481 """Attempt to repair servo host."""
482 try:
483 self._repair_strategy.repair(self)
484 except:
485 self.disconnect_servo()
486 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700487
488
Fang Dengd4fe7392013-09-20 12:18:21 -0700489 def has_power(self):
490 """Return whether or not the servo host is powered by PoE."""
491 # TODO(fdeng): See crbug.com/302791
492 # For now, assume all servo hosts in the lab have power.
493 return self.is_in_lab()
494
495
496 def power_cycle(self):
497 """Cycle power to this host via PoE if it is a lab device.
498
Richard Barnette9a26ad62016-06-10 12:03:08 -0700499 @raises AutoservRepairError if it fails to power cycle the
Fang Dengd4fe7392013-09-20 12:18:21 -0700500 servo host.
501
502 """
503 if self.has_power():
504 try:
505 rpm_client.set_power(self.hostname, 'CYCLE')
506 except (socket.error, xmlrpclib.Error,
507 httplib.BadStatusLine,
508 rpm_client.RemotePowerException) as e:
Richard Barnette9a26ad62016-06-10 12:03:08 -0700509 raise hosts.AutoservRepairError(
Fang Dengd4fe7392013-09-20 12:18:21 -0700510 'Power cycling %s failed: %s' % (self.hostname, e))
511 else:
512 logging.info('Skipping power cycling, not a lab device.')
513
514
Dan Shi4d478522014-02-14 13:46:32 -0800515 def get_servo(self):
516 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700517
Dan Shi4d478522014-02-14 13:46:32 -0800518 @return: a servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700519 """
Dan Shi4d478522014-02-14 13:46:32 -0800520 return self._servo
521
522
Richard Barnetteea3e4602016-06-10 12:36:41 -0700523def make_servo_hostname(dut_hostname):
524 """Given a DUT's hostname, return the hostname of its servo.
525
526 @param dut_hostname: hostname of a DUT.
527
528 @return hostname of the DUT's servo.
529
530 """
531 host_parts = dut_hostname.split('.')
532 host_parts[0] = host_parts[0] + '-servo'
533 return '.'.join(host_parts)
534
535
536def servo_host_is_up(servo_hostname):
537 """
538 Given a servo host name, return if it's up or not.
539
540 @param servo_hostname: hostname of the servo host.
541
542 @return True if it's up, False otherwise
543 """
544 # Technically, this duplicates the SSH ping done early in the servo
545 # proxy initialization code. However, this ping ends in a couple
546 # seconds when if fails, rather than the 60 seconds it takes to decide
547 # that an SSH ping has timed out. Specifically, that timeout happens
548 # when our servo DNS name resolves, but there is no host at that IP.
549 logging.info('Pinging servo host at %s', servo_hostname)
550 ping_config = ping_runner.PingConfig(
551 servo_hostname, count=3,
552 ignore_result=True, ignore_status=True)
553 return ping_runner.PingRunner().ping(ping_config).received > 0
554
555
Richard Barnettee519dcd2016-08-15 17:37:17 -0700556def _map_afe_board_to_servo_board(afe_board):
557 """Map a board we get from the AFE to a servo appropriate value.
558
559 Many boards are identical to other boards for servo's purposes.
560 This function makes that mapping.
561
562 @param afe_board string board name received from AFE.
563 @return board we expect servo to have.
564
565 """
566 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
567 BOARD_MAP = {'gizmo': 'panther'}
568 mapped_board = afe_board
569 if afe_board in BOARD_MAP:
570 mapped_board = BOARD_MAP[afe_board]
571 else:
572 for suffix in KNOWN_SUFFIXES:
573 if afe_board.endswith(suffix):
574 mapped_board = afe_board[0:-len(suffix)]
575 break
576 if mapped_board != afe_board:
577 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
578 return mapped_board
579
580
Richard Barnetteea3e4602016-06-10 12:36:41 -0700581def _get_standard_servo_args(dut_host):
582 """
583 Return servo data associated with a given DUT.
584
585 This checks for the presence of servo host and port attached to the
586 given `dut_host`. This data should be stored in the
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700587 `_afe_host.attributes` field in the provided `dut_host` parameter.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700588
589 @param dut_host Instance of `Host` on which to find the servo
590 attributes.
591 @return A tuple of `servo_args` dict with host and an option port,
592 plus an `is_in_lab` flag indicating whether this in the CrOS
593 test lab, or some different environment.
594 """
595 servo_args = None
596 is_in_lab = False
597 is_ssp_moblab = False
598 if utils.is_in_container():
599 is_moblab = _CONFIG.get_config_value(
600 'SSP', 'is_moblab', type=bool, default=False)
601 is_ssp_moblab = is_moblab
602 else:
603 is_moblab = utils.is_moblab()
Kevin Cheng05ae2a42016-06-06 10:12:48 -0700604 attrs = dut_host._afe_host.attributes
Richard Barnetteea3e4602016-06-10 12:36:41 -0700605 if attrs and SERVO_HOST_ATTR in attrs:
606 servo_host = attrs[SERVO_HOST_ATTR]
607 if (is_ssp_moblab and servo_host in ['localhost', '127.0.0.1']):
608 servo_host = _CONFIG.get_config_value(
609 'SSP', 'host_container_ip', type=str, default=None)
610 servo_args = {SERVO_HOST_ATTR: servo_host}
611 if SERVO_PORT_ATTR in attrs:
Kevin Cheng692e5292016-08-14 00:23:24 -0700612 try:
613 servo_port = attrs[SERVO_PORT_ATTR]
614 servo_args[SERVO_PORT_ATTR] = int(servo_port)
615 except ValueError:
616 logging.error('servo port is not an int: %s', servo_port)
617 # Let's set the servo args to None since we're not creating
618 # the ServoHost object with the proper port now.
619 servo_args = None
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700620 if SERVO_SERIAL_ATTR in attrs:
621 servo_args[SERVO_SERIAL_ATTR] = attrs[SERVO_SERIAL_ATTR]
Richard Barnetteea3e4602016-06-10 12:36:41 -0700622 is_in_lab = (not is_moblab
623 and utils.host_is_in_lab_zone(servo_host))
624
625 # TODO(jrbarnette): This test to use the default lab servo hostname
626 # is a legacy that we need only until every host in the DB has
627 # proper attributes.
628 elif (not is_moblab and
629 not dnsname_mangler.is_ip_address(dut_host.hostname)):
630 servo_host = make_servo_hostname(dut_host.hostname)
631 is_in_lab = utils.host_is_in_lab_zone(servo_host)
632 if is_in_lab:
633 servo_args = {SERVO_HOST_ATTR: servo_host}
Richard Barnette9a26ad62016-06-10 12:03:08 -0700634 if servo_args is not None:
635 servo_board = afe_utils.get_board(dut_host)
636 if servo_board is not None:
637 servo_board = _map_afe_board_to_servo_board(servo_board)
638 servo_args[SERVO_BOARD_ATTR] = servo_board
Richard Barnetteea3e4602016-06-10 12:36:41 -0700639 return servo_args, is_in_lab
640
641
Dan Shi023aae32016-05-25 11:13:01 -0700642def create_servo_host(dut, servo_args, try_lab_servo=False,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700643 try_servo_repair=False):
Richard Barnetteea3e4602016-06-10 12:36:41 -0700644 """
645 Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -0800646
Richard Barnette9a26ad62016-06-10 12:03:08 -0700647 This function attempts to create and verify or repair a `ServoHost`
648 object for a servo connected to the given `dut`, subject to various
649 constraints imposed by the parameters:
650 * When the `servo_args` parameter is not `None`, a servo
651 host must be created, and must be checked with `repair()`.
652 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
653 true:
654 * If `try_servo_repair` is true, then create a servo host and
655 check it with `repair()`.
656 * Otherwise, if the servo responds to `ping` then create a
657 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800658
Richard Barnette9a26ad62016-06-10 12:03:08 -0700659 In cases where `servo_args` was not `None`, repair failure
660 exceptions are passed back to the caller; otherwise, exceptions
661 are logged and then discarded.
662
663 Parameters for a servo host consist of a host name, port number, and
664 DUT board, and are determined from one of these sources, in order of
665 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -0700666 * Servo attributes from the `dut` parameter take precedence over
667 all other sources of information.
668 * If a DNS entry for the servo based on the DUT hostname exists in
669 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -0700670 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700671 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -0700672 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700673
674 @param dut An instance of `Host` from which to take
675 servo parameters (if available).
676 @param servo_args A dictionary with servo parameters to use if
677 they can't be found from `dut`. If this
678 argument is supplied, unrepaired exceptions
679 from `verify()` will be passed back to the
680 caller.
681 @param try_lab_servo If not true, servo host creation will be
682 skipped unless otherwise required by the
683 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700684 @param try_servo_repair If true, check a servo host with
685 `repair()` instead of `verify()`.
Dan Shi4d478522014-02-14 13:46:32 -0800686
687 @returns: A ServoHost object or None. See comments above.
688
689 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700690 require_repair = servo_args is not None
Richard Barnetteea3e4602016-06-10 12:36:41 -0700691 is_in_lab = False
Richard Barnette9a26ad62016-06-10 12:03:08 -0700692 if dut is not None and (try_lab_servo or require_repair):
Richard Barnetteea3e4602016-06-10 12:36:41 -0700693 servo_args_override, is_in_lab = _get_standard_servo_args(dut)
694 if servo_args_override is not None:
695 servo_args = servo_args_override
696 if servo_args is None:
697 return None
Richard Barnette9a26ad62016-06-10 12:03:08 -0700698 if (not require_repair and not try_servo_repair and
699 not servo_host_is_up(servo_args[SERVO_HOST_ATTR])):
Dan Shibbb0cb62014-03-24 17:50:57 -0700700 return None
Richard Barnette9a26ad62016-06-10 12:03:08 -0700701 newhost = ServoHost(is_in_lab=is_in_lab, **servo_args)
702 # Note that the logic of repair() includes everything done
703 # by verify(). It's sufficient to call one or the other;
704 # we don't need both.
705 if require_repair:
706 newhost.repair()
707 else:
708 try:
709 if try_servo_repair:
710 newhost.repair()
711 else:
712 newhost.verify()
713 except Exception as e:
714 operation = 'repair' if try_servo_repair else 'verification'
715 logging.exception('Servo %s failed for %s',
716 operation, newhost.hostname)
717 return newhost