blob: 811ef4dd6d495f5810ecd3e868147665df580ecf [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
Fang Deng5d518f42013-08-02 14:04:32 -070012import logging
Fang Deng5d518f42013-08-02 14:04:32 -070013import xmlrpclib
Raul E Rangel52ca2e82018-07-03 14:10:14 -060014import os
Fang Deng5d518f42013-08-02 14:04:32 -070015
16from autotest_lib.client.bin import utils
beeps5e8c45a2013-12-17 22:05:11 -080017from autotest_lib.client.common_lib import global_config
Richard Barnette9a26ad62016-06-10 12:03:08 -070018from autotest_lib.client.common_lib import hosts
Fang Deng5d518f42013-08-02 14:04:32 -070019from autotest_lib.client.common_lib.cros import retry
Christopher Wileycef1f902014-06-19 11:11:23 -070020from autotest_lib.client.common_lib.cros.network import ping_runner
Richard Barnette9a26ad62016-06-10 12:03:08 -070021from autotest_lib.server.cros.servo import servo
Richard Barnetted31580e2018-05-14 19:58:00 +000022from autotest_lib.server.hosts import servo_repair
Garry Wangebc015b2019-06-06 17:45:06 -070023from autotest_lib.server.hosts import base_servohost
Dan Shi5e2efb72017-02-07 11:40:23 -080024
Fang Deng5d518f42013-08-02 14:04:32 -070025
Simran Basi0739d682015-02-25 16:22:56 -080026# Names of the host attributes in the database that represent the values for
27# the servo_host and servo_port for a servo connected to the DUT.
28SERVO_HOST_ATTR = 'servo_host'
29SERVO_PORT_ATTR = 'servo_port'
Richard Barnettee519dcd2016-08-15 17:37:17 -070030SERVO_BOARD_ATTR = 'servo_board'
Nick Sanders2f3c9852018-10-24 12:10:24 -070031# Model is inferred from host labels.
32SERVO_MODEL_ATTR = 'servo_model'
Kevin Cheng643ce8a2016-09-15 15:42:12 -070033SERVO_SERIAL_ATTR = 'servo_serial'
Prathmesh Prabhucba44292018-08-28 17:44:45 -070034SERVO_ATTR_KEYS = (
35 SERVO_BOARD_ATTR,
36 SERVO_HOST_ATTR,
37 SERVO_PORT_ATTR,
38 SERVO_SERIAL_ATTR,
39)
Simran Basi0739d682015-02-25 16:22:56 -080040
Dan Shi3b2adf62015-09-02 17:46:54 -070041_CONFIG = global_config.global_config
xixuan6cf6d2f2016-01-29 15:29:00 -080042ENABLE_SSH_TUNNEL_FOR_SERVO = _CONFIG.get_config_value(
43 'CROS', 'enable_ssh_tunnel_for_servo', type=bool, default=False)
Simran Basi0739d682015-02-25 16:22:56 -080044
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070045AUTOTEST_BASE = _CONFIG.get_config_value(
46 'SCHEDULER', 'drone_installation_directory',
47 default='/usr/local/autotest')
48
Fang Deng5d518f42013-08-02 14:04:32 -070049
Garry Wangebc015b2019-06-06 17:45:06 -070050class ServoHost(base_servohost.BaseServoHost):
51 """Host class for a servo host(e.g. beaglebone, labstation)
52 that with a servo instance for a specific port."""
Fang Deng5d518f42013-08-02 14:04:32 -070053
Raul E Rangel52ca2e82018-07-03 14:10:14 -060054 DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999'))
Richard Barnette9a26ad62016-06-10 12:03:08 -070055
Dan Shie5b3c512014-08-21 12:12:09 -070056 # Timeout for initializing servo signals.
Wai-Hong Tam37b6ed32017-09-19 15:52:39 -070057 INITIALIZE_SERVO_TIMEOUT_SECS = 60
Richard Barnette9a26ad62016-06-10 12:03:08 -070058
xixuan6cf6d2f2016-01-29 15:29:00 -080059 # Ready test function
60 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070061
62
Richard Barnette17bfc6c2016-08-04 18:41:43 -070063 def _initialize(self, servo_host='localhost',
Richard Barnettee519dcd2016-08-15 17:37:17 -070064 servo_port=DEFAULT_PORT, servo_board=None,
Nick Sanders2f3c9852018-10-24 12:10:24 -070065 servo_model=None, servo_serial=None, is_in_lab=None,
66 *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.
Raul E Rangel52ca2e82018-07-03 14:10:14 -060073 @param servo_port: Port the servod process is listening on. Defaults
74 to the SERVOD_PORT environment variable if set,
75 otherwise 9999.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070076 @param servo_board: Board that the servo is connected to.
Nick Sanders2f3c9852018-10-24 12:10:24 -070077 @param servo_model: Model that the servo is connected to.
Dan Shi4d478522014-02-14 13:46:32 -080078 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
79 to None, for which utils.host_is_in_lab_zone will be
80 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -070081
82 """
83 super(ServoHost, self)._initialize(hostname=servo_host,
Garry Wangebc015b2019-06-06 17:45:06 -070084 is_in_lab=is_in_lab, *args, **dargs)
Richard Barnette42f4db92018-08-23 15:05:15 -070085 self.servo_port = int(servo_port)
Richard Barnettee519dcd2016-08-15 17:37:17 -070086 self.servo_board = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -070087 self.servo_model = servo_model
Kevin Cheng643ce8a2016-09-15 15:42:12 -070088 self.servo_serial = servo_serial
Richard Barnettee519dcd2016-08-15 17:37:17 -070089 self._servo = None
Garry Wangebc015b2019-06-06 17:45:06 -070090
Richard Barnette9a26ad62016-06-10 12:03:08 -070091 self._repair_strategy = (
92 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -070093
Richard Barnette9a26ad62016-06-10 12:03:08 -070094
95 def connect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070096 """Establish a connection to the servod server on this host.
Richard Barnette9a26ad62016-06-10 12:03:08 -070097
98 Initializes `self._servo` and then verifies that all network
99 connections are working. This will create an ssh tunnel if
100 it's required.
101
102 As a side effect of testing the connection, all signals on the
103 target servo are reset to default values, and the USB stick is
104 set to the neutral (off) position.
105 """
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700106 servo_obj = servo.Servo(servo_host=self, servo_serial=self.servo_serial)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700107 timeout, _ = retry.timeout(
108 servo_obj.initialize_dut,
109 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
110 if timeout:
111 raise hosts.AutoservVerifyError(
112 'Servo initialize timed out.')
113 self._servo = servo_obj
114
115
116 def disconnect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700117 """Disconnect our servo if it exists.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700118
119 If we've previously successfully connected to our servo,
120 disconnect any established ssh tunnel, and set `self._servo`
121 back to `None`.
122 """
123 if self._servo:
124 # N.B. This call is safe even without a tunnel:
125 # rpc_server_tracker.disconnect() silently ignores
126 # unknown ports.
127 self.rpc_server_tracker.disconnect(self.servo_port)
128 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700129
130
Fang Deng5d518f42013-08-02 14:04:32 -0700131 def get_servod_server_proxy(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700132 """Return a proxy that can be used to communicate with servod server.
Fang Deng5d518f42013-08-02 14:04:32 -0700133
134 @returns: An xmlrpclib.ServerProxy that is connected to the servod
135 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700136 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700137 if ENABLE_SSH_TUNNEL_FOR_SERVO and not self.is_localhost():
138 return self.rpc_server_tracker.xmlrpc_connect(
139 None, self.servo_port,
140 ready_test_name=self.SERVO_READY_METHOD,
Allen Li2b1a8992018-11-27 14:17:18 -0800141 timeout_seconds=60,
Allen Li556f4532018-12-03 18:11:23 -0800142 request_timeout_seconds=3600)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700143 else:
144 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
145 return xmlrpclib.ServerProxy(remote)
Fang Deng5d518f42013-08-02 14:04:32 -0700146
147
Richard Barnette1edbb162016-11-01 11:47:50 -0700148 def verify(self, silent=False):
149 """Update the servo host and verify it's in a good state.
150
151 @param silent If true, suppress logging in `status.log`.
152 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700153 message = 'Beginning verify for servo host %s port %s serial %s'
154 message %= (self.hostname, self.servo_port, self.servo_serial)
155 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700156 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700157 self._repair_strategy.verify(self, silent)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700158 except:
159 self.disconnect_servo()
160 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700161
162
Richard Barnette1edbb162016-11-01 11:47:50 -0700163 def repair(self, silent=False):
164 """Attempt to repair servo host.
165
166 @param silent If true, suppress logging in `status.log`.
167 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700168 message = 'Beginning repair for servo host %s port %s serial %s'
169 message %= (self.hostname, self.servo_port, self.servo_serial)
170 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700171 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700172 self._repair_strategy.repair(self, silent)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700173 except:
174 self.disconnect_servo()
175 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700176
177
Dan Shi4d478522014-02-14 13:46:32 -0800178 def get_servo(self):
179 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700180
Dan Shi4d478522014-02-14 13:46:32 -0800181 @return: a servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700182 """
Dan Shi4d478522014-02-14 13:46:32 -0800183 return self._servo
184
185
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700186 def close(self):
Congbin Guofc3b8962019-03-22 17:38:46 -0700187 """Close the associated servo and the host object."""
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700188 if self._servo:
Congbin Guo2e5e2a22018-07-27 10:32:48 -0700189 # In some cases when we run as lab-tools, the job object is None.
Congbin Guofc3b8962019-03-22 17:38:46 -0700190 if self.job and not self._servo.uart_logs_dir:
191 self._servo.uart_logs_dir = self.job.resultdir
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700192 self._servo.close()
193
194 super(ServoHost, self).close()
195
196
Richard Barnetteea3e4602016-06-10 12:36:41 -0700197def make_servo_hostname(dut_hostname):
198 """Given a DUT's hostname, return the hostname of its servo.
199
200 @param dut_hostname: hostname of a DUT.
201
202 @return hostname of the DUT's servo.
203
204 """
205 host_parts = dut_hostname.split('.')
206 host_parts[0] = host_parts[0] + '-servo'
207 return '.'.join(host_parts)
208
209
210def servo_host_is_up(servo_hostname):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700211 """Given a servo host name, return if it's up or not.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700212
213 @param servo_hostname: hostname of the servo host.
214
215 @return True if it's up, False otherwise
216 """
217 # Technically, this duplicates the SSH ping done early in the servo
218 # proxy initialization code. However, this ping ends in a couple
219 # seconds when if fails, rather than the 60 seconds it takes to decide
220 # that an SSH ping has timed out. Specifically, that timeout happens
221 # when our servo DNS name resolves, but there is no host at that IP.
222 logging.info('Pinging servo host at %s', servo_hostname)
223 ping_config = ping_runner.PingConfig(
224 servo_hostname, count=3,
225 ignore_result=True, ignore_status=True)
226 return ping_runner.PingRunner().ping(ping_config).received > 0
227
228
Richard Barnettee519dcd2016-08-15 17:37:17 -0700229def _map_afe_board_to_servo_board(afe_board):
230 """Map a board we get from the AFE to a servo appropriate value.
231
232 Many boards are identical to other boards for servo's purposes.
233 This function makes that mapping.
234
235 @param afe_board string board name received from AFE.
236 @return board we expect servo to have.
237
238 """
239 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
240 BOARD_MAP = {'gizmo': 'panther'}
241 mapped_board = afe_board
242 if afe_board in BOARD_MAP:
243 mapped_board = BOARD_MAP[afe_board]
244 else:
245 for suffix in KNOWN_SUFFIXES:
246 if afe_board.endswith(suffix):
247 mapped_board = afe_board[0:-len(suffix)]
248 break
249 if mapped_board != afe_board:
250 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
251 return mapped_board
252
253
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700254def get_servo_args_for_host(dut_host):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700255 """Return servo data associated with a given DUT.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700256
Richard Barnetteea3e4602016-06-10 12:36:41 -0700257 @param dut_host Instance of `Host` on which to find the servo
258 attributes.
Prathmesh Prabhuf605dd32018-08-28 17:09:04 -0700259 @return `servo_args` dict with host and an optional port.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700260 """
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700261 info = dut_host.host_info_store.get()
262 servo_args = {k: v for k, v in info.attributes.iteritems()
263 if k in SERVO_ATTR_KEYS}
Richard Barnetteea3e4602016-06-10 12:36:41 -0700264
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700265 if SERVO_PORT_ATTR in servo_args:
266 try:
267 servo_args[SERVO_PORT_ATTR] = int(servo_args[SERVO_PORT_ATTR])
268 except ValueError:
269 logging.error('servo port is not an int: %s',
270 servo_args[SERVO_PORT_ATTR])
271 # Reset servo_args because we don't want to use an invalid port.
272 servo_args.pop(SERVO_HOST_ATTR, None)
273
274 if info.board:
275 servo_args[SERVO_BOARD_ATTR] = _map_afe_board_to_servo_board(info.board)
Nick Sanders2f3c9852018-10-24 12:10:24 -0700276 if info.model:
277 servo_args[SERVO_MODEL_ATTR] = info.model
Prathmesh Prabhu6f5f6362018-09-05 17:20:31 -0700278 return servo_args if SERVO_HOST_ATTR in servo_args else None
Richard Barnetteea3e4602016-06-10 12:36:41 -0700279
280
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700281def _tweak_args_for_ssp_moblab(servo_args):
282 if servo_args[SERVO_HOST_ATTR] in ['localhost', '127.0.0.1']:
283 servo_args[SERVO_HOST_ATTR] = _CONFIG.get_config_value(
284 'SSP', 'host_container_ip', type=str, default=None)
285
286
Dan Shi023aae32016-05-25 11:13:01 -0700287def create_servo_host(dut, servo_args, try_lab_servo=False,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700288 try_servo_repair=False):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700289 """Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -0800290
Richard Barnette9a26ad62016-06-10 12:03:08 -0700291 This function attempts to create and verify or repair a `ServoHost`
292 object for a servo connected to the given `dut`, subject to various
293 constraints imposed by the parameters:
294 * When the `servo_args` parameter is not `None`, a servo
295 host must be created, and must be checked with `repair()`.
296 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
297 true:
298 * If `try_servo_repair` is true, then create a servo host and
299 check it with `repair()`.
300 * Otherwise, if the servo responds to `ping` then create a
301 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800302
Richard Barnette9a26ad62016-06-10 12:03:08 -0700303 In cases where `servo_args` was not `None`, repair failure
304 exceptions are passed back to the caller; otherwise, exceptions
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700305 are logged and then discarded. Note that this only happens in cases
306 where we're called from a test (not special task) control file that
307 has an explicit dependency on servo. In that case, we require that
308 repair not write to `status.log`, so as to avoid polluting test
309 results.
310
311 TODO(jrbarnette): The special handling for servo in test control
312 files is a thorn in my flesh; I dearly hope to see it cut out before
313 my retirement.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700314
315 Parameters for a servo host consist of a host name, port number, and
316 DUT board, and are determined from one of these sources, in order of
317 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -0700318 * Servo attributes from the `dut` parameter take precedence over
319 all other sources of information.
320 * If a DNS entry for the servo based on the DUT hostname exists in
321 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -0700322 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700323 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -0700324 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700325
326 @param dut An instance of `Host` from which to take
327 servo parameters (if available).
328 @param servo_args A dictionary with servo parameters to use if
329 they can't be found from `dut`. If this
330 argument is supplied, unrepaired exceptions
331 from `verify()` will be passed back to the
332 caller.
333 @param try_lab_servo If not true, servo host creation will be
334 skipped unless otherwise required by the
335 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700336 @param try_servo_repair If true, check a servo host with
337 `repair()` instead of `verify()`.
Dan Shi4d478522014-02-14 13:46:32 -0800338
339 @returns: A ServoHost object or None. See comments above.
340
341 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700342 servo_dependency = servo_args is not None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700343 if dut is not None and (try_lab_servo or servo_dependency):
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700344 servo_args_override = get_servo_args_for_host(dut)
Richard Barnetteea3e4602016-06-10 12:36:41 -0700345 if servo_args_override is not None:
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700346 if utils.in_moblab_ssp():
347 _tweak_args_for_ssp_moblab(servo_args_override)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700348 logging.debug(
349 'Overriding provided servo_args (%s) with arguments'
350 ' determined from the host (%s)',
351 servo_args,
352 servo_args_override,
353 )
Richard Barnetteea3e4602016-06-10 12:36:41 -0700354 servo_args = servo_args_override
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700355
Richard Barnetteea3e4602016-06-10 12:36:41 -0700356 if servo_args is None:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700357 logging.debug('No servo_args provided, and failed to find overrides.')
Richard Barnetteea3e4602016-06-10 12:36:41 -0700358 return None
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700359 if SERVO_HOST_ATTR not in servo_args:
360 logging.debug('%s attribute missing from servo_args: %s',
361 SERVO_HOST_ATTR, servo_args)
362 return None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700363 if (not servo_dependency and not try_servo_repair and
Richard Barnette9a26ad62016-06-10 12:03:08 -0700364 not servo_host_is_up(servo_args[SERVO_HOST_ATTR])):
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700365 logging.debug('ServoHost is not up.')
Dan Shibbb0cb62014-03-24 17:50:57 -0700366 return None
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700367
Garry Wangebc015b2019-06-06 17:45:06 -0700368 newhost = ServoHost(**servo_args)
369
Richard Barnette9a26ad62016-06-10 12:03:08 -0700370 # Note that the logic of repair() includes everything done
371 # by verify(). It's sufficient to call one or the other;
372 # we don't need both.
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700373 if servo_dependency:
374 newhost.repair(silent=True)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700375 return newhost
376
377 if try_servo_repair:
378 try:
379 newhost.repair()
380 except Exception:
381 logging.exception('servo repair failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700382 else:
383 try:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700384 newhost.verify()
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700385 except Exception:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700386 logging.exception('servo verify failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700387 return newhost