blob: f479215cd9ff888bb396a00ec339151403508a11 [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
Raul E Rangel52ca2e82018-07-03 14:10:14 -060013import os
Garry Wangc1288cf2019-12-17 14:58:00 -080014import time
Gregory Nisbet265a52c2019-12-10 20:38:42 -080015import traceback
Dana Goyette4dc0adc2019-05-06 14:51:53 -070016import xmlrpclib
Fang Deng5d518f42013-08-02 14:04:32 -070017
18from autotest_lib.client.bin import utils
Garry Wang79e9af62019-06-12 15:19:19 -070019from autotest_lib.client.common_lib import error
beeps5e8c45a2013-12-17 22:05:11 -080020from autotest_lib.client.common_lib import global_config
Richard Barnette9a26ad62016-06-10 12:03:08 -070021from autotest_lib.client.common_lib import hosts
Fang Deng5d518f42013-08-02 14:04:32 -070022from autotest_lib.client.common_lib.cros import retry
Christopher Wileycef1f902014-06-19 11:11:23 -070023from autotest_lib.client.common_lib.cros.network import ping_runner
Richard Barnette9a26ad62016-06-10 12:03:08 -070024from autotest_lib.server.cros.servo import servo
Richard Barnetted31580e2018-05-14 19:58:00 +000025from autotest_lib.server.hosts import servo_repair
Garry Wangebc015b2019-06-06 17:45:06 -070026from autotest_lib.server.hosts import base_servohost
Dan Shi5e2efb72017-02-07 11:40:23 -080027
Fang Deng5d518f42013-08-02 14:04:32 -070028
Simran Basi0739d682015-02-25 16:22:56 -080029# Names of the host attributes in the database that represent the values for
30# the servo_host and servo_port for a servo connected to the DUT.
31SERVO_HOST_ATTR = 'servo_host'
32SERVO_PORT_ATTR = 'servo_port'
Richard Barnettee519dcd2016-08-15 17:37:17 -070033SERVO_BOARD_ATTR = 'servo_board'
Nick Sanders2f3c9852018-10-24 12:10:24 -070034# Model is inferred from host labels.
35SERVO_MODEL_ATTR = 'servo_model'
Kevin Cheng643ce8a2016-09-15 15:42:12 -070036SERVO_SERIAL_ATTR = 'servo_serial'
Prathmesh Prabhucba44292018-08-28 17:44:45 -070037SERVO_ATTR_KEYS = (
38 SERVO_BOARD_ATTR,
39 SERVO_HOST_ATTR,
40 SERVO_PORT_ATTR,
41 SERVO_SERIAL_ATTR,
42)
Simran Basi0739d682015-02-25 16:22:56 -080043
Garry Wangc1288cf2019-12-17 14:58:00 -080044# Timeout value for stop/start servod process.
45SERVOD_TEARDOWN_TIMEOUT = 3
46SERVOD_QUICK_STARTUP_TIMEOUT = 20
47SERVOD_STARTUP_TIMEOUT = 60
48
Dan Shi3b2adf62015-09-02 17:46:54 -070049_CONFIG = global_config.global_config
xixuan6cf6d2f2016-01-29 15:29:00 -080050ENABLE_SSH_TUNNEL_FOR_SERVO = _CONFIG.get_config_value(
51 'CROS', 'enable_ssh_tunnel_for_servo', type=bool, default=False)
Simran Basi0739d682015-02-25 16:22:56 -080052
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070053AUTOTEST_BASE = _CONFIG.get_config_value(
54 'SCHEDULER', 'drone_installation_directory',
55 default='/usr/local/autotest')
56
Otabek Kasimovcc9738e2020-02-14 16:17:15 -080057SERVO_STATE_LABEL_PREFIX = 'servo_state'
58SERVO_STATE_WORKING = 'WORKING'
59SERVO_STATE_BROKEN = 'BROKEN'
60
Fang Deng5d518f42013-08-02 14:04:32 -070061
Garry Wangebc015b2019-06-06 17:45:06 -070062class ServoHost(base_servohost.BaseServoHost):
63 """Host class for a servo host(e.g. beaglebone, labstation)
Dana Goyette0b6e6402019-10-04 11:09:24 -070064 that with a servo instance for a specific port.
65
66 @type _servo: servo.Servo | None
67 """
Fang Deng5d518f42013-08-02 14:04:32 -070068
Raul E Rangel52ca2e82018-07-03 14:10:14 -060069 DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999'))
Richard Barnette9a26ad62016-06-10 12:03:08 -070070
Dan Shie5b3c512014-08-21 12:12:09 -070071 # Timeout for initializing servo signals.
Wai-Hong Tam37b6ed32017-09-19 15:52:39 -070072 INITIALIZE_SERVO_TIMEOUT_SECS = 60
Richard Barnette9a26ad62016-06-10 12:03:08 -070073
xixuan6cf6d2f2016-01-29 15:29:00 -080074 # Ready test function
75 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070076
Otabek Kasimovcc9738e2020-02-14 16:17:15 -080077 def _init_attributes(self):
78 self._servo_state = None
79 self.servo_port = None
80 self.servo_board = None
81 self.servo_model = None
82 self.servo_serial = None
83 self._servo = None
84 self._servod_server_proxy = None
85
Fang Deng5d518f42013-08-02 14:04:32 -070086
Richard Barnette17bfc6c2016-08-04 18:41:43 -070087 def _initialize(self, servo_host='localhost',
Richard Barnettee519dcd2016-08-15 17:37:17 -070088 servo_port=DEFAULT_PORT, servo_board=None,
Nick Sanders2f3c9852018-10-24 12:10:24 -070089 servo_model=None, servo_serial=None, is_in_lab=None,
90 *args, **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -070091 """Initialize a ServoHost instance.
92
93 A ServoHost instance represents a host that controls a servo.
94
95 @param servo_host: Name of the host where the servod process
96 is running.
Raul E Rangel52ca2e82018-07-03 14:10:14 -060097 @param servo_port: Port the servod process is listening on. Defaults
98 to the SERVOD_PORT environment variable if set,
99 otherwise 9999.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700100 @param servo_board: Board that the servo is connected to.
Nick Sanders2f3c9852018-10-24 12:10:24 -0700101 @param servo_model: Model that the servo is connected to.
Dan Shi4d478522014-02-14 13:46:32 -0800102 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
103 to None, for which utils.host_is_in_lab_zone will be
104 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -0700105
106 """
107 super(ServoHost, self)._initialize(hostname=servo_host,
Garry Wangebc015b2019-06-06 17:45:06 -0700108 is_in_lab=is_in_lab, *args, **dargs)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800109 self._init_attributes()
Richard Barnette42f4db92018-08-23 15:05:15 -0700110 self.servo_port = int(servo_port)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700111 self.servo_board = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -0700112 self.servo_model = servo_model
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700113 self.servo_serial = servo_serial
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800114
Garry Wang79e9af62019-06-12 15:19:19 -0700115 # Path of the servo host lock file.
116 self._lock_file = (self.TEMP_FILE_DIR + str(self.servo_port)
117 + self.LOCK_FILE_POSTFIX)
118 # File path to declare a reboot request.
119 self._reboot_file = (self.TEMP_FILE_DIR + str(self.servo_port)
120 + self.REBOOT_FILE_POSTFIX)
121
122 # Lock the servo host if it's an in-lab labstation to prevent other
123 # task to reboot it until current task completes. We also wait and
124 # make sure the labstation is up here, in the case of the labstation is
125 # in the middle of reboot.
Garry Wang7c00b0f2019-06-25 17:28:17 -0700126 self._is_locked = False
Garry Wang42b4d862019-06-25 15:50:49 -0700127 if (self.wait_up(self.REBOOT_TIMEOUT) and self.is_in_lab()
128 and self.is_labstation()):
Garry Wang79e9af62019-06-12 15:19:19 -0700129 self._lock()
Garry Wangebc015b2019-06-06 17:45:06 -0700130
Richard Barnette9a26ad62016-06-10 12:03:08 -0700131 self._repair_strategy = (
132 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -0700133
Richard Barnette9a26ad62016-06-10 12:03:08 -0700134 def connect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700135 """Establish a connection to the servod server on this host.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700136
137 Initializes `self._servo` and then verifies that all network
138 connections are working. This will create an ssh tunnel if
139 it's required.
140
141 As a side effect of testing the connection, all signals on the
142 target servo are reset to default values, and the USB stick is
143 set to the neutral (off) position.
144 """
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700145 servo_obj = servo.Servo(servo_host=self, servo_serial=self.servo_serial)
Kuang-che Wu05763f52019-08-30 16:48:21 +0800146 self._servo = servo_obj
Richard Barnette9a26ad62016-06-10 12:03:08 -0700147 timeout, _ = retry.timeout(
148 servo_obj.initialize_dut,
149 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
150 if timeout:
151 raise hosts.AutoservVerifyError(
152 'Servo initialize timed out.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700153
154
155 def disconnect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700156 """Disconnect our servo if it exists.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700157
158 If we've previously successfully connected to our servo,
159 disconnect any established ssh tunnel, and set `self._servo`
160 back to `None`.
161 """
162 if self._servo:
163 # N.B. This call is safe even without a tunnel:
164 # rpc_server_tracker.disconnect() silently ignores
165 # unknown ports.
166 self.rpc_server_tracker.disconnect(self.servo_port)
167 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700168
Garry Wangc1288cf2019-12-17 14:58:00 -0800169
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800170 def _create_servod_server_proxy(self):
171 """Create a proxy that can be used to communicate with servod server.
Fang Deng5d518f42013-08-02 14:04:32 -0700172
173 @returns: An xmlrpclib.ServerProxy that is connected to the servod
174 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700175 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700176 if ENABLE_SSH_TUNNEL_FOR_SERVO and not self.is_localhost():
177 return self.rpc_server_tracker.xmlrpc_connect(
178 None, self.servo_port,
179 ready_test_name=self.SERVO_READY_METHOD,
Allen Li2b1a8992018-11-27 14:17:18 -0800180 timeout_seconds=60,
Allen Li556f4532018-12-03 18:11:23 -0800181 request_timeout_seconds=3600)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700182 else:
183 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
184 return xmlrpclib.ServerProxy(remote)
Fang Deng5d518f42013-08-02 14:04:32 -0700185
186
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800187 def get_servod_server_proxy(self):
188 """Return a cached proxy if exists; otherwise, create a new one.
189
190 @returns: An xmlrpclib.ServerProxy that is connected to the servod
191 server on the host.
192 """
193 # Single-threaded execution, no race
194 if self._servod_server_proxy is None:
195 self._servod_server_proxy = self._create_servod_server_proxy()
196 return self._servod_server_proxy
197
198
Richard Barnette1edbb162016-11-01 11:47:50 -0700199 def verify(self, silent=False):
200 """Update the servo host and verify it's in a good state.
201
202 @param silent If true, suppress logging in `status.log`.
203 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700204 message = 'Beginning verify for servo host %s port %s serial %s'
205 message %= (self.hostname, self.servo_port, self.servo_serial)
206 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700207 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700208 self._repair_strategy.verify(self, silent)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800209 self._servo_state = SERVO_STATE_WORKING
210 self.record('INFO', None, None, 'ServoHost verify set servo_state as WORKING')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700211 except:
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800212 self._servo_state = SERVO_STATE_BROKEN
213 self.record('INFO', None, None, 'ServoHost verify set servo_state as BROKEN')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700214 self.disconnect_servo()
Garry Wangc1288cf2019-12-17 14:58:00 -0800215 self.stop_servod()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700216 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700217
218
Richard Barnette1edbb162016-11-01 11:47:50 -0700219 def repair(self, silent=False):
220 """Attempt to repair servo host.
221
222 @param silent If true, suppress logging in `status.log`.
223 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700224 message = 'Beginning repair for servo host %s port %s serial %s'
225 message %= (self.hostname, self.servo_port, self.servo_serial)
226 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700227 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700228 self._repair_strategy.repair(self, silent)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800229 self._servo_state = SERVO_STATE_WORKING
230 self.record('INFO', None, None, 'ServoHost repair set servo_state as WORKING')
Garry Wang464ff1e2019-07-18 17:20:34 -0700231 # If target is a labstation then try to withdraw any existing
232 # reboot request created by this servo because it passed repair.
233 if self.is_labstation():
234 self.withdraw_reboot_request()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700235 except:
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800236 self._servo_state = SERVO_STATE_BROKEN
237 self.record('INFO', None, None, 'ServoHost repair set servo_state as BROKEN')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700238 self.disconnect_servo()
Garry Wangc1288cf2019-12-17 14:58:00 -0800239 self.stop_servod()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700240 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700241
242
Dan Shi4d478522014-02-14 13:46:32 -0800243 def get_servo(self):
244 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700245
Dan Shi4d478522014-02-14 13:46:32 -0800246 @return: a servo.Servo object.
Dana Goyette353d1d92019-06-27 10:43:59 -0700247 @rtype: autotest_lib.server.cros.servo.servo.Servo
Fang Deng5d518f42013-08-02 14:04:32 -0700248 """
Dan Shi4d478522014-02-14 13:46:32 -0800249 return self._servo
250
251
Garry Wang79e9af62019-06-12 15:19:19 -0700252 def request_reboot(self):
253 """Request servohost to be rebooted when it's safe to by touch a file.
254 """
255 logging.debug('Request to reboot servohost %s has been created by '
Garry Wang464ff1e2019-07-18 17:20:34 -0700256 'servo with port # %s', self.hostname, self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700257 self.run('touch %s' % self._reboot_file, ignore_status=True)
258
259
Garry Wang464ff1e2019-07-18 17:20:34 -0700260 def withdraw_reboot_request(self):
261 """Withdraw a servohost reboot request if exists by remove the flag
262 file.
263 """
264 logging.debug('Withdrawing request to reboot servohost %s that created'
265 ' by servo with port # %s if exists.',
266 self.hostname, self.servo_port)
267 self.run('rm -f %s' % self._reboot_file, ignore_status=True)
268
269
Garry Wangc1288cf2019-12-17 14:58:00 -0800270 def start_servod(self, quick_startup=False):
271 """Start the servod process on servohost.
272 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800273 # Skip if running on the localhost.(crbug.com/1038168)
274 if self.is_localhost():
275 logging.debug("Servohost is a localhost, skipping start servod.")
276 return
277
278 cmd = 'start servod'
Garry Wangc1288cf2019-12-17 14:58:00 -0800279 if self.servo_board:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800280 cmd += ' BOARD=%s' % self.servo_board
Garry Wangc1288cf2019-12-17 14:58:00 -0800281 if self.servo_model:
282 cmd += ' MODEL=%s' % self.servo_model
Garry Wangc1288cf2019-12-17 14:58:00 -0800283 else:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800284 logging.warning('Board for DUT is unknown; starting servod'
285 ' assuming a pre-configured board.')
286
287 cmd += ' PORT=%d' % self.servo_port
288 if self.servo_serial:
289 cmd += ' SERIAL=%s' % self.servo_serial
Garry Wangcdd27b22020-01-13 14:59:11 -0800290 self.run(cmd, timeout=60)
Garry Wangc1288cf2019-12-17 14:58:00 -0800291
292 # There's a lag between when `start servod` completes and when
293 # the _ServodConnectionVerifier trigger can actually succeed.
294 # The call to time.sleep() below gives time to make sure that
295 # the trigger won't fail after we return.
296
297 # Normally servod on servo_v3 and labstation take ~10 seconds to ready,
298 # But in the rare case all servo on a labstation are in heavy use they
299 # may take ~30 seconds. So the timeout value will double these value,
300 # and we'll try quick start up when first time initialize servohost,
301 # and use standard start up timeout in repair.
302 if quick_startup:
303 timeout = SERVOD_QUICK_STARTUP_TIMEOUT
304 else:
305 timeout = SERVOD_STARTUP_TIMEOUT
306 logging.debug('Wait %s seconds for servod process fully up.', timeout)
307 time.sleep(timeout)
308
309
310 def stop_servod(self):
311 """Stop the servod process on servohost.
312 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800313 # Skip if running on the localhost.(crbug.com/1038168)
314 if self.is_localhost():
315 logging.debug("Servohost is a localhost, skipping stop servod.")
316 return
317
Garry Wangc1288cf2019-12-17 14:58:00 -0800318 logging.debug('Stopping servod on port %s', self.servo_port)
Garry Wangcdd27b22020-01-13 14:59:11 -0800319 self.run('stop servod PORT=%d' % self.servo_port,
320 timeout=60, ignore_status=True)
Garry Wangc1288cf2019-12-17 14:58:00 -0800321 logging.debug('Wait %s seconds for servod process fully teardown.',
322 SERVOD_TEARDOWN_TIMEOUT)
323 time.sleep(SERVOD_TEARDOWN_TIMEOUT)
324
325
326 def restart_servod(self, quick_startup=False):
327 """Restart the servod process on servohost.
328 """
329 self.stop_servod()
330 self.start_servod(quick_startup)
331
332
Garry Wang79e9af62019-06-12 15:19:19 -0700333 def _lock(self):
334 """lock servohost by touching a file.
335 """
336 logging.debug('Locking servohost %s by touching %s file',
337 self.hostname, self._lock_file)
338 self.run('touch %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700339 self._is_locked = True
Garry Wang79e9af62019-06-12 15:19:19 -0700340
341
342 def _unlock(self):
343 """Unlock servohost by removing the lock file.
344 """
345 logging.debug('Unlocking servohost by removing %s file',
346 self._lock_file)
347 self.run('rm %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700348 self._is_locked = False
Garry Wang79e9af62019-06-12 15:19:19 -0700349
350
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700351 def close(self):
Congbin Guofc3b8962019-03-22 17:38:46 -0700352 """Close the associated servo and the host object."""
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700353 if self._servo:
Congbin Guo2e5e2a22018-07-27 10:32:48 -0700354 # In some cases when we run as lab-tools, the job object is None.
Congbin Guofc3b8962019-03-22 17:38:46 -0700355 if self.job and not self._servo.uart_logs_dir:
356 self._servo.uart_logs_dir = self.job.resultdir
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700357 self._servo.close()
358
Garry Wang7c00b0f2019-06-25 17:28:17 -0700359 if self._is_locked:
360 # Remove the lock if the servohost has been locked.
Garry Wang79e9af62019-06-12 15:19:19 -0700361 try:
362 self._unlock()
363 except error.AutoservSSHTimeout:
364 logging.error('Unlock servohost failed due to ssh timeout.'
365 ' It may caused by servohost went down during'
366 ' the task.')
367
Garry Wangc1288cf2019-12-17 14:58:00 -0800368 # We want always stop servod after task to minimum the impact of bad
369 # servod process interfere other servods.(see crbug.com/1028665)
Garry Wang4c624bc2020-01-27 16:34:43 -0800370 try:
371 self.stop_servod()
372 except error.AutoservRunError as e:
373 logging.info("Failed to stop servod due to:\n%s\n"
374 "This error is forgived.", str(e))
Garry Wangc1288cf2019-12-17 14:58:00 -0800375
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700376 super(ServoHost, self).close()
377
378
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800379 def get_servo_state(self):
380 return SERVO_STATE_BROKEN if self._servo_state is None else self._servo_state
381
382
Richard Barnetteea3e4602016-06-10 12:36:41 -0700383def make_servo_hostname(dut_hostname):
384 """Given a DUT's hostname, return the hostname of its servo.
385
386 @param dut_hostname: hostname of a DUT.
387
388 @return hostname of the DUT's servo.
389
390 """
391 host_parts = dut_hostname.split('.')
392 host_parts[0] = host_parts[0] + '-servo'
393 return '.'.join(host_parts)
394
395
396def servo_host_is_up(servo_hostname):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700397 """Given a servo host name, return if it's up or not.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700398
399 @param servo_hostname: hostname of the servo host.
400
401 @return True if it's up, False otherwise
402 """
403 # Technically, this duplicates the SSH ping done early in the servo
404 # proxy initialization code. However, this ping ends in a couple
405 # seconds when if fails, rather than the 60 seconds it takes to decide
406 # that an SSH ping has timed out. Specifically, that timeout happens
407 # when our servo DNS name resolves, but there is no host at that IP.
408 logging.info('Pinging servo host at %s', servo_hostname)
409 ping_config = ping_runner.PingConfig(
410 servo_hostname, count=3,
411 ignore_result=True, ignore_status=True)
412 return ping_runner.PingRunner().ping(ping_config).received > 0
413
414
Richard Barnettee519dcd2016-08-15 17:37:17 -0700415def _map_afe_board_to_servo_board(afe_board):
416 """Map a board we get from the AFE to a servo appropriate value.
417
418 Many boards are identical to other boards for servo's purposes.
419 This function makes that mapping.
420
421 @param afe_board string board name received from AFE.
422 @return board we expect servo to have.
423
424 """
425 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
426 BOARD_MAP = {'gizmo': 'panther'}
427 mapped_board = afe_board
428 if afe_board in BOARD_MAP:
429 mapped_board = BOARD_MAP[afe_board]
430 else:
431 for suffix in KNOWN_SUFFIXES:
432 if afe_board.endswith(suffix):
433 mapped_board = afe_board[0:-len(suffix)]
434 break
435 if mapped_board != afe_board:
436 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
437 return mapped_board
438
439
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700440def get_servo_args_for_host(dut_host):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700441 """Return servo data associated with a given DUT.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700442
Richard Barnetteea3e4602016-06-10 12:36:41 -0700443 @param dut_host Instance of `Host` on which to find the servo
444 attributes.
Prathmesh Prabhuf605dd32018-08-28 17:09:04 -0700445 @return `servo_args` dict with host and an optional port.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700446 """
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700447 info = dut_host.host_info_store.get()
448 servo_args = {k: v for k, v in info.attributes.iteritems()
449 if k in SERVO_ATTR_KEYS}
Richard Barnetteea3e4602016-06-10 12:36:41 -0700450
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700451 if SERVO_PORT_ATTR in servo_args:
452 try:
453 servo_args[SERVO_PORT_ATTR] = int(servo_args[SERVO_PORT_ATTR])
454 except ValueError:
455 logging.error('servo port is not an int: %s',
456 servo_args[SERVO_PORT_ATTR])
457 # Reset servo_args because we don't want to use an invalid port.
458 servo_args.pop(SERVO_HOST_ATTR, None)
459
460 if info.board:
461 servo_args[SERVO_BOARD_ATTR] = _map_afe_board_to_servo_board(info.board)
Nick Sanders2f3c9852018-10-24 12:10:24 -0700462 if info.model:
463 servo_args[SERVO_MODEL_ATTR] = info.model
Prathmesh Prabhu6f5f6362018-09-05 17:20:31 -0700464 return servo_args if SERVO_HOST_ATTR in servo_args else None
Richard Barnetteea3e4602016-06-10 12:36:41 -0700465
466
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700467def _tweak_args_for_ssp_moblab(servo_args):
468 if servo_args[SERVO_HOST_ATTR] in ['localhost', '127.0.0.1']:
469 servo_args[SERVO_HOST_ATTR] = _CONFIG.get_config_value(
470 'SSP', 'host_container_ip', type=str, default=None)
471
472
Dan Shi023aae32016-05-25 11:13:01 -0700473def create_servo_host(dut, servo_args, try_lab_servo=False,
Gregory Nisbetde13e2a2019-12-09 22:44:00 -0800474 try_servo_repair=False, dut_host_info=None):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700475 """Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -0800476
Richard Barnette9a26ad62016-06-10 12:03:08 -0700477 This function attempts to create and verify or repair a `ServoHost`
478 object for a servo connected to the given `dut`, subject to various
479 constraints imposed by the parameters:
480 * When the `servo_args` parameter is not `None`, a servo
481 host must be created, and must be checked with `repair()`.
482 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
483 true:
484 * If `try_servo_repair` is true, then create a servo host and
485 check it with `repair()`.
486 * Otherwise, if the servo responds to `ping` then create a
487 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800488
Richard Barnette9a26ad62016-06-10 12:03:08 -0700489 In cases where `servo_args` was not `None`, repair failure
490 exceptions are passed back to the caller; otherwise, exceptions
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700491 are logged and then discarded. Note that this only happens in cases
492 where we're called from a test (not special task) control file that
493 has an explicit dependency on servo. In that case, we require that
494 repair not write to `status.log`, so as to avoid polluting test
495 results.
496
497 TODO(jrbarnette): The special handling for servo in test control
498 files is a thorn in my flesh; I dearly hope to see it cut out before
499 my retirement.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700500
501 Parameters for a servo host consist of a host name, port number, and
502 DUT board, and are determined from one of these sources, in order of
503 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -0700504 * Servo attributes from the `dut` parameter take precedence over
505 all other sources of information.
506 * If a DNS entry for the servo based on the DUT hostname exists in
507 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -0700508 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700509 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -0700510 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700511
512 @param dut An instance of `Host` from which to take
513 servo parameters (if available).
514 @param servo_args A dictionary with servo parameters to use if
515 they can't be found from `dut`. If this
516 argument is supplied, unrepaired exceptions
517 from `verify()` will be passed back to the
518 caller.
519 @param try_lab_servo If not true, servo host creation will be
520 skipped unless otherwise required by the
521 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700522 @param try_servo_repair If true, check a servo host with
523 `repair()` instead of `verify()`.
Dan Shi4d478522014-02-14 13:46:32 -0800524
525 @returns: A ServoHost object or None. See comments above.
526
527 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700528 servo_dependency = servo_args is not None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700529 if dut is not None and (try_lab_servo or servo_dependency):
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700530 servo_args_override = get_servo_args_for_host(dut)
Richard Barnetteea3e4602016-06-10 12:36:41 -0700531 if servo_args_override is not None:
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700532 if utils.in_moblab_ssp():
533 _tweak_args_for_ssp_moblab(servo_args_override)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700534 logging.debug(
535 'Overriding provided servo_args (%s) with arguments'
536 ' determined from the host (%s)',
537 servo_args,
538 servo_args_override,
539 )
Richard Barnetteea3e4602016-06-10 12:36:41 -0700540 servo_args = servo_args_override
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700541
Richard Barnetteea3e4602016-06-10 12:36:41 -0700542 if servo_args is None:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700543 logging.debug('No servo_args provided, and failed to find overrides.')
Richard Barnetteea3e4602016-06-10 12:36:41 -0700544 return None
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700545 if SERVO_HOST_ATTR not in servo_args:
546 logging.debug('%s attribute missing from servo_args: %s',
547 SERVO_HOST_ATTR, servo_args)
548 return None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700549 if (not servo_dependency and not try_servo_repair and
Richard Barnette9a26ad62016-06-10 12:03:08 -0700550 not servo_host_is_up(servo_args[SERVO_HOST_ATTR])):
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700551 logging.debug('ServoHost is not up.')
Dan Shibbb0cb62014-03-24 17:50:57 -0700552 return None
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700553
Garry Wangebc015b2019-06-06 17:45:06 -0700554 newhost = ServoHost(**servo_args)
Garry Wangcdd27b22020-01-13 14:59:11 -0800555 try:
556 newhost.restart_servod(quick_startup=True)
557 except error.AutoservSSHTimeout:
558 logging.warning("Restart servod failed due ssh connection "
559 "to servohost timed out. This error is forgiven"
560 " here, we will retry in servo repair process.")
561 except error.AutoservRunError as e:
562 logging.warning("Restart servod failed due to:\n%s\n"
563 "This error is forgiven here, we will retry"
564 " in servo repair process.", str(e))
Garry Wangebc015b2019-06-06 17:45:06 -0700565
Gregory Nisbetde13e2a2019-12-09 22:44:00 -0800566 # TODO(gregorynisbet): Clean all of this up.
567 logging.debug('create_servo_host: attempt to set info store on '
568 'servo host')
569 try:
570 if dut_host_info is None:
571 logging.debug('create_servo_host: dut_host_info is '
572 'None, skipping')
573 else:
574 newhost.set_dut_host_info(dut_host_info)
575 logging.debug('create_servo_host: successfully set info '
576 'store')
577 except Exception:
578 logging.error("create_servo_host: (%s)", traceback.format_exc())
579
Richard Barnette9a26ad62016-06-10 12:03:08 -0700580 # Note that the logic of repair() includes everything done
581 # by verify(). It's sufficient to call one or the other;
582 # we don't need both.
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700583 if servo_dependency:
584 newhost.repair(silent=True)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700585 return newhost
586
587 if try_servo_repair:
588 try:
589 newhost.repair()
590 except Exception:
591 logging.exception('servo repair failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700592 else:
593 try:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700594 newhost.verify()
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700595 except Exception:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700596 logging.exception('servo verify failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700597 return newhost