blob: 87c0eb4cdad01fdfcbdf3decc950e6f4cbf48fe3 [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
Fang Deng5d518f42013-08-02 14:04:32 -070057
Garry Wangebc015b2019-06-06 17:45:06 -070058class ServoHost(base_servohost.BaseServoHost):
59 """Host class for a servo host(e.g. beaglebone, labstation)
Dana Goyette0b6e6402019-10-04 11:09:24 -070060 that with a servo instance for a specific port.
61
62 @type _servo: servo.Servo | None
63 """
Fang Deng5d518f42013-08-02 14:04:32 -070064
Raul E Rangel52ca2e82018-07-03 14:10:14 -060065 DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999'))
Richard Barnette9a26ad62016-06-10 12:03:08 -070066
Dan Shie5b3c512014-08-21 12:12:09 -070067 # Timeout for initializing servo signals.
Wai-Hong Tam37b6ed32017-09-19 15:52:39 -070068 INITIALIZE_SERVO_TIMEOUT_SECS = 60
Richard Barnette9a26ad62016-06-10 12:03:08 -070069
xixuan6cf6d2f2016-01-29 15:29:00 -080070 # Ready test function
71 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070072
73
Richard Barnette17bfc6c2016-08-04 18:41:43 -070074 def _initialize(self, servo_host='localhost',
Richard Barnettee519dcd2016-08-15 17:37:17 -070075 servo_port=DEFAULT_PORT, servo_board=None,
Nick Sanders2f3c9852018-10-24 12:10:24 -070076 servo_model=None, servo_serial=None, is_in_lab=None,
77 *args, **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -070078 """Initialize a ServoHost instance.
79
80 A ServoHost instance represents a host that controls a servo.
81
82 @param servo_host: Name of the host where the servod process
83 is running.
Raul E Rangel52ca2e82018-07-03 14:10:14 -060084 @param servo_port: Port the servod process is listening on. Defaults
85 to the SERVOD_PORT environment variable if set,
86 otherwise 9999.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070087 @param servo_board: Board that the servo is connected to.
Nick Sanders2f3c9852018-10-24 12:10:24 -070088 @param servo_model: Model that the servo is connected to.
Dan Shi4d478522014-02-14 13:46:32 -080089 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
90 to None, for which utils.host_is_in_lab_zone will be
91 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -070092
93 """
94 super(ServoHost, self)._initialize(hostname=servo_host,
Garry Wangebc015b2019-06-06 17:45:06 -070095 is_in_lab=is_in_lab, *args, **dargs)
Richard Barnette42f4db92018-08-23 15:05:15 -070096 self.servo_port = int(servo_port)
Richard Barnettee519dcd2016-08-15 17:37:17 -070097 self.servo_board = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -070098 self.servo_model = servo_model
Kevin Cheng643ce8a2016-09-15 15:42:12 -070099 self.servo_serial = servo_serial
Richard Barnettee519dcd2016-08-15 17:37:17 -0700100 self._servo = None
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800101 self._servod_server_proxy = None
102
Garry Wang79e9af62019-06-12 15:19:19 -0700103 # Path of the servo host lock file.
104 self._lock_file = (self.TEMP_FILE_DIR + str(self.servo_port)
105 + self.LOCK_FILE_POSTFIX)
106 # File path to declare a reboot request.
107 self._reboot_file = (self.TEMP_FILE_DIR + str(self.servo_port)
108 + self.REBOOT_FILE_POSTFIX)
109
110 # Lock the servo host if it's an in-lab labstation to prevent other
111 # task to reboot it until current task completes. We also wait and
112 # make sure the labstation is up here, in the case of the labstation is
113 # in the middle of reboot.
Garry Wang7c00b0f2019-06-25 17:28:17 -0700114 self._is_locked = False
Garry Wang42b4d862019-06-25 15:50:49 -0700115 if (self.wait_up(self.REBOOT_TIMEOUT) and self.is_in_lab()
116 and self.is_labstation()):
Garry Wang79e9af62019-06-12 15:19:19 -0700117 self._lock()
Garry Wangebc015b2019-06-06 17:45:06 -0700118
Richard Barnette9a26ad62016-06-10 12:03:08 -0700119 self._repair_strategy = (
120 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -0700121
Richard Barnette9a26ad62016-06-10 12:03:08 -0700122 def connect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700123 """Establish a connection to the servod server on this host.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700124
125 Initializes `self._servo` and then verifies that all network
126 connections are working. This will create an ssh tunnel if
127 it's required.
128
129 As a side effect of testing the connection, all signals on the
130 target servo are reset to default values, and the USB stick is
131 set to the neutral (off) position.
132 """
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700133 servo_obj = servo.Servo(servo_host=self, servo_serial=self.servo_serial)
Kuang-che Wu05763f52019-08-30 16:48:21 +0800134 self._servo = servo_obj
Richard Barnette9a26ad62016-06-10 12:03:08 -0700135 timeout, _ = retry.timeout(
136 servo_obj.initialize_dut,
137 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
138 if timeout:
139 raise hosts.AutoservVerifyError(
140 'Servo initialize timed out.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700141
142
143 def disconnect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700144 """Disconnect our servo if it exists.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700145
146 If we've previously successfully connected to our servo,
147 disconnect any established ssh tunnel, and set `self._servo`
148 back to `None`.
149 """
150 if self._servo:
151 # N.B. This call is safe even without a tunnel:
152 # rpc_server_tracker.disconnect() silently ignores
153 # unknown ports.
154 self.rpc_server_tracker.disconnect(self.servo_port)
155 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700156
Garry Wangc1288cf2019-12-17 14:58:00 -0800157
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800158 def _create_servod_server_proxy(self):
159 """Create a proxy that can be used to communicate with servod server.
Fang Deng5d518f42013-08-02 14:04:32 -0700160
161 @returns: An xmlrpclib.ServerProxy that is connected to the servod
162 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700163 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700164 if ENABLE_SSH_TUNNEL_FOR_SERVO and not self.is_localhost():
165 return self.rpc_server_tracker.xmlrpc_connect(
166 None, self.servo_port,
167 ready_test_name=self.SERVO_READY_METHOD,
Allen Li2b1a8992018-11-27 14:17:18 -0800168 timeout_seconds=60,
Allen Li556f4532018-12-03 18:11:23 -0800169 request_timeout_seconds=3600)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700170 else:
171 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
172 return xmlrpclib.ServerProxy(remote)
Fang Deng5d518f42013-08-02 14:04:32 -0700173
174
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800175 def get_servod_server_proxy(self):
176 """Return a cached proxy if exists; otherwise, create a new one.
177
178 @returns: An xmlrpclib.ServerProxy that is connected to the servod
179 server on the host.
180 """
181 # Single-threaded execution, no race
182 if self._servod_server_proxy is None:
183 self._servod_server_proxy = self._create_servod_server_proxy()
184 return self._servod_server_proxy
185
186
Richard Barnette1edbb162016-11-01 11:47:50 -0700187 def verify(self, silent=False):
188 """Update the servo host and verify it's in a good state.
189
190 @param silent If true, suppress logging in `status.log`.
191 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700192 message = 'Beginning verify for servo host %s port %s serial %s'
193 message %= (self.hostname, self.servo_port, self.servo_serial)
194 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700195 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700196 self._repair_strategy.verify(self, silent)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700197 except:
198 self.disconnect_servo()
Garry Wangc1288cf2019-12-17 14:58:00 -0800199 self.stop_servod()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700200 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700201
202
Richard Barnette1edbb162016-11-01 11:47:50 -0700203 def repair(self, silent=False):
204 """Attempt to repair servo host.
205
206 @param silent If true, suppress logging in `status.log`.
207 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700208 message = 'Beginning repair for servo host %s port %s serial %s'
209 message %= (self.hostname, self.servo_port, self.servo_serial)
210 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700211 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700212 self._repair_strategy.repair(self, silent)
Garry Wang464ff1e2019-07-18 17:20:34 -0700213 # If target is a labstation then try to withdraw any existing
214 # reboot request created by this servo because it passed repair.
215 if self.is_labstation():
216 self.withdraw_reboot_request()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700217 except:
218 self.disconnect_servo()
Garry Wangc1288cf2019-12-17 14:58:00 -0800219 self.stop_servod()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700220 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700221
222
Dan Shi4d478522014-02-14 13:46:32 -0800223 def get_servo(self):
224 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700225
Dan Shi4d478522014-02-14 13:46:32 -0800226 @return: a servo.Servo object.
Dana Goyette353d1d92019-06-27 10:43:59 -0700227 @rtype: autotest_lib.server.cros.servo.servo.Servo
Fang Deng5d518f42013-08-02 14:04:32 -0700228 """
Dan Shi4d478522014-02-14 13:46:32 -0800229 return self._servo
230
231
Garry Wang79e9af62019-06-12 15:19:19 -0700232 def request_reboot(self):
233 """Request servohost to be rebooted when it's safe to by touch a file.
234 """
235 logging.debug('Request to reboot servohost %s has been created by '
Garry Wang464ff1e2019-07-18 17:20:34 -0700236 'servo with port # %s', self.hostname, self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700237 self.run('touch %s' % self._reboot_file, ignore_status=True)
238
239
Garry Wang464ff1e2019-07-18 17:20:34 -0700240 def withdraw_reboot_request(self):
241 """Withdraw a servohost reboot request if exists by remove the flag
242 file.
243 """
244 logging.debug('Withdrawing request to reboot servohost %s that created'
245 ' by servo with port # %s if exists.',
246 self.hostname, self.servo_port)
247 self.run('rm -f %s' % self._reboot_file, ignore_status=True)
248
249
Garry Wangc1288cf2019-12-17 14:58:00 -0800250 def start_servod(self, quick_startup=False):
251 """Start the servod process on servohost.
252 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800253 # Skip if running on the localhost.(crbug.com/1038168)
254 if self.is_localhost():
255 logging.debug("Servohost is a localhost, skipping start servod.")
256 return
257
258 cmd = 'start servod'
Garry Wangc1288cf2019-12-17 14:58:00 -0800259 if self.servo_board:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800260 cmd += ' BOARD=%s' % self.servo_board
Garry Wangc1288cf2019-12-17 14:58:00 -0800261 if self.servo_model:
262 cmd += ' MODEL=%s' % self.servo_model
Garry Wangc1288cf2019-12-17 14:58:00 -0800263 else:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800264 logging.warning('Board for DUT is unknown; starting servod'
265 ' assuming a pre-configured board.')
266
267 cmd += ' PORT=%d' % self.servo_port
268 if self.servo_serial:
269 cmd += ' SERIAL=%s' % self.servo_serial
Garry Wangcdd27b22020-01-13 14:59:11 -0800270 self.run(cmd, timeout=60)
Garry Wangc1288cf2019-12-17 14:58:00 -0800271
272 # There's a lag between when `start servod` completes and when
273 # the _ServodConnectionVerifier trigger can actually succeed.
274 # The call to time.sleep() below gives time to make sure that
275 # the trigger won't fail after we return.
276
277 # Normally servod on servo_v3 and labstation take ~10 seconds to ready,
278 # But in the rare case all servo on a labstation are in heavy use they
279 # may take ~30 seconds. So the timeout value will double these value,
280 # and we'll try quick start up when first time initialize servohost,
281 # and use standard start up timeout in repair.
282 if quick_startup:
283 timeout = SERVOD_QUICK_STARTUP_TIMEOUT
284 else:
285 timeout = SERVOD_STARTUP_TIMEOUT
286 logging.debug('Wait %s seconds for servod process fully up.', timeout)
287 time.sleep(timeout)
288
289
290 def stop_servod(self):
291 """Stop the servod process on servohost.
292 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800293 # Skip if running on the localhost.(crbug.com/1038168)
294 if self.is_localhost():
295 logging.debug("Servohost is a localhost, skipping stop servod.")
296 return
297
Garry Wangc1288cf2019-12-17 14:58:00 -0800298 logging.debug('Stopping servod on port %s', self.servo_port)
Garry Wangcdd27b22020-01-13 14:59:11 -0800299 self.run('stop servod PORT=%d' % self.servo_port,
300 timeout=60, ignore_status=True)
Garry Wangc1288cf2019-12-17 14:58:00 -0800301 logging.debug('Wait %s seconds for servod process fully teardown.',
302 SERVOD_TEARDOWN_TIMEOUT)
303 time.sleep(SERVOD_TEARDOWN_TIMEOUT)
304
305
306 def restart_servod(self, quick_startup=False):
307 """Restart the servod process on servohost.
308 """
309 self.stop_servod()
310 self.start_servod(quick_startup)
311
312
Garry Wang79e9af62019-06-12 15:19:19 -0700313 def _lock(self):
314 """lock servohost by touching a file.
315 """
316 logging.debug('Locking servohost %s by touching %s file',
317 self.hostname, self._lock_file)
318 self.run('touch %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700319 self._is_locked = True
Garry Wang79e9af62019-06-12 15:19:19 -0700320
321
322 def _unlock(self):
323 """Unlock servohost by removing the lock file.
324 """
325 logging.debug('Unlocking servohost by removing %s file',
326 self._lock_file)
327 self.run('rm %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700328 self._is_locked = False
Garry Wang79e9af62019-06-12 15:19:19 -0700329
330
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700331 def close(self):
Congbin Guofc3b8962019-03-22 17:38:46 -0700332 """Close the associated servo and the host object."""
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700333 if self._servo:
Congbin Guo2e5e2a22018-07-27 10:32:48 -0700334 # In some cases when we run as lab-tools, the job object is None.
Congbin Guofc3b8962019-03-22 17:38:46 -0700335 if self.job and not self._servo.uart_logs_dir:
336 self._servo.uart_logs_dir = self.job.resultdir
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700337 self._servo.close()
338
Garry Wang7c00b0f2019-06-25 17:28:17 -0700339 if self._is_locked:
340 # Remove the lock if the servohost has been locked.
Garry Wang79e9af62019-06-12 15:19:19 -0700341 try:
342 self._unlock()
343 except error.AutoservSSHTimeout:
344 logging.error('Unlock servohost failed due to ssh timeout.'
345 ' It may caused by servohost went down during'
346 ' the task.')
347
Garry Wangc1288cf2019-12-17 14:58:00 -0800348 # We want always stop servod after task to minimum the impact of bad
349 # servod process interfere other servods.(see crbug.com/1028665)
Garry Wang4c624bc2020-01-27 16:34:43 -0800350 try:
351 self.stop_servod()
352 except error.AutoservRunError as e:
353 logging.info("Failed to stop servod due to:\n%s\n"
354 "This error is forgived.", str(e))
Garry Wangc1288cf2019-12-17 14:58:00 -0800355
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700356 super(ServoHost, self).close()
357
358
Richard Barnetteea3e4602016-06-10 12:36:41 -0700359def make_servo_hostname(dut_hostname):
360 """Given a DUT's hostname, return the hostname of its servo.
361
362 @param dut_hostname: hostname of a DUT.
363
364 @return hostname of the DUT's servo.
365
366 """
367 host_parts = dut_hostname.split('.')
368 host_parts[0] = host_parts[0] + '-servo'
369 return '.'.join(host_parts)
370
371
372def servo_host_is_up(servo_hostname):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700373 """Given a servo host name, return if it's up or not.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700374
375 @param servo_hostname: hostname of the servo host.
376
377 @return True if it's up, False otherwise
378 """
379 # Technically, this duplicates the SSH ping done early in the servo
380 # proxy initialization code. However, this ping ends in a couple
381 # seconds when if fails, rather than the 60 seconds it takes to decide
382 # that an SSH ping has timed out. Specifically, that timeout happens
383 # when our servo DNS name resolves, but there is no host at that IP.
384 logging.info('Pinging servo host at %s', servo_hostname)
385 ping_config = ping_runner.PingConfig(
386 servo_hostname, count=3,
387 ignore_result=True, ignore_status=True)
388 return ping_runner.PingRunner().ping(ping_config).received > 0
389
390
Richard Barnettee519dcd2016-08-15 17:37:17 -0700391def _map_afe_board_to_servo_board(afe_board):
392 """Map a board we get from the AFE to a servo appropriate value.
393
394 Many boards are identical to other boards for servo's purposes.
395 This function makes that mapping.
396
397 @param afe_board string board name received from AFE.
398 @return board we expect servo to have.
399
400 """
401 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
402 BOARD_MAP = {'gizmo': 'panther'}
403 mapped_board = afe_board
404 if afe_board in BOARD_MAP:
405 mapped_board = BOARD_MAP[afe_board]
406 else:
407 for suffix in KNOWN_SUFFIXES:
408 if afe_board.endswith(suffix):
409 mapped_board = afe_board[0:-len(suffix)]
410 break
411 if mapped_board != afe_board:
412 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
413 return mapped_board
414
415
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700416def get_servo_args_for_host(dut_host):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700417 """Return servo data associated with a given DUT.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700418
Richard Barnetteea3e4602016-06-10 12:36:41 -0700419 @param dut_host Instance of `Host` on which to find the servo
420 attributes.
Prathmesh Prabhuf605dd32018-08-28 17:09:04 -0700421 @return `servo_args` dict with host and an optional port.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700422 """
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700423 info = dut_host.host_info_store.get()
424 servo_args = {k: v for k, v in info.attributes.iteritems()
425 if k in SERVO_ATTR_KEYS}
Richard Barnetteea3e4602016-06-10 12:36:41 -0700426
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700427 if SERVO_PORT_ATTR in servo_args:
428 try:
429 servo_args[SERVO_PORT_ATTR] = int(servo_args[SERVO_PORT_ATTR])
430 except ValueError:
431 logging.error('servo port is not an int: %s',
432 servo_args[SERVO_PORT_ATTR])
433 # Reset servo_args because we don't want to use an invalid port.
434 servo_args.pop(SERVO_HOST_ATTR, None)
435
436 if info.board:
437 servo_args[SERVO_BOARD_ATTR] = _map_afe_board_to_servo_board(info.board)
Nick Sanders2f3c9852018-10-24 12:10:24 -0700438 if info.model:
439 servo_args[SERVO_MODEL_ATTR] = info.model
Prathmesh Prabhu6f5f6362018-09-05 17:20:31 -0700440 return servo_args if SERVO_HOST_ATTR in servo_args else None
Richard Barnetteea3e4602016-06-10 12:36:41 -0700441
442
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700443def _tweak_args_for_ssp_moblab(servo_args):
444 if servo_args[SERVO_HOST_ATTR] in ['localhost', '127.0.0.1']:
445 servo_args[SERVO_HOST_ATTR] = _CONFIG.get_config_value(
446 'SSP', 'host_container_ip', type=str, default=None)
447
448
Dan Shi023aae32016-05-25 11:13:01 -0700449def create_servo_host(dut, servo_args, try_lab_servo=False,
Gregory Nisbetde13e2a2019-12-09 22:44:00 -0800450 try_servo_repair=False, dut_host_info=None):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700451 """Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -0800452
Richard Barnette9a26ad62016-06-10 12:03:08 -0700453 This function attempts to create and verify or repair a `ServoHost`
454 object for a servo connected to the given `dut`, subject to various
455 constraints imposed by the parameters:
456 * When the `servo_args` parameter is not `None`, a servo
457 host must be created, and must be checked with `repair()`.
458 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
459 true:
460 * If `try_servo_repair` is true, then create a servo host and
461 check it with `repair()`.
462 * Otherwise, if the servo responds to `ping` then create a
463 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800464
Richard Barnette9a26ad62016-06-10 12:03:08 -0700465 In cases where `servo_args` was not `None`, repair failure
466 exceptions are passed back to the caller; otherwise, exceptions
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700467 are logged and then discarded. Note that this only happens in cases
468 where we're called from a test (not special task) control file that
469 has an explicit dependency on servo. In that case, we require that
470 repair not write to `status.log`, so as to avoid polluting test
471 results.
472
473 TODO(jrbarnette): The special handling for servo in test control
474 files is a thorn in my flesh; I dearly hope to see it cut out before
475 my retirement.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700476
477 Parameters for a servo host consist of a host name, port number, and
478 DUT board, and are determined from one of these sources, in order of
479 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -0700480 * Servo attributes from the `dut` parameter take precedence over
481 all other sources of information.
482 * If a DNS entry for the servo based on the DUT hostname exists in
483 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -0700484 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700485 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -0700486 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700487
488 @param dut An instance of `Host` from which to take
489 servo parameters (if available).
490 @param servo_args A dictionary with servo parameters to use if
491 they can't be found from `dut`. If this
492 argument is supplied, unrepaired exceptions
493 from `verify()` will be passed back to the
494 caller.
495 @param try_lab_servo If not true, servo host creation will be
496 skipped unless otherwise required by the
497 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700498 @param try_servo_repair If true, check a servo host with
499 `repair()` instead of `verify()`.
Dan Shi4d478522014-02-14 13:46:32 -0800500
501 @returns: A ServoHost object or None. See comments above.
502
503 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700504 servo_dependency = servo_args is not None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700505 if dut is not None and (try_lab_servo or servo_dependency):
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700506 servo_args_override = get_servo_args_for_host(dut)
Richard Barnetteea3e4602016-06-10 12:36:41 -0700507 if servo_args_override is not None:
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700508 if utils.in_moblab_ssp():
509 _tweak_args_for_ssp_moblab(servo_args_override)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700510 logging.debug(
511 'Overriding provided servo_args (%s) with arguments'
512 ' determined from the host (%s)',
513 servo_args,
514 servo_args_override,
515 )
Richard Barnetteea3e4602016-06-10 12:36:41 -0700516 servo_args = servo_args_override
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700517
Richard Barnetteea3e4602016-06-10 12:36:41 -0700518 if servo_args is None:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700519 logging.debug('No servo_args provided, and failed to find overrides.')
Richard Barnetteea3e4602016-06-10 12:36:41 -0700520 return None
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700521 if SERVO_HOST_ATTR not in servo_args:
522 logging.debug('%s attribute missing from servo_args: %s',
523 SERVO_HOST_ATTR, servo_args)
524 return None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700525 if (not servo_dependency and not try_servo_repair and
Richard Barnette9a26ad62016-06-10 12:03:08 -0700526 not servo_host_is_up(servo_args[SERVO_HOST_ATTR])):
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700527 logging.debug('ServoHost is not up.')
Dan Shibbb0cb62014-03-24 17:50:57 -0700528 return None
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700529
Garry Wangebc015b2019-06-06 17:45:06 -0700530 newhost = ServoHost(**servo_args)
Garry Wangcdd27b22020-01-13 14:59:11 -0800531 try:
532 newhost.restart_servod(quick_startup=True)
533 except error.AutoservSSHTimeout:
534 logging.warning("Restart servod failed due ssh connection "
535 "to servohost timed out. This error is forgiven"
536 " here, we will retry in servo repair process.")
537 except error.AutoservRunError as e:
538 logging.warning("Restart servod failed due to:\n%s\n"
539 "This error is forgiven here, we will retry"
540 " in servo repair process.", str(e))
Garry Wangebc015b2019-06-06 17:45:06 -0700541
Gregory Nisbetde13e2a2019-12-09 22:44:00 -0800542 # TODO(gregorynisbet): Clean all of this up.
543 logging.debug('create_servo_host: attempt to set info store on '
544 'servo host')
545 try:
546 if dut_host_info is None:
547 logging.debug('create_servo_host: dut_host_info is '
548 'None, skipping')
549 else:
550 newhost.set_dut_host_info(dut_host_info)
551 logging.debug('create_servo_host: successfully set info '
552 'store')
553 except Exception:
554 logging.error("create_servo_host: (%s)", traceback.format_exc())
555
Richard Barnette9a26ad62016-06-10 12:03:08 -0700556 # Note that the logic of repair() includes everything done
557 # by verify(). It's sufficient to call one or the other;
558 # we don't need both.
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700559 if servo_dependency:
560 newhost.repair(silent=True)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700561 return newhost
562
563 if try_servo_repair:
564 try:
565 newhost.repair()
566 except Exception:
567 logging.exception('servo repair failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700568 else:
569 try:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700570 newhost.verify()
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700571 except Exception:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700572 logging.exception('servo verify failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700573 return newhost