blob: 7627956f45a9c07b285efb0673be24bf697443fc [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
Dana Goyette6006ead2019-10-04 17:12:24 +000014import shutil
Dana Goyette4dc0adc2019-05-06 14:51:53 -070015import xmlrpclib
Fang Deng5d518f42013-08-02 14:04:32 -070016
17from autotest_lib.client.bin import utils
Garry Wang79e9af62019-06-12 15:19:19 -070018from 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
Fang Deng5d518f42013-08-02 14:04:32 -070021from autotest_lib.client.common_lib.cros import retry
Christopher Wileycef1f902014-06-19 11:11:23 -070022from autotest_lib.client.common_lib.cros.network import ping_runner
Richard Barnette9a26ad62016-06-10 12:03:08 -070023from autotest_lib.server.cros.servo import servo
Richard Barnetted31580e2018-05-14 19:58:00 +000024from autotest_lib.server.hosts import servo_repair
Garry Wangebc015b2019-06-06 17:45:06 -070025from autotest_lib.server.hosts import base_servohost
Dan Shi5e2efb72017-02-07 11:40:23 -080026
Fang Deng5d518f42013-08-02 14:04:32 -070027
Simran Basi0739d682015-02-25 16:22:56 -080028# Names of the host attributes in the database that represent the values for
29# the servo_host and servo_port for a servo connected to the DUT.
30SERVO_HOST_ATTR = 'servo_host'
31SERVO_PORT_ATTR = 'servo_port'
Richard Barnettee519dcd2016-08-15 17:37:17 -070032SERVO_BOARD_ATTR = 'servo_board'
Nick Sanders2f3c9852018-10-24 12:10:24 -070033# Model is inferred from host labels.
34SERVO_MODEL_ATTR = 'servo_model'
Kevin Cheng643ce8a2016-09-15 15:42:12 -070035SERVO_SERIAL_ATTR = 'servo_serial'
Prathmesh Prabhucba44292018-08-28 17:44:45 -070036SERVO_ATTR_KEYS = (
37 SERVO_BOARD_ATTR,
38 SERVO_HOST_ATTR,
39 SERVO_PORT_ATTR,
40 SERVO_SERIAL_ATTR,
41)
Simran Basi0739d682015-02-25 16:22:56 -080042
Dan Shi3b2adf62015-09-02 17:46:54 -070043_CONFIG = global_config.global_config
xixuan6cf6d2f2016-01-29 15:29:00 -080044ENABLE_SSH_TUNNEL_FOR_SERVO = _CONFIG.get_config_value(
45 'CROS', 'enable_ssh_tunnel_for_servo', type=bool, default=False)
Simran Basi0739d682015-02-25 16:22:56 -080046
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070047AUTOTEST_BASE = _CONFIG.get_config_value(
48 'SCHEDULER', 'drone_installation_directory',
49 default='/usr/local/autotest')
50
Fang Deng5d518f42013-08-02 14:04:32 -070051
Garry Wangebc015b2019-06-06 17:45:06 -070052class ServoHost(base_servohost.BaseServoHost):
53 """Host class for a servo host(e.g. beaglebone, labstation)
Dana Goyette6006ead2019-10-04 17:12:24 +000054 that with a servo instance for a specific port."""
Fang Deng5d518f42013-08-02 14:04:32 -070055
Raul E Rangel52ca2e82018-07-03 14:10:14 -060056 DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999'))
Richard Barnette9a26ad62016-06-10 12:03:08 -070057
Dan Shie5b3c512014-08-21 12:12:09 -070058 # Timeout for initializing servo signals.
Wai-Hong Tam37b6ed32017-09-19 15:52:39 -070059 INITIALIZE_SERVO_TIMEOUT_SECS = 60
Richard Barnette9a26ad62016-06-10 12:03:08 -070060
xixuan6cf6d2f2016-01-29 15:29:00 -080061 # Ready test function
62 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070063
64
Richard Barnette17bfc6c2016-08-04 18:41:43 -070065 def _initialize(self, servo_host='localhost',
Richard Barnettee519dcd2016-08-15 17:37:17 -070066 servo_port=DEFAULT_PORT, servo_board=None,
Nick Sanders2f3c9852018-10-24 12:10:24 -070067 servo_model=None, servo_serial=None, is_in_lab=None,
68 *args, **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -070069 """Initialize a ServoHost instance.
70
71 A ServoHost instance represents a host that controls a servo.
72
73 @param servo_host: Name of the host where the servod process
74 is running.
Raul E Rangel52ca2e82018-07-03 14:10:14 -060075 @param servo_port: Port the servod process is listening on. Defaults
76 to the SERVOD_PORT environment variable if set,
77 otherwise 9999.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070078 @param servo_board: Board that the servo is connected to.
Nick Sanders2f3c9852018-10-24 12:10:24 -070079 @param servo_model: Model that the servo is connected to.
Dan Shi4d478522014-02-14 13:46:32 -080080 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
81 to None, for which utils.host_is_in_lab_zone will be
82 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -070083
84 """
85 super(ServoHost, self)._initialize(hostname=servo_host,
Garry Wangebc015b2019-06-06 17:45:06 -070086 is_in_lab=is_in_lab, *args, **dargs)
Richard Barnette42f4db92018-08-23 15:05:15 -070087 self.servo_port = int(servo_port)
Richard Barnettee519dcd2016-08-15 17:37:17 -070088 self.servo_board = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -070089 self.servo_model = servo_model
Kevin Cheng643ce8a2016-09-15 15:42:12 -070090 self.servo_serial = servo_serial
Richard Barnettee519dcd2016-08-15 17:37:17 -070091 self._servo = None
Garry Wang79e9af62019-06-12 15:19:19 -070092 # Path of the servo host lock file.
93 self._lock_file = (self.TEMP_FILE_DIR + str(self.servo_port)
94 + self.LOCK_FILE_POSTFIX)
95 # File path to declare a reboot request.
96 self._reboot_file = (self.TEMP_FILE_DIR + str(self.servo_port)
97 + self.REBOOT_FILE_POSTFIX)
98
99 # Lock the servo host if it's an in-lab labstation to prevent other
100 # task to reboot it until current task completes. We also wait and
101 # make sure the labstation is up here, in the case of the labstation is
102 # in the middle of reboot.
Garry Wang7c00b0f2019-06-25 17:28:17 -0700103 self._is_locked = False
Garry Wang42b4d862019-06-25 15:50:49 -0700104 if (self.wait_up(self.REBOOT_TIMEOUT) and self.is_in_lab()
105 and self.is_labstation()):
Garry Wang79e9af62019-06-12 15:19:19 -0700106 self._lock()
Garry Wangebc015b2019-06-06 17:45:06 -0700107
Richard Barnette9a26ad62016-06-10 12:03:08 -0700108 self._repair_strategy = (
109 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -0700110
Dana Goyette6006ead2019-10-04 17:12:24 +0000111 self._prev_log_size = 0
112 self._prev_log_inode = 0
113
Richard Barnette9a26ad62016-06-10 12:03:08 -0700114 def connect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700115 """Establish a connection to the servod server on this host.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700116
117 Initializes `self._servo` and then verifies that all network
118 connections are working. This will create an ssh tunnel if
119 it's required.
120
121 As a side effect of testing the connection, all signals on the
122 target servo are reset to default values, and the USB stick is
123 set to the neutral (off) position.
124 """
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700125 servo_obj = servo.Servo(servo_host=self, servo_serial=self.servo_serial)
Kuang-che Wu05763f52019-08-30 16:48:21 +0800126 self._servo = servo_obj
Richard Barnette9a26ad62016-06-10 12:03:08 -0700127 timeout, _ = retry.timeout(
128 servo_obj.initialize_dut,
129 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
130 if timeout:
131 raise hosts.AutoservVerifyError(
132 'Servo initialize timed out.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700133
134
135 def disconnect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700136 """Disconnect our servo if it exists.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700137
138 If we've previously successfully connected to our servo,
139 disconnect any established ssh tunnel, and set `self._servo`
140 back to `None`.
141 """
142 if self._servo:
143 # N.B. This call is safe even without a tunnel:
144 # rpc_server_tracker.disconnect() silently ignores
145 # unknown ports.
146 self.rpc_server_tracker.disconnect(self.servo_port)
147 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700148
Dana Goyette6006ead2019-10-04 17:12:24 +0000149 def fetch_servod_log(self, filename, skip_old=False):
150 """Save the servod log into the given local file.
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700151
Dana Goyette6006ead2019-10-04 17:12:24 +0000152 The inode number is used for checking whether the log was rotated:
153 it skips old data only if the log is actually the same file.
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700154
Dana Goyette6006ead2019-10-04 17:12:24 +0000155 If filename is not set, this just refreshes the stored info about the
156 log file's size and inode, for use in future calls.
157
158 @param filename: save the contents into a file with the given name.
159 @param skip_old: if True, skip past the old data in the log file.
160 @type filename: str
161 @type skip_old: bool
162 @rtype: None
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700163 """
164 if self.is_localhost():
165 return
166
Dana Goyette6006ead2019-10-04 17:12:24 +0000167 log_name = 'servod_%s' % self.servo_port
168 log_path = '/var/log/%s.log' % log_name
169
170 # %n = name, %i = inode, %s = size.
171 cmd = "/usr/bin/stat --format '%n|%i|%s' {}".format(log_path)
172 result = self.run(cmd, ignore_status=True)
173 if result.exit_status != 0:
174 if 'No such file or directory' not in result.stderr:
175 # Warn only if log file is broken/unreadable, not just missing.
176 logging.warn("Couldn't stat servod log: %s", result.stderr)
177
178 self._prev_log_size = 0
179 self._prev_log_inode = 0
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700180 return
181
Dana Goyette6006ead2019-10-04 17:12:24 +0000182 (path, inode, size) = result.stdout.split('|')
183 inode = int(inode)
184 size = int(size)
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700185
Dana Goyette6006ead2019-10-04 17:12:24 +0000186 prev_inode = self._prev_log_inode
187 prev_size = self._prev_log_size
188 if not prev_inode or not prev_size or inode != prev_inode:
189 # Don't skip if it's actually a different file, or it somehow shrunk
190 skip_old = False
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700191
Dana Goyette6006ead2019-10-04 17:12:24 +0000192 if filename:
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700193 try:
Dana Goyette6006ead2019-10-04 17:12:24 +0000194 if skip_old:
195 # Fetch whole log to .log.tmp, then save only the new bytes.
196 temp_filename = filename + '.tmp'
197 self.get_file(log_path, temp_filename)
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700198
Dana Goyette6006ead2019-10-04 17:12:24 +0000199 with open(temp_filename, 'rb') as temp_log_file:
200 temp_log_file.seek(prev_size)
201 with open(filename, 'wb') as real_log_file:
202 # read in pieces, in case the log file is big
203 shutil.copyfileobj(temp_log_file, real_log_file)
204 os.unlink(temp_filename)
205 else:
206 self.get_file(log_path, filename)
207 except EnvironmentError:
208 logging.warn("Couldn't save copy of servod log:", exc_info=True)
Dana Goyette4dc0adc2019-05-06 14:51:53 -0700209
Dana Goyette6006ead2019-10-04 17:12:24 +0000210 self._prev_log_size = size
211 self._prev_log_inode = inode
Fang Deng5d518f42013-08-02 14:04:32 -0700212
Fang Deng5d518f42013-08-02 14:04:32 -0700213 def get_servod_server_proxy(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700214 """Return a proxy that can be used to communicate with servod server.
Fang Deng5d518f42013-08-02 14:04:32 -0700215
216 @returns: An xmlrpclib.ServerProxy that is connected to the servod
217 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700218 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700219 if ENABLE_SSH_TUNNEL_FOR_SERVO and not self.is_localhost():
220 return self.rpc_server_tracker.xmlrpc_connect(
221 None, self.servo_port,
222 ready_test_name=self.SERVO_READY_METHOD,
Allen Li2b1a8992018-11-27 14:17:18 -0800223 timeout_seconds=60,
Allen Li556f4532018-12-03 18:11:23 -0800224 request_timeout_seconds=3600)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700225 else:
226 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
227 return xmlrpclib.ServerProxy(remote)
Fang Deng5d518f42013-08-02 14:04:32 -0700228
229
Richard Barnette1edbb162016-11-01 11:47:50 -0700230 def verify(self, silent=False):
231 """Update the servo host and verify it's in a good state.
232
233 @param silent If true, suppress logging in `status.log`.
234 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700235 message = 'Beginning verify for servo host %s port %s serial %s'
236 message %= (self.hostname, self.servo_port, self.servo_serial)
237 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700238 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700239 self._repair_strategy.verify(self, silent)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700240 except:
241 self.disconnect_servo()
242 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700243
244
Richard Barnette1edbb162016-11-01 11:47:50 -0700245 def repair(self, silent=False):
246 """Attempt to repair servo host.
247
248 @param silent If true, suppress logging in `status.log`.
249 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700250 message = 'Beginning repair for servo host %s port %s serial %s'
251 message %= (self.hostname, self.servo_port, self.servo_serial)
252 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700253 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700254 self._repair_strategy.repair(self, silent)
Garry Wang464ff1e2019-07-18 17:20:34 -0700255 # If target is a labstation then try to withdraw any existing
256 # reboot request created by this servo because it passed repair.
257 if self.is_labstation():
258 self.withdraw_reboot_request()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700259 except:
260 self.disconnect_servo()
261 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700262
263
Dan Shi4d478522014-02-14 13:46:32 -0800264 def get_servo(self):
265 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700266
Dan Shi4d478522014-02-14 13:46:32 -0800267 @return: a servo.Servo object.
Dana Goyette353d1d92019-06-27 10:43:59 -0700268 @rtype: autotest_lib.server.cros.servo.servo.Servo
Fang Deng5d518f42013-08-02 14:04:32 -0700269 """
Dan Shi4d478522014-02-14 13:46:32 -0800270 return self._servo
271
272
Garry Wang79e9af62019-06-12 15:19:19 -0700273 def request_reboot(self):
274 """Request servohost to be rebooted when it's safe to by touch a file.
275 """
276 logging.debug('Request to reboot servohost %s has been created by '
Garry Wang464ff1e2019-07-18 17:20:34 -0700277 'servo with port # %s', self.hostname, self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700278 self.run('touch %s' % self._reboot_file, ignore_status=True)
279
280
Garry Wang464ff1e2019-07-18 17:20:34 -0700281 def withdraw_reboot_request(self):
282 """Withdraw a servohost reboot request if exists by remove the flag
283 file.
284 """
285 logging.debug('Withdrawing request to reboot servohost %s that created'
286 ' by servo with port # %s if exists.',
287 self.hostname, self.servo_port)
288 self.run('rm -f %s' % self._reboot_file, ignore_status=True)
289
290
Garry Wang79e9af62019-06-12 15:19:19 -0700291 def _lock(self):
292 """lock servohost by touching a file.
293 """
294 logging.debug('Locking servohost %s by touching %s file',
295 self.hostname, self._lock_file)
296 self.run('touch %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700297 self._is_locked = True
Garry Wang79e9af62019-06-12 15:19:19 -0700298
299
300 def _unlock(self):
301 """Unlock servohost by removing the lock file.
302 """
303 logging.debug('Unlocking servohost by removing %s file',
304 self._lock_file)
305 self.run('rm %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700306 self._is_locked = False
Garry Wang79e9af62019-06-12 15:19:19 -0700307
308
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700309 def close(self):
Congbin Guofc3b8962019-03-22 17:38:46 -0700310 """Close the associated servo and the host object."""
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700311 if self._servo:
Congbin Guo2e5e2a22018-07-27 10:32:48 -0700312 # In some cases when we run as lab-tools, the job object is None.
Congbin Guofc3b8962019-03-22 17:38:46 -0700313 if self.job and not self._servo.uart_logs_dir:
314 self._servo.uart_logs_dir = self.job.resultdir
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700315 self._servo.close()
316
Garry Wang7c00b0f2019-06-25 17:28:17 -0700317 if self._is_locked:
318 # Remove the lock if the servohost has been locked.
Garry Wang79e9af62019-06-12 15:19:19 -0700319 try:
320 self._unlock()
321 except error.AutoservSSHTimeout:
322 logging.error('Unlock servohost failed due to ssh timeout.'
323 ' It may caused by servohost went down during'
324 ' the task.')
325
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700326 super(ServoHost, self).close()
327
328
Richard Barnetteea3e4602016-06-10 12:36:41 -0700329def make_servo_hostname(dut_hostname):
330 """Given a DUT's hostname, return the hostname of its servo.
331
332 @param dut_hostname: hostname of a DUT.
333
334 @return hostname of the DUT's servo.
335
336 """
337 host_parts = dut_hostname.split('.')
338 host_parts[0] = host_parts[0] + '-servo'
339 return '.'.join(host_parts)
340
341
342def servo_host_is_up(servo_hostname):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700343 """Given a servo host name, return if it's up or not.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700344
345 @param servo_hostname: hostname of the servo host.
346
347 @return True if it's up, False otherwise
348 """
349 # Technically, this duplicates the SSH ping done early in the servo
350 # proxy initialization code. However, this ping ends in a couple
351 # seconds when if fails, rather than the 60 seconds it takes to decide
352 # that an SSH ping has timed out. Specifically, that timeout happens
353 # when our servo DNS name resolves, but there is no host at that IP.
354 logging.info('Pinging servo host at %s', servo_hostname)
355 ping_config = ping_runner.PingConfig(
356 servo_hostname, count=3,
357 ignore_result=True, ignore_status=True)
358 return ping_runner.PingRunner().ping(ping_config).received > 0
359
360
Richard Barnettee519dcd2016-08-15 17:37:17 -0700361def _map_afe_board_to_servo_board(afe_board):
362 """Map a board we get from the AFE to a servo appropriate value.
363
364 Many boards are identical to other boards for servo's purposes.
365 This function makes that mapping.
366
367 @param afe_board string board name received from AFE.
368 @return board we expect servo to have.
369
370 """
371 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
372 BOARD_MAP = {'gizmo': 'panther'}
373 mapped_board = afe_board
374 if afe_board in BOARD_MAP:
375 mapped_board = BOARD_MAP[afe_board]
376 else:
377 for suffix in KNOWN_SUFFIXES:
378 if afe_board.endswith(suffix):
379 mapped_board = afe_board[0:-len(suffix)]
380 break
381 if mapped_board != afe_board:
382 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
383 return mapped_board
384
385
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700386def get_servo_args_for_host(dut_host):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700387 """Return servo data associated with a given DUT.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700388
Richard Barnetteea3e4602016-06-10 12:36:41 -0700389 @param dut_host Instance of `Host` on which to find the servo
390 attributes.
Prathmesh Prabhuf605dd32018-08-28 17:09:04 -0700391 @return `servo_args` dict with host and an optional port.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700392 """
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700393 info = dut_host.host_info_store.get()
394 servo_args = {k: v for k, v in info.attributes.iteritems()
395 if k in SERVO_ATTR_KEYS}
Richard Barnetteea3e4602016-06-10 12:36:41 -0700396
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700397 if SERVO_PORT_ATTR in servo_args:
398 try:
399 servo_args[SERVO_PORT_ATTR] = int(servo_args[SERVO_PORT_ATTR])
400 except ValueError:
401 logging.error('servo port is not an int: %s',
402 servo_args[SERVO_PORT_ATTR])
403 # Reset servo_args because we don't want to use an invalid port.
404 servo_args.pop(SERVO_HOST_ATTR, None)
405
406 if info.board:
407 servo_args[SERVO_BOARD_ATTR] = _map_afe_board_to_servo_board(info.board)
Nick Sanders2f3c9852018-10-24 12:10:24 -0700408 if info.model:
409 servo_args[SERVO_MODEL_ATTR] = info.model
Prathmesh Prabhu6f5f6362018-09-05 17:20:31 -0700410 return servo_args if SERVO_HOST_ATTR in servo_args else None
Richard Barnetteea3e4602016-06-10 12:36:41 -0700411
412
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700413def _tweak_args_for_ssp_moblab(servo_args):
414 if servo_args[SERVO_HOST_ATTR] in ['localhost', '127.0.0.1']:
415 servo_args[SERVO_HOST_ATTR] = _CONFIG.get_config_value(
416 'SSP', 'host_container_ip', type=str, default=None)
417
418
Dan Shi023aae32016-05-25 11:13:01 -0700419def create_servo_host(dut, servo_args, try_lab_servo=False,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700420 try_servo_repair=False):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700421 """Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -0800422
Richard Barnette9a26ad62016-06-10 12:03:08 -0700423 This function attempts to create and verify or repair a `ServoHost`
424 object for a servo connected to the given `dut`, subject to various
425 constraints imposed by the parameters:
426 * When the `servo_args` parameter is not `None`, a servo
427 host must be created, and must be checked with `repair()`.
428 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
429 true:
430 * If `try_servo_repair` is true, then create a servo host and
431 check it with `repair()`.
432 * Otherwise, if the servo responds to `ping` then create a
433 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800434
Richard Barnette9a26ad62016-06-10 12:03:08 -0700435 In cases where `servo_args` was not `None`, repair failure
436 exceptions are passed back to the caller; otherwise, exceptions
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700437 are logged and then discarded. Note that this only happens in cases
438 where we're called from a test (not special task) control file that
439 has an explicit dependency on servo. In that case, we require that
440 repair not write to `status.log`, so as to avoid polluting test
441 results.
442
443 TODO(jrbarnette): The special handling for servo in test control
444 files is a thorn in my flesh; I dearly hope to see it cut out before
445 my retirement.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700446
447 Parameters for a servo host consist of a host name, port number, and
448 DUT board, and are determined from one of these sources, in order of
449 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -0700450 * Servo attributes from the `dut` parameter take precedence over
451 all other sources of information.
452 * If a DNS entry for the servo based on the DUT hostname exists in
453 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -0700454 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700455 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -0700456 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700457
458 @param dut An instance of `Host` from which to take
459 servo parameters (if available).
460 @param servo_args A dictionary with servo parameters to use if
461 they can't be found from `dut`. If this
462 argument is supplied, unrepaired exceptions
463 from `verify()` will be passed back to the
464 caller.
465 @param try_lab_servo If not true, servo host creation will be
466 skipped unless otherwise required by the
467 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700468 @param try_servo_repair If true, check a servo host with
469 `repair()` instead of `verify()`.
Dan Shi4d478522014-02-14 13:46:32 -0800470
471 @returns: A ServoHost object or None. See comments above.
472
473 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700474 servo_dependency = servo_args is not None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700475 if dut is not None and (try_lab_servo or servo_dependency):
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700476 servo_args_override = get_servo_args_for_host(dut)
Richard Barnetteea3e4602016-06-10 12:36:41 -0700477 if servo_args_override is not None:
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700478 if utils.in_moblab_ssp():
479 _tweak_args_for_ssp_moblab(servo_args_override)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700480 logging.debug(
481 'Overriding provided servo_args (%s) with arguments'
482 ' determined from the host (%s)',
483 servo_args,
484 servo_args_override,
485 )
Richard Barnetteea3e4602016-06-10 12:36:41 -0700486 servo_args = servo_args_override
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700487
Richard Barnetteea3e4602016-06-10 12:36:41 -0700488 if servo_args is None:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700489 logging.debug('No servo_args provided, and failed to find overrides.')
Richard Barnetteea3e4602016-06-10 12:36:41 -0700490 return None
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700491 if SERVO_HOST_ATTR not in servo_args:
492 logging.debug('%s attribute missing from servo_args: %s',
493 SERVO_HOST_ATTR, servo_args)
494 return None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700495 if (not servo_dependency and not try_servo_repair and
Richard Barnette9a26ad62016-06-10 12:03:08 -0700496 not servo_host_is_up(servo_args[SERVO_HOST_ATTR])):
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700497 logging.debug('ServoHost is not up.')
Dan Shibbb0cb62014-03-24 17:50:57 -0700498 return None
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700499
Garry Wangebc015b2019-06-06 17:45:06 -0700500 newhost = ServoHost(**servo_args)
501
Richard Barnette9a26ad62016-06-10 12:03:08 -0700502 # Note that the logic of repair() includes everything done
503 # by verify(). It's sufficient to call one or the other;
504 # we don't need both.
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700505 if servo_dependency:
506 newhost.repair(silent=True)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700507 return newhost
508
509 if try_servo_repair:
510 try:
511 newhost.repair()
512 except Exception:
513 logging.exception('servo repair failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700514 else:
515 try:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700516 newhost.verify()
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700517 except Exception:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700518 logging.exception('servo verify failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700519 return newhost