blob: f675e4ea90715444435843a9af2daa58be1d3cf2 [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
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -080014import re
15import tarfile
Garry Wangc1288cf2019-12-17 14:58:00 -080016import time
Gregory Nisbet265a52c2019-12-10 20:38:42 -080017import traceback
Dana Goyette4dc0adc2019-05-06 14:51:53 -070018import xmlrpclib
Fang Deng5d518f42013-08-02 14:04:32 -070019
20from autotest_lib.client.bin import utils
Garry Wang79e9af62019-06-12 15:19:19 -070021from autotest_lib.client.common_lib import error
beeps5e8c45a2013-12-17 22:05:11 -080022from autotest_lib.client.common_lib import global_config
Richard Barnette9a26ad62016-06-10 12:03:08 -070023from autotest_lib.client.common_lib import hosts
Fang Deng5d518f42013-08-02 14:04:32 -070024from autotest_lib.client.common_lib.cros import retry
Christopher Wileycef1f902014-06-19 11:11:23 -070025from autotest_lib.client.common_lib.cros.network import ping_runner
Richard Barnette9a26ad62016-06-10 12:03:08 -070026from autotest_lib.server.cros.servo import servo
Richard Barnetted31580e2018-05-14 19:58:00 +000027from autotest_lib.server.hosts import servo_repair
Garry Wangebc015b2019-06-06 17:45:06 -070028from autotest_lib.server.hosts import base_servohost
Dan Shi5e2efb72017-02-07 11:40:23 -080029
Fang Deng5d518f42013-08-02 14:04:32 -070030
Simran Basi0739d682015-02-25 16:22:56 -080031# Names of the host attributes in the database that represent the values for
32# the servo_host and servo_port for a servo connected to the DUT.
33SERVO_HOST_ATTR = 'servo_host'
34SERVO_PORT_ATTR = 'servo_port'
Richard Barnettee519dcd2016-08-15 17:37:17 -070035SERVO_BOARD_ATTR = 'servo_board'
Nick Sanders2f3c9852018-10-24 12:10:24 -070036# Model is inferred from host labels.
37SERVO_MODEL_ATTR = 'servo_model'
Kevin Cheng643ce8a2016-09-15 15:42:12 -070038SERVO_SERIAL_ATTR = 'servo_serial'
Prathmesh Prabhucba44292018-08-28 17:44:45 -070039SERVO_ATTR_KEYS = (
40 SERVO_BOARD_ATTR,
41 SERVO_HOST_ATTR,
42 SERVO_PORT_ATTR,
43 SERVO_SERIAL_ATTR,
44)
Simran Basi0739d682015-02-25 16:22:56 -080045
Garry Wangc1288cf2019-12-17 14:58:00 -080046# Timeout value for stop/start servod process.
47SERVOD_TEARDOWN_TIMEOUT = 3
48SERVOD_QUICK_STARTUP_TIMEOUT = 20
49SERVOD_STARTUP_TIMEOUT = 60
50
Garry Wangd7367482020-02-27 13:52:40 -080051# pools that support dual v4. (go/cros-fw-lab-strategy)
52POOLS_SUPPORT_DUAL_V4 = {'faft-cr50',
53 'faft-cr50-experimental',
54 'faft-cr50-tot',
55 'faft-cr50-debug',
56 'faft_cr50_debug'
57 'faft-pd-debug',
58 'faft_pd_debug'}
59
Dan Shi3b2adf62015-09-02 17:46:54 -070060_CONFIG = global_config.global_config
xixuan6cf6d2f2016-01-29 15:29:00 -080061ENABLE_SSH_TUNNEL_FOR_SERVO = _CONFIG.get_config_value(
62 'CROS', 'enable_ssh_tunnel_for_servo', type=bool, default=False)
Simran Basi0739d682015-02-25 16:22:56 -080063
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -070064AUTOTEST_BASE = _CONFIG.get_config_value(
65 'SCHEDULER', 'drone_installation_directory',
66 default='/usr/local/autotest')
67
Otabek Kasimovcc9738e2020-02-14 16:17:15 -080068SERVO_STATE_LABEL_PREFIX = 'servo_state'
69SERVO_STATE_WORKING = 'WORKING'
70SERVO_STATE_BROKEN = 'BROKEN'
Otabek Kasimov7267a7a2020-03-04 11:18:45 -080071SERVO_STATE_NOT_CONNECTED = 'NOT_CONNECTED'
Otabek Kasimova7ba91a2020-03-09 08:31:01 -070072SERVO_STATE_WRONG_CONFIG = 'WRONG_CONFIG'
Otabek Kasimov7267a7a2020-03-04 11:18:45 -080073SERVO_STATE_UNKNOWN = 'UNKNOWN'
Fang Deng5d518f42013-08-02 14:04:32 -070074
Otabek Kasimova7ba91a2020-03-09 08:31:01 -070075
Garry Wangebc015b2019-06-06 17:45:06 -070076class ServoHost(base_servohost.BaseServoHost):
77 """Host class for a servo host(e.g. beaglebone, labstation)
Dana Goyette0b6e6402019-10-04 11:09:24 -070078 that with a servo instance for a specific port.
79
80 @type _servo: servo.Servo | None
81 """
Fang Deng5d518f42013-08-02 14:04:32 -070082
Raul E Rangel52ca2e82018-07-03 14:10:14 -060083 DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999'))
Richard Barnette9a26ad62016-06-10 12:03:08 -070084
Dan Shie5b3c512014-08-21 12:12:09 -070085 # Timeout for initializing servo signals.
Wai-Hong Tam37b6ed32017-09-19 15:52:39 -070086 INITIALIZE_SERVO_TIMEOUT_SECS = 60
Richard Barnette9a26ad62016-06-10 12:03:08 -070087
xixuan6cf6d2f2016-01-29 15:29:00 -080088 # Ready test function
89 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070090
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -080091 # Directory prefix on the servo host where the servod logs are stored.
92 SERVOD_LOG_PREFIX = '/var/log/servod'
93
94 # Exit code to use when symlinks for servod logs are not found.
95 NO_SYMLINKS_CODE = 9
96
97 # Directory in the job's results directory to dump the logs into.
98 LOG_DIR = 'servod'
99
100 # Prefix for joint loglevel files in the logs.
101 JOINT_LOG_PREFIX = 'log'
102
103 # Regex group to extract timestamp from logfile name.
104 TS_GROUP = 'ts'
105
106 # This regex is used to extract the timestamp from servod logs.
107 # files always start with log.
108 TS_RE = (r'log.'
109 # The timestamp is of format %Y-%m-%d--%H-%M-%S.MS
110 r'(?P<%s>\d{4}(\-\d{2}){2}\-(-\d{2}){3}.\d{3})'
111 # The loglevel is optional depending on labstation version.
112 r'(.(INFO|DEBUG|WARNING))?' % TS_GROUP)
113 TS_EXTRACTOR = re.compile(TS_RE)
114
115 # Regex group to extract MCU name from logline in servod logs.
116 MCU_GROUP = 'mcu'
117
118 # Regex group to extract logline from MCU logline in servod logs.
119 LINE_GROUP = 'line'
120
121 # This regex is used to extract the mcu and the line content from an
122 # MCU logline in servod logs. e.g. EC or servo_v4 console logs.
123 # Here is an example log-line:
124 #
125 # 2020-01-23 13:15:12,223 - servo_v4 - EC3PO.Console - DEBUG -
126 # console.py:219:LogConsoleOutput - /dev/pts/9 - cc polarity: cc1
127 #
128 # Here is conceptually how they are formatted:
129 #
130 # <time> - <MCU> - EC3PO.Console - <LVL> - <file:line:func> - <pts> -
131 # <output>
132 #
133 # The log format starts with a timestamp
134 MCU_RE = (r'[\d\-]+ [\d:,]+ '
135 # The mcu that is logging this is next.
136 r'- (?P<%s>\w+) - '
137 # Next, we have more log outputs before the actual line.
138 # Information about the file line, logging function etc.
139 # Anchor on EC3PO Console, LogConsoleOutput and dev/pts.
140 # NOTE: if the log format changes, this regex needs to be
141 # adjusted.
142 r'EC3PO\.Console[\s\-\w\d:.]+LogConsoleOutput - /dev/pts/\d+ - '
143 # Lastly, we get the MCU's console line.
144 r'(?P<%s>.+$)' % (MCU_GROUP, LINE_GROUP))
145 MCU_EXTRACTOR = re.compile(MCU_RE)
146
147 # Suffix to identify compressed logfiles.
148 COMPRESSION_SUFFIX = '.tbz2'
149
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800150 def _init_attributes(self):
151 self._servo_state = None
152 self.servo_port = None
153 self.servo_board = None
154 self.servo_model = None
155 self.servo_serial = None
156 self._servo = None
157 self._servod_server_proxy = None
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800158 # Flag to make sure that multiple calls to close do not result in the
159 # logic executing multiple times.
160 self._closed = False
Fang Deng5d518f42013-08-02 14:04:32 -0700161
Richard Barnette17bfc6c2016-08-04 18:41:43 -0700162 def _initialize(self, servo_host='localhost',
Richard Barnettee519dcd2016-08-15 17:37:17 -0700163 servo_port=DEFAULT_PORT, servo_board=None,
Nick Sanders2f3c9852018-10-24 12:10:24 -0700164 servo_model=None, servo_serial=None, is_in_lab=None,
165 *args, **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -0700166 """Initialize a ServoHost instance.
167
168 A ServoHost instance represents a host that controls a servo.
169
170 @param servo_host: Name of the host where the servod process
171 is running.
Raul E Rangel52ca2e82018-07-03 14:10:14 -0600172 @param servo_port: Port the servod process is listening on. Defaults
173 to the SERVOD_PORT environment variable if set,
174 otherwise 9999.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700175 @param servo_board: Board that the servo is connected to.
Nick Sanders2f3c9852018-10-24 12:10:24 -0700176 @param servo_model: Model that the servo is connected to.
Dan Shi4d478522014-02-14 13:46:32 -0800177 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
178 to None, for which utils.host_is_in_lab_zone will be
179 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -0700180
181 """
182 super(ServoHost, self)._initialize(hostname=servo_host,
Garry Wangebc015b2019-06-06 17:45:06 -0700183 is_in_lab=is_in_lab, *args, **dargs)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800184 self._init_attributes()
Richard Barnette42f4db92018-08-23 15:05:15 -0700185 self.servo_port = int(servo_port)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700186 self.servo_board = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -0700187 self.servo_model = servo_model
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700188 self.servo_serial = servo_serial
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800189
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800190 # The location of the log files on the servo host for this instance.
191 self.remote_log_dir = '%s_%s' % (self.SERVOD_LOG_PREFIX,
192 self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700193 # Path of the servo host lock file.
194 self._lock_file = (self.TEMP_FILE_DIR + str(self.servo_port)
195 + self.LOCK_FILE_POSTFIX)
196 # File path to declare a reboot request.
197 self._reboot_file = (self.TEMP_FILE_DIR + str(self.servo_port)
198 + self.REBOOT_FILE_POSTFIX)
199
200 # Lock the servo host if it's an in-lab labstation to prevent other
201 # task to reboot it until current task completes. We also wait and
202 # make sure the labstation is up here, in the case of the labstation is
203 # in the middle of reboot.
Garry Wang7c00b0f2019-06-25 17:28:17 -0700204 self._is_locked = False
Garry Wang42b4d862019-06-25 15:50:49 -0700205 if (self.wait_up(self.REBOOT_TIMEOUT) and self.is_in_lab()
206 and self.is_labstation()):
Garry Wang79e9af62019-06-12 15:19:19 -0700207 self._lock()
Garry Wangebc015b2019-06-06 17:45:06 -0700208
Richard Barnette9a26ad62016-06-10 12:03:08 -0700209 self._repair_strategy = (
210 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -0700211
Richard Barnette9a26ad62016-06-10 12:03:08 -0700212 def connect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700213 """Establish a connection to the servod server on this host.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700214
215 Initializes `self._servo` and then verifies that all network
216 connections are working. This will create an ssh tunnel if
217 it's required.
218
219 As a side effect of testing the connection, all signals on the
220 target servo are reset to default values, and the USB stick is
221 set to the neutral (off) position.
222 """
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700223 servo_obj = servo.Servo(servo_host=self, servo_serial=self.servo_serial)
Kuang-che Wu05763f52019-08-30 16:48:21 +0800224 self._servo = servo_obj
Richard Barnette9a26ad62016-06-10 12:03:08 -0700225 timeout, _ = retry.timeout(
226 servo_obj.initialize_dut,
227 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
228 if timeout:
229 raise hosts.AutoservVerifyError(
230 'Servo initialize timed out.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700231
232
233 def disconnect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700234 """Disconnect our servo if it exists.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700235
236 If we've previously successfully connected to our servo,
237 disconnect any established ssh tunnel, and set `self._servo`
238 back to `None`.
239 """
240 if self._servo:
241 # N.B. This call is safe even without a tunnel:
242 # rpc_server_tracker.disconnect() silently ignores
243 # unknown ports.
244 self.rpc_server_tracker.disconnect(self.servo_port)
245 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700246
Garry Wangc1288cf2019-12-17 14:58:00 -0800247
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800248 def _create_servod_server_proxy(self):
249 """Create a proxy that can be used to communicate with servod server.
Fang Deng5d518f42013-08-02 14:04:32 -0700250
251 @returns: An xmlrpclib.ServerProxy that is connected to the servod
252 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700253 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700254 if ENABLE_SSH_TUNNEL_FOR_SERVO and not self.is_localhost():
255 return self.rpc_server_tracker.xmlrpc_connect(
256 None, self.servo_port,
257 ready_test_name=self.SERVO_READY_METHOD,
Allen Li2b1a8992018-11-27 14:17:18 -0800258 timeout_seconds=60,
Allen Li556f4532018-12-03 18:11:23 -0800259 request_timeout_seconds=3600)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700260 else:
261 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
262 return xmlrpclib.ServerProxy(remote)
Fang Deng5d518f42013-08-02 14:04:32 -0700263
264
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800265 def get_servod_server_proxy(self):
266 """Return a cached proxy if exists; otherwise, create a new one.
267
268 @returns: An xmlrpclib.ServerProxy that is connected to the servod
269 server on the host.
270 """
271 # Single-threaded execution, no race
272 if self._servod_server_proxy is None:
273 self._servod_server_proxy = self._create_servod_server_proxy()
274 return self._servod_server_proxy
275
276
Richard Barnette1edbb162016-11-01 11:47:50 -0700277 def verify(self, silent=False):
278 """Update the servo host and verify it's in a good state.
279
280 @param silent If true, suppress logging in `status.log`.
281 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700282 message = 'Beginning verify for servo host %s port %s serial %s'
283 message %= (self.hostname, self.servo_port, self.servo_serial)
284 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700285 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700286 self._repair_strategy.verify(self, silent)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800287 self._servo_state = SERVO_STATE_WORKING
288 self.record('INFO', None, None, 'ServoHost verify set servo_state as WORKING')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700289 except:
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800290 self._servo_state = SERVO_STATE_BROKEN
291 self.record('INFO', None, None, 'ServoHost verify set servo_state as BROKEN')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700292 self.disconnect_servo()
Garry Wangc1288cf2019-12-17 14:58:00 -0800293 self.stop_servod()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700294 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700295
296
Richard Barnette1edbb162016-11-01 11:47:50 -0700297 def repair(self, silent=False):
298 """Attempt to repair servo host.
299
300 @param silent If true, suppress logging in `status.log`.
301 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700302 message = 'Beginning repair for servo host %s port %s serial %s'
303 message %= (self.hostname, self.servo_port, self.servo_serial)
304 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700305 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700306 self._repair_strategy.repair(self, silent)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800307 self._servo_state = SERVO_STATE_WORKING
308 self.record('INFO', None, None, 'ServoHost repair set servo_state as WORKING')
Garry Wang464ff1e2019-07-18 17:20:34 -0700309 # If target is a labstation then try to withdraw any existing
310 # reboot request created by this servo because it passed repair.
311 if self.is_labstation():
312 self.withdraw_reboot_request()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700313 except:
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800314 self._servo_state = SERVO_STATE_BROKEN
315 self.record('INFO', None, None, 'ServoHost repair set servo_state as BROKEN')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700316 self.disconnect_servo()
Garry Wangc1288cf2019-12-17 14:58:00 -0800317 self.stop_servod()
Richard Barnette9a26ad62016-06-10 12:03:08 -0700318 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700319
320
Dan Shi4d478522014-02-14 13:46:32 -0800321 def get_servo(self):
322 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700323
Dan Shi4d478522014-02-14 13:46:32 -0800324 @return: a servo.Servo object.
Dana Goyette353d1d92019-06-27 10:43:59 -0700325 @rtype: autotest_lib.server.cros.servo.servo.Servo
Fang Deng5d518f42013-08-02 14:04:32 -0700326 """
Dan Shi4d478522014-02-14 13:46:32 -0800327 return self._servo
328
329
Garry Wang79e9af62019-06-12 15:19:19 -0700330 def request_reboot(self):
331 """Request servohost to be rebooted when it's safe to by touch a file.
332 """
333 logging.debug('Request to reboot servohost %s has been created by '
Garry Wang464ff1e2019-07-18 17:20:34 -0700334 'servo with port # %s', self.hostname, self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700335 self.run('touch %s' % self._reboot_file, ignore_status=True)
336
337
Garry Wang464ff1e2019-07-18 17:20:34 -0700338 def withdraw_reboot_request(self):
339 """Withdraw a servohost reboot request if exists by remove the flag
340 file.
341 """
342 logging.debug('Withdrawing request to reboot servohost %s that created'
343 ' by servo with port # %s if exists.',
344 self.hostname, self.servo_port)
345 self.run('rm -f %s' % self._reboot_file, ignore_status=True)
346
347
Garry Wangc1288cf2019-12-17 14:58:00 -0800348 def start_servod(self, quick_startup=False):
349 """Start the servod process on servohost.
350 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800351 # Skip if running on the localhost.(crbug.com/1038168)
352 if self.is_localhost():
353 logging.debug("Servohost is a localhost, skipping start servod.")
354 return
355
356 cmd = 'start servod'
Garry Wangc1288cf2019-12-17 14:58:00 -0800357 if self.servo_board:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800358 cmd += ' BOARD=%s' % self.servo_board
Garry Wangc1288cf2019-12-17 14:58:00 -0800359 if self.servo_model:
360 cmd += ' MODEL=%s' % self.servo_model
Garry Wangc1288cf2019-12-17 14:58:00 -0800361 else:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800362 logging.warning('Board for DUT is unknown; starting servod'
363 ' assuming a pre-configured board.')
364
365 cmd += ' PORT=%d' % self.servo_port
366 if self.servo_serial:
367 cmd += ' SERIAL=%s' % self.servo_serial
Garry Wangd7367482020-02-27 13:52:40 -0800368
369 # Start servod with dual_v4 if the DUT/servo from designated pools.
370 dut_host_info = self.get_dut_host_info()
371 if dut_host_info:
372 if bool(dut_host_info.pools & POOLS_SUPPORT_DUAL_V4):
373 logging.debug('The DUT is detected in following designated'
374 ' pools %s,starting servod with DUAL_V4 option.',
375 POOLS_SUPPORT_DUAL_V4)
376 cmd += ' DUAL_V4=1'
377
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800378 # Remove the symbolic links from the logs. This helps ensure that
379 # a failed servod instantiation does not cause us to grab old logs
380 # by mistake.
381 self.remove_latest_log_symlinks()
Garry Wangcdd27b22020-01-13 14:59:11 -0800382 self.run(cmd, timeout=60)
Garry Wangc1288cf2019-12-17 14:58:00 -0800383
384 # There's a lag between when `start servod` completes and when
385 # the _ServodConnectionVerifier trigger can actually succeed.
386 # The call to time.sleep() below gives time to make sure that
387 # the trigger won't fail after we return.
388
389 # Normally servod on servo_v3 and labstation take ~10 seconds to ready,
390 # But in the rare case all servo on a labstation are in heavy use they
391 # may take ~30 seconds. So the timeout value will double these value,
392 # and we'll try quick start up when first time initialize servohost,
393 # and use standard start up timeout in repair.
394 if quick_startup:
395 timeout = SERVOD_QUICK_STARTUP_TIMEOUT
396 else:
397 timeout = SERVOD_STARTUP_TIMEOUT
398 logging.debug('Wait %s seconds for servod process fully up.', timeout)
399 time.sleep(timeout)
400
401
402 def stop_servod(self):
403 """Stop the servod process on servohost.
404 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800405 # Skip if running on the localhost.(crbug.com/1038168)
406 if self.is_localhost():
407 logging.debug("Servohost is a localhost, skipping stop servod.")
408 return
409
Garry Wangc1288cf2019-12-17 14:58:00 -0800410 logging.debug('Stopping servod on port %s', self.servo_port)
Garry Wangcdd27b22020-01-13 14:59:11 -0800411 self.run('stop servod PORT=%d' % self.servo_port,
412 timeout=60, ignore_status=True)
Garry Wangc1288cf2019-12-17 14:58:00 -0800413 logging.debug('Wait %s seconds for servod process fully teardown.',
414 SERVOD_TEARDOWN_TIMEOUT)
415 time.sleep(SERVOD_TEARDOWN_TIMEOUT)
416
417
418 def restart_servod(self, quick_startup=False):
419 """Restart the servod process on servohost.
420 """
421 self.stop_servod()
422 self.start_servod(quick_startup)
423
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800424 def _extract_compressed_logs(self, logdir, relevant_files):
425 """Decompress servod logs in |logdir|.
426
427 @param logdir: directory containing compressed servod logs.
428 @param relevant_files: list of files in |logdir| to consider.
429
430 @returns: tuple, (tarfiles, files) where
431 tarfiles: list of the compressed filenames that have been
432 extracted and deleted
433 files: list of the uncompressed files that were generated
434 """
435 # For all tar-files, first extract them to the directory, and
436 # then let the common flow handle them.
437 tarfiles = [cf for cf in relevant_files if
438 cf.endswith(self.COMPRESSION_SUFFIX)]
439 files = []
440 for f in tarfiles:
441 norm_name = os.path.basename(f)[:-len(self.COMPRESSION_SUFFIX)]
442 with tarfile.open(f) as tf:
443 # Each tarfile has only one member, as
444 # that's the compressed log.
445 member = tf.members[0]
446 # Manipulate so that it only extracts the basename, and not
447 # the directories etc.
448 member.name = norm_name
449 files.append(os.path.join(logdir, member.name))
450 tf.extract(member, logdir)
451 # File has been extracted: remove the compressed file.
452 os.remove(f)
453 return tarfiles, files
454
455 def _extract_mcu_logs(self, log_subdir):
456 """Extract MCU (EC, Cr50, etc) console output from servod debug logs.
457
458 Using the MCU_EXTRACTOR regex (above) extract and split out MCU console
459 lines from the logs to generate invidiual console logs e.g. after
460 this method, you can find an ec.txt and servo_v4.txt in |log_dir| if
461 those MCUs had any console input/output.
462
463 @param log_subdir: directory with log.DEBUG.txt main servod debug logs.
464 """
465 # Extract the MCU for each one. The MCU logs are only in the .DEBUG
466 # files
467 mcu_lines_file = os.path.join(log_subdir, 'log.DEBUG.txt')
468 if not os.path.exists(mcu_lines_file):
469 logging.info('No DEBUG logs found to extract MCU logs from.')
470 return
471 mcu_files = {}
472 mcu_file_template = '%s.txt'
473 with open(mcu_lines_file, 'r') as f:
474 for line in f:
475 match = self.MCU_EXTRACTOR.match(line)
476 if match:
477 mcu = match.group(self.MCU_GROUP).lower()
478 line = match.group(self.LINE_GROUP)
479 if mcu not in mcu_files:
480 mcu_file = os.path.join(log_subdir,
481 mcu_file_template % mcu)
482 mcu_files[mcu] = open(mcu_file, 'a')
483 fd = mcu_files[mcu]
484 fd.write(line + '\n')
485 for f in mcu_files:
486 mcu_files[f].close()
487
488
489 def remove_latest_log_symlinks(self):
490 """Remove the conveninence symlinks 'latest' servod logs."""
491 symlink_wildcard = '%s/latest*' % self.remote_log_dir
492 cmd = 'rm ' + symlink_wildcard
493 self.run(cmd, stderr_tee=None, ignore_status=True)
494
495 def grab_logs(self, outdir):
496 """Retrieve logs from servo_host to |outdir|/servod_{port}.{ts}/.
497
498 This method first collects all logs on the servo_host side pertaining
499 to this servod instance (port, instatiation). It glues them together
500 into combined log.[level].txt files and extracts all available MCU
501 console I/O from the logs into individual files e.g. servo_v4.txt
502
503 All the output can be found in a directory inside |outdir| that
504 this generates based on |LOG_DIR|, the servod port, and the instance
505 timestamp on the servo_host side.
506
507 @param outdir: directory to create a subdirectory into to place the
508 servod logs into.
509 """
510 # First, extract the timestamp. This cmd gives the real filename of
511 # the latest aka current log file.
512 cmd = ('if [ -f %(dir)s/latest.DEBUG ];'
513 'then realpath %(dir)s/latest.DEBUG;'
514 'elif [ -f %(dir)s/latest ];'
515 'then realpath %(dir)s/latest;'
516 'else exit %(code)d;'
517 'fi' % {'dir': self.remote_log_dir,
518 'code': self.NO_SYMLINKS_CODE})
519 res = self.run(cmd, stderr_tee=None, ignore_status=True)
520 if res.exit_status != 0:
521 if res.exit_status == self.NO_SYMLINKS_CODE:
522 logging.warning('servod log latest symlinks not found. '
523 'This is likely due to an error starting up '
524 'servod. Ignoring..')
525 else:
526 logging.warning('Failed to find servod logs on servo host.')
527 logging.warning(res.stderr.strip())
528 return
529 fname = os.path.basename(res.stdout.strip())
530 # From the fname, ought to extract the timestamp using the TS_EXTRACTOR
Ruben Rodriguez Buchillone9aa2b02020-03-04 12:14:28 -0800531 ts_match = self.TS_EXTRACTOR.match(fname)
532 if not ts_match:
533 logging.warning('Failed to extract timestamp from servod log file '
534 '%r. Skipping. The servo host is using outdated '
535 'servod logging and needs to be updated.', fname)
536 return
537 instance_ts = ts_match.group(self.TS_GROUP)
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800538 # Create the local results log dir.
539 log_dir = os.path.join(outdir, '%s_%s.%s' % (self.LOG_DIR,
540 str(self.servo_port),
541 instance_ts))
542 logging.info('Saving servod logs to %s.', log_dir)
543 os.mkdir(log_dir)
544 # Now, get all files with that timestamp.
545 cmd = 'find %s -maxdepth 1 -name "log.%s*"' % (self.remote_log_dir,
546 instance_ts)
547 res = self.run(cmd, stderr_tee=None, ignore_status=True)
548 files = res.stdout.strip().split()
549 try:
550 self.get_file(files, log_dir, try_rsync=False)
551
552 except error.AutoservRunError as e:
553 result = e.result_obj
554 if result.exit_status != 0:
555 stderr = result.stderr.strip()
556 logging.warning("Couldn't retrieve servod logs. Ignoring: %s",
557 stderr or '\n%s' % result)
558 return
559 local_files = [os.path.join(log_dir, f) for f in os.listdir(log_dir)]
560 # TODO(crrev.com/c/1793030): remove no-level case once CL is pushed
561 for level_name in ('DEBUG', 'INFO', 'WARNING', ''):
562 # Create the joint files for each loglevel. i.e log.DEBUG
563 joint_file = self.JOINT_LOG_PREFIX
564 if level_name:
565 joint_file = '%s.%s' % (self.JOINT_LOG_PREFIX, level_name)
566 # This helps with some online tools to avoid complaints about an
567 # unknown filetype.
568 joint_file = joint_file + '.txt'
569 joint_path = os.path.join(log_dir, joint_file)
570 files = [f for f in local_files if level_name in f]
571 if not files:
572 # TODO(crrev.com/c/1793030): remove no-level case once CL
573 # is pushed
574 continue
575 # Extract compressed logs if any.
576 compressed, extracted = self._extract_compressed_logs(log_dir,
577 files)
578 files = list(set(files) - set(compressed))
579 files.extend(extracted)
580 # Need to sort. As they all share the same timestamp, and
581 # loglevel, the index itself is sufficient. The highest index
582 # is the oldest file, therefore we need a descending sort.
583 def sortkey(f, level=level_name):
584 """Custom sortkey to sort based on rotation number int."""
585 if f.endswith(level_name): return 0
586 return int(f.split('.')[-1])
587
588 files.sort(reverse=True, key=sortkey)
589 # Just rename the first file rather than building from scratch.
590 os.rename(files[0], joint_path)
591 with open(joint_path, 'a') as joint_f:
592 for logfile in files[1:]:
593 # Transfer the file to the joint file line by line.
594 with open(logfile, 'r') as log_f:
595 for line in log_f:
596 joint_f.write(line)
597 # File has been written over. Delete safely.
598 os.remove(logfile)
599 # Need to remove all files form |local_files| so we don't
600 # analyze them again.
601 local_files = list(set(local_files) - set(files) - set(compressed))
602 # Lastly, extract MCU logs from the joint logs.
603 self._extract_mcu_logs(log_dir)
604
Garry Wangc1288cf2019-12-17 14:58:00 -0800605
Garry Wang79e9af62019-06-12 15:19:19 -0700606 def _lock(self):
607 """lock servohost by touching a file.
608 """
609 logging.debug('Locking servohost %s by touching %s file',
610 self.hostname, self._lock_file)
611 self.run('touch %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700612 self._is_locked = True
Garry Wang79e9af62019-06-12 15:19:19 -0700613
614
615 def _unlock(self):
616 """Unlock servohost by removing the lock file.
617 """
618 logging.debug('Unlocking servohost by removing %s file',
619 self._lock_file)
620 self.run('rm %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -0700621 self._is_locked = False
Garry Wang79e9af62019-06-12 15:19:19 -0700622
623
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700624 def close(self):
Congbin Guofc3b8962019-03-22 17:38:46 -0700625 """Close the associated servo and the host object."""
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800626 if self._closed:
627 logging.debug('ServoHost is already closed.')
628 return
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700629 if self._servo:
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800630 outdir = None if not self.job else self.job.resultdir
Congbin Guo2e5e2a22018-07-27 10:32:48 -0700631 # In some cases when we run as lab-tools, the job object is None.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800632 self._servo.close(outdir)
633
634 if self.job and not self.is_localhost():
635 # Grab all logs from this servod instance before stopping servod.
636 # TODO(crbug.com/1011516): once enabled, remove the check against
637 # localhost and instead check against log-rotiation enablement.
638 try:
639 self.grab_logs(self.job.resultdir)
640 except error.AutoservRunError as e:
641 logging.info('Failed to grab servo logs due to: %s. '
642 'This error is forgiven.', str(e))
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700643
Garry Wang7c00b0f2019-06-25 17:28:17 -0700644 if self._is_locked:
645 # Remove the lock if the servohost has been locked.
Garry Wang79e9af62019-06-12 15:19:19 -0700646 try:
647 self._unlock()
648 except error.AutoservSSHTimeout:
649 logging.error('Unlock servohost failed due to ssh timeout.'
650 ' It may caused by servohost went down during'
651 ' the task.')
Garry Wangc1288cf2019-12-17 14:58:00 -0800652 # We want always stop servod after task to minimum the impact of bad
653 # servod process interfere other servods.(see crbug.com/1028665)
Garry Wang4c624bc2020-01-27 16:34:43 -0800654 try:
655 self.stop_servod()
656 except error.AutoservRunError as e:
657 logging.info("Failed to stop servod due to:\n%s\n"
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800658 "This error is forgiven.", str(e))
Garry Wangc1288cf2019-12-17 14:58:00 -0800659
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700660 super(ServoHost, self).close()
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800661 # Mark closed.
662 self._closed = True
Congbin Guoa1f9cba2018-07-03 11:36:59 -0700663
664
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800665 def get_servo_state(self):
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700666 if self._servo_state is None:
667 return SERVO_STATE_UNKNOWN
668 return self._servo_state
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800669
670
Richard Barnetteea3e4602016-06-10 12:36:41 -0700671def make_servo_hostname(dut_hostname):
672 """Given a DUT's hostname, return the hostname of its servo.
673
674 @param dut_hostname: hostname of a DUT.
675
676 @return hostname of the DUT's servo.
677
678 """
679 host_parts = dut_hostname.split('.')
680 host_parts[0] = host_parts[0] + '-servo'
681 return '.'.join(host_parts)
682
683
684def servo_host_is_up(servo_hostname):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700685 """Given a servo host name, return if it's up or not.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700686
687 @param servo_hostname: hostname of the servo host.
688
689 @return True if it's up, False otherwise
690 """
691 # Technically, this duplicates the SSH ping done early in the servo
692 # proxy initialization code. However, this ping ends in a couple
693 # seconds when if fails, rather than the 60 seconds it takes to decide
694 # that an SSH ping has timed out. Specifically, that timeout happens
695 # when our servo DNS name resolves, but there is no host at that IP.
696 logging.info('Pinging servo host at %s', servo_hostname)
697 ping_config = ping_runner.PingConfig(
698 servo_hostname, count=3,
699 ignore_result=True, ignore_status=True)
700 return ping_runner.PingRunner().ping(ping_config).received > 0
701
702
Richard Barnettee519dcd2016-08-15 17:37:17 -0700703def _map_afe_board_to_servo_board(afe_board):
704 """Map a board we get from the AFE to a servo appropriate value.
705
706 Many boards are identical to other boards for servo's purposes.
707 This function makes that mapping.
708
709 @param afe_board string board name received from AFE.
710 @return board we expect servo to have.
711
712 """
713 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
714 BOARD_MAP = {'gizmo': 'panther'}
715 mapped_board = afe_board
716 if afe_board in BOARD_MAP:
717 mapped_board = BOARD_MAP[afe_board]
718 else:
719 for suffix in KNOWN_SUFFIXES:
720 if afe_board.endswith(suffix):
721 mapped_board = afe_board[0:-len(suffix)]
722 break
723 if mapped_board != afe_board:
724 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
725 return mapped_board
726
727
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700728def get_servo_args_for_host(dut_host):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700729 """Return servo data associated with a given DUT.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700730
Richard Barnetteea3e4602016-06-10 12:36:41 -0700731 @param dut_host Instance of `Host` on which to find the servo
732 attributes.
Prathmesh Prabhuf605dd32018-08-28 17:09:04 -0700733 @return `servo_args` dict with host and an optional port.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700734 """
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700735 info = dut_host.host_info_store.get()
736 servo_args = {k: v for k, v in info.attributes.iteritems()
737 if k in SERVO_ATTR_KEYS}
Richard Barnetteea3e4602016-06-10 12:36:41 -0700738
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700739 if SERVO_PORT_ATTR in servo_args:
740 try:
741 servo_args[SERVO_PORT_ATTR] = int(servo_args[SERVO_PORT_ATTR])
742 except ValueError:
743 logging.error('servo port is not an int: %s',
744 servo_args[SERVO_PORT_ATTR])
745 # Reset servo_args because we don't want to use an invalid port.
746 servo_args.pop(SERVO_HOST_ATTR, None)
747
748 if info.board:
749 servo_args[SERVO_BOARD_ATTR] = _map_afe_board_to_servo_board(info.board)
Nick Sanders2f3c9852018-10-24 12:10:24 -0700750 if info.model:
751 servo_args[SERVO_MODEL_ATTR] = info.model
Prathmesh Prabhu6f5f6362018-09-05 17:20:31 -0700752 return servo_args if SERVO_HOST_ATTR in servo_args else None
Richard Barnetteea3e4602016-06-10 12:36:41 -0700753
754
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700755def _tweak_args_for_ssp_moblab(servo_args):
756 if servo_args[SERVO_HOST_ATTR] in ['localhost', '127.0.0.1']:
757 servo_args[SERVO_HOST_ATTR] = _CONFIG.get_config_value(
758 'SSP', 'host_container_ip', type=str, default=None)
759
760
Dan Shi023aae32016-05-25 11:13:01 -0700761def create_servo_host(dut, servo_args, try_lab_servo=False,
Gregory Nisbetde13e2a2019-12-09 22:44:00 -0800762 try_servo_repair=False, dut_host_info=None):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700763 """Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -0800764
Richard Barnette9a26ad62016-06-10 12:03:08 -0700765 This function attempts to create and verify or repair a `ServoHost`
766 object for a servo connected to the given `dut`, subject to various
767 constraints imposed by the parameters:
768 * When the `servo_args` parameter is not `None`, a servo
769 host must be created, and must be checked with `repair()`.
770 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
771 true:
772 * If `try_servo_repair` is true, then create a servo host and
773 check it with `repair()`.
774 * Otherwise, if the servo responds to `ping` then create a
775 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800776
Richard Barnette9a26ad62016-06-10 12:03:08 -0700777 In cases where `servo_args` was not `None`, repair failure
778 exceptions are passed back to the caller; otherwise, exceptions
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700779 are logged and then discarded. Note that this only happens in cases
780 where we're called from a test (not special task) control file that
781 has an explicit dependency on servo. In that case, we require that
782 repair not write to `status.log`, so as to avoid polluting test
783 results.
784
785 TODO(jrbarnette): The special handling for servo in test control
786 files is a thorn in my flesh; I dearly hope to see it cut out before
787 my retirement.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700788
789 Parameters for a servo host consist of a host name, port number, and
790 DUT board, and are determined from one of these sources, in order of
791 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -0700792 * Servo attributes from the `dut` parameter take precedence over
793 all other sources of information.
794 * If a DNS entry for the servo based on the DUT hostname exists in
795 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -0700796 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700797 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -0700798 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -0700799
800 @param dut An instance of `Host` from which to take
801 servo parameters (if available).
802 @param servo_args A dictionary with servo parameters to use if
803 they can't be found from `dut`. If this
804 argument is supplied, unrepaired exceptions
805 from `verify()` will be passed back to the
806 caller.
807 @param try_lab_servo If not true, servo host creation will be
808 skipped unless otherwise required by the
809 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700810 @param try_servo_repair If true, check a servo host with
811 `repair()` instead of `verify()`.
Dan Shi4d478522014-02-14 13:46:32 -0800812
813 @returns: A ServoHost object or None. See comments above.
814
815 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700816 servo_dependency = servo_args is not None
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700817 if dut is not None and (try_lab_servo or servo_dependency):
Prathmesh Prabhub4810232018-09-07 13:24:08 -0700818 servo_args_override = get_servo_args_for_host(dut)
Richard Barnetteea3e4602016-06-10 12:36:41 -0700819 if servo_args_override is not None:
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -0700820 if utils.in_moblab_ssp():
821 _tweak_args_for_ssp_moblab(servo_args_override)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700822 logging.debug(
823 'Overriding provided servo_args (%s) with arguments'
824 ' determined from the host (%s)',
825 servo_args,
826 servo_args_override,
827 )
Richard Barnetteea3e4602016-06-10 12:36:41 -0700828 servo_args = servo_args_override
Prathmesh Prabhucba44292018-08-28 17:44:45 -0700829
Richard Barnetteea3e4602016-06-10 12:36:41 -0700830 if servo_args is None:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700831 logging.debug('No servo_args provided, and failed to find overrides.')
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700832 if try_lab_servo or servo_dependency:
833 return None, SERVO_STATE_NOT_CONNECTED
834 else:
835 # For regular test case which not required the servo
836 return None, None
837
838 servo_hostname = servo_args.get(SERVO_HOST_ATTR)
839 servo_port = servo_args.get(SERVO_PORT_ATTR)
840 if not _is_servo_host_information_exist(servo_hostname, servo_port):
841 logging.debug(
842 'Servo connection info missed hostname: %s , port: %s',
843 servo_hostname, servo_port)
844 return None, SERVO_STATE_NOT_CONNECTED
845 if not is_servo_host_information_valid(servo_hostname, servo_port):
846 logging.debug(
847 'Servo connection info is incorrect hostname: %s , port: %s',
848 servo_hostname, servo_port)
849 return None, SERVO_STATE_WRONG_CONFIG
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700850 if (not servo_dependency and not try_servo_repair and
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700851 not servo_host_is_up(servo_hostname)):
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700852 logging.debug('ServoHost is not up.')
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700853 return None, SERVO_STATE_BROKEN
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700854
Garry Wangebc015b2019-06-06 17:45:06 -0700855 newhost = ServoHost(**servo_args)
Garry Wangcdd27b22020-01-13 14:59:11 -0800856 try:
857 newhost.restart_servod(quick_startup=True)
858 except error.AutoservSSHTimeout:
859 logging.warning("Restart servod failed due ssh connection "
860 "to servohost timed out. This error is forgiven"
861 " here, we will retry in servo repair process.")
862 except error.AutoservRunError as e:
863 logging.warning("Restart servod failed due to:\n%s\n"
864 "This error is forgiven here, we will retry"
865 " in servo repair process.", str(e))
Garry Wangebc015b2019-06-06 17:45:06 -0700866
Gregory Nisbetde13e2a2019-12-09 22:44:00 -0800867 # TODO(gregorynisbet): Clean all of this up.
868 logging.debug('create_servo_host: attempt to set info store on '
869 'servo host')
870 try:
871 if dut_host_info is None:
872 logging.debug('create_servo_host: dut_host_info is '
873 'None, skipping')
874 else:
875 newhost.set_dut_host_info(dut_host_info)
876 logging.debug('create_servo_host: successfully set info '
877 'store')
878 except Exception:
879 logging.error("create_servo_host: (%s)", traceback.format_exc())
880
Richard Barnette9a26ad62016-06-10 12:03:08 -0700881 # Note that the logic of repair() includes everything done
882 # by verify(). It's sufficient to call one or the other;
883 # we don't need both.
Richard Barnette07c2e1d2016-10-26 14:24:28 -0700884 if servo_dependency:
885 newhost.repair(silent=True)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700886 return newhost, newhost.get_servo_state()
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700887
888 if try_servo_repair:
889 try:
890 newhost.repair()
891 except Exception:
892 logging.exception('servo repair failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700893 else:
894 try:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700895 newhost.verify()
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700896 except Exception:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -0700897 logging.exception('servo verify failed for %s', newhost.hostname)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700898 return newhost, newhost.get_servo_state()
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800899
900
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700901def _is_servo_host_information_exist(hostname, port):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800902 if hostname is None or len(hostname.strip()) == 0:
903 return False
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700904 if port is None:
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800905 return False
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700906 if not type(port) is int:
907 try:
908 int(port)
909 except ValueError:
910 return False
911
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800912 return True
913
914
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700915def is_servo_host_information_valid(hostname, port):
916 if not _is_servo_host_information_exist(hostname, port):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800917 return False
918 # checking range and correct of the port
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700919 port_int = int(port)
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800920 if port_int < 1 or port_int > 65000:
921 return False
922 # we expecting host contain only latters, digits and '-' or '_'
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700923 if not re.match('[a-zA-Z0-9-_\.]*$', hostname) or len(hostname) < 5:
Otabek Kasimov7267a7a2020-03-04 11:18:45 -0800924 return False
925 return True