blob: 98a4dcef2cc4301af2cec183646d8c93e64dfabc [file] [log] [blame]
Derek Beckettf73baca2020-08-19 15:08:47 -07001# Lint as: python2, python3
Fang Deng5d518f42013-08-02 14:04:32 -07002# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5#
6# Expects to be run in an environment with sudo and no interactive password
7# prompt, such as within the Chromium OS development chroot.
8
9
10"""This file provides core logic for servo verify/repair process."""
11
12
Derek Beckettf73baca2020-08-19 15:08:47 -070013from __future__ import absolute_import
14from __future__ import division
15from __future__ import print_function
16
Fang Deng5d518f42013-08-02 14:04:32 -070017import logging
Raul E Rangel52ca2e82018-07-03 14:10:14 -060018import os
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -080019import re
20import tarfile
Andrew McRaef0679932020-08-13 09:15:23 +100021import threading
Garry Wang2b5eef92020-08-21 16:23:35 -070022import json
Garry Wangc1288cf2019-12-17 14:58:00 -080023import time
Derek Beckettf73baca2020-08-19 15:08:47 -070024import six
25import six.moves.xmlrpc_client
Otabek Kasimov120b6fa2020-07-03 00:15:27 -070026import calendar
Fang Deng5d518f42013-08-02 14:04:32 -070027
28from autotest_lib.client.bin import utils
Garry Wang79e9af62019-06-12 15:19:19 -070029from autotest_lib.client.common_lib import error
Richard Barnette9a26ad62016-06-10 12:03:08 -070030from autotest_lib.client.common_lib import hosts
Garry Wang7b0e1b72020-03-25 19:08:59 -070031from autotest_lib.client.common_lib import lsbrelease_utils
Fang Deng5d518f42013-08-02 14:04:32 -070032from autotest_lib.client.common_lib.cros import retry
Richard Barnette9a26ad62016-06-10 12:03:08 -070033from autotest_lib.server.cros.servo import servo
Richard Barnetted31580e2018-05-14 19:58:00 +000034from autotest_lib.server.hosts import servo_repair
Garry Wangebc015b2019-06-06 17:45:06 -070035from autotest_lib.server.hosts import base_servohost
Garry Wang11b5e872020-03-11 15:14:08 -070036from autotest_lib.server.hosts import servo_constants
Otabek Kasimov4ea636e2020-04-14 23:35:06 -070037from autotest_lib.server.cros.faft.utils import config
Garry Wang11b5e872020-03-11 15:14:08 -070038from autotest_lib.client.common_lib import global_config
Otabek Kasimov8475cce2020-07-14 12:11:31 -070039from autotest_lib.site_utils.admin_audit import servo_updater
Otabek Kasimov382c3bb2020-10-28 13:22:45 -070040from autotest_lib.server.cros.servo.topology import servo_topology
Garry Wangd7367482020-02-27 13:52:40 -080041
Otabek Kasimov15963492020-06-23 21:10:51 -070042try:
Mike Frysinger714c5b02020-09-04 23:22:54 -040043 from autotest_lib.utils.frozen_chromite.lib import metrics
Otabek Kasimov15963492020-06-23 21:10:51 -070044except ImportError:
45 metrics = utils.metrics_mock
46
Dan Shi3b2adf62015-09-02 17:46:54 -070047_CONFIG = global_config.global_config
Fang Deng5d518f42013-08-02 14:04:32 -070048
Otabek Kasimova7ba91a2020-03-09 08:31:01 -070049
Garry Wangebc015b2019-06-06 17:45:06 -070050class ServoHost(base_servohost.BaseServoHost):
51 """Host class for a servo host(e.g. beaglebone, labstation)
Dana Goyette0b6e6402019-10-04 11:09:24 -070052 that with a servo instance for a specific port.
53
54 @type _servo: servo.Servo | None
55 """
Fang Deng5d518f42013-08-02 14:04:32 -070056
Raul E Rangel52ca2e82018-07-03 14:10:14 -060057 DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999'))
Richard Barnette9a26ad62016-06-10 12:03:08 -070058
Dan Shie5b3c512014-08-21 12:12:09 -070059 # Timeout for initializing servo signals.
Wai-Hong Tam37b6ed32017-09-19 15:52:39 -070060 INITIALIZE_SERVO_TIMEOUT_SECS = 60
Richard Barnette9a26ad62016-06-10 12:03:08 -070061
Otabek Kasimov545739c2020-08-20 00:24:21 -070062 # Default timeout for run terminal command.
63 DEFAULT_TERMINAL_TIMEOUT = 30
64
xixuan6cf6d2f2016-01-29 15:29:00 -080065 # Ready test function
66 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070067
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -080068 # Directory prefix on the servo host where the servod logs are stored.
69 SERVOD_LOG_PREFIX = '/var/log/servod'
70
71 # Exit code to use when symlinks for servod logs are not found.
72 NO_SYMLINKS_CODE = 9
73
74 # Directory in the job's results directory to dump the logs into.
75 LOG_DIR = 'servod'
76
77 # Prefix for joint loglevel files in the logs.
78 JOINT_LOG_PREFIX = 'log'
79
80 # Regex group to extract timestamp from logfile name.
81 TS_GROUP = 'ts'
82
83 # This regex is used to extract the timestamp from servod logs.
Garry Wang22f2e842020-09-09 20:19:19 -070084 # files always start with log.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -080085 TS_RE = (r'log.'
86 # The timestamp is of format %Y-%m-%d--%H-%M-%S.MS
87 r'(?P<%s>\d{4}(\-\d{2}){2}\-(-\d{2}){3}.\d{3})'
88 # The loglevel is optional depending on labstation version.
89 r'(.(INFO|DEBUG|WARNING))?' % TS_GROUP)
90 TS_EXTRACTOR = re.compile(TS_RE)
91
92 # Regex group to extract MCU name from logline in servod logs.
93 MCU_GROUP = 'mcu'
94
95 # Regex group to extract logline from MCU logline in servod logs.
96 LINE_GROUP = 'line'
97
98 # This regex is used to extract the mcu and the line content from an
99 # MCU logline in servod logs. e.g. EC or servo_v4 console logs.
100 # Here is an example log-line:
101 #
102 # 2020-01-23 13:15:12,223 - servo_v4 - EC3PO.Console - DEBUG -
103 # console.py:219:LogConsoleOutput - /dev/pts/9 - cc polarity: cc1
104 #
105 # Here is conceptually how they are formatted:
106 #
107 # <time> - <MCU> - EC3PO.Console - <LVL> - <file:line:func> - <pts> -
108 # <output>
109 #
Garry Wang22f2e842020-09-09 20:19:19 -0700110 # The log format starts with a timestamp
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800111 MCU_RE = (r'[\d\-]+ [\d:,]+ '
112 # The mcu that is logging this is next.
113 r'- (?P<%s>\w+) - '
114 # Next, we have more log outputs before the actual line.
115 # Information about the file line, logging function etc.
116 # Anchor on EC3PO Console, LogConsoleOutput and dev/pts.
117 # NOTE: if the log format changes, this regex needs to be
118 # adjusted.
119 r'EC3PO\.Console[\s\-\w\d:.]+LogConsoleOutput - /dev/pts/\d+ - '
120 # Lastly, we get the MCU's console line.
121 r'(?P<%s>.+$)' % (MCU_GROUP, LINE_GROUP))
122 MCU_EXTRACTOR = re.compile(MCU_RE)
123
Otabek Kasimov545739c2020-08-20 00:24:21 -0700124 # Regex to detect timeout messages when USBC pigtail has timeout issue.
125 # e.g.: [475635.427072 PD TMOUT RX 1/1]
126 USBC_PIGTAIL_TIMEOUT_RE = r'\[[\d \.]{1,20}(PD TMOUT RX 1\/1)\]'
127
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800128 # Suffix to identify compressed logfiles.
129 COMPRESSION_SUFFIX = '.tbz2'
130
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700131 # A suffix to mark servod log directories that came from instance that
132 # ran during this servo_host, but are not the last one running e.g. when
133 # an instance (on purpose, or due to a bug) restarted in the middle of the
134 # run.
135 OLD_LOG_SUFFIX = 'old'
136
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800137 def _init_attributes(self):
138 self._servo_state = None
139 self.servo_port = None
140 self.servo_board = None
141 self.servo_model = None
142 self.servo_serial = None
Garry Wangcb06f3b2020-10-08 20:56:21 -0700143 self.servo_setup = None
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800144 self.servo_recovery = None
Garry Wang6a680062020-11-03 13:40:29 -0800145 self.additional_servod_args = None
Otabek Kasimov39637412020-11-23 19:09:27 -0800146 self._dut_health_profile = None
Garry Wang000c6c02020-05-11 21:27:23 -0700147 # The flag that indicate if a servo is connected to a smart usbhub.
148 # TODO(xianuowang@) remove this flag once all usbhubs in the lab
149 # get replaced.
150 self.smart_usbhub = None
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800151 self._servo = None
Otabek Kasimov382c3bb2020-10-28 13:22:45 -0700152 self._topology = None
Andrew McRaef0679932020-08-13 09:15:23 +1000153 self._tunnel_proxy = None
154 self._tunnel_proxy_lock = threading.Lock()
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700155 self._initial_instance_ts = None
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800156 # Flag to make sure that multiple calls to close do not result in the
157 # logic executing multiple times.
158 self._closed = False
Andrew McRaef0679932020-08-13 09:15:23 +1000159 # Per-thread local data
160 self._local = threading.local()
Fang Deng5d518f42013-08-02 14:04:32 -0700161
Garry Wangcb06f3b2020-10-08 20:56:21 -0700162 def _initialize(self,
163 servo_host='localhost',
164 servo_port=DEFAULT_PORT,
165 servo_board=None,
166 servo_model=None,
167 servo_serial=None,
168 servo_setup=None,
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800169 servo_recovery=None,
Garry Wang6a680062020-11-03 13:40:29 -0800170 additional_servod_args=None,
Garry Wangcb06f3b2020-10-08 20:56:21 -0700171 is_in_lab=None,
172 *args,
173 **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -0700174 """Initialize a ServoHost instance.
175
176 A ServoHost instance represents a host that controls a servo.
177
178 @param servo_host: Name of the host where the servod process
179 is running.
Raul E Rangel52ca2e82018-07-03 14:10:14 -0600180 @param servo_port: Port the servod process is listening on. Defaults
181 to the SERVOD_PORT environment variable if set,
182 otherwise 9999.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700183 @param servo_board: Board that the servo is connected to.
Nick Sanders2f3c9852018-10-24 12:10:24 -0700184 @param servo_model: Model that the servo is connected to.
Garry Wangcb06f3b2020-10-08 20:56:21 -0700185 @param servo_serial: Serial number of the servo device.
186 @param servo_setup: Type of servo setup, e.g. REGULAR or DUAL_V4.
Garry Wang6a680062020-11-03 13:40:29 -0800187 @param additional_servod_args: Additional args that will append to
188 servod start command.
Dan Shi4d478522014-02-14 13:46:32 -0800189 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
190 to None, for which utils.host_is_in_lab_zone will be
191 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -0700192
193 """
194 super(ServoHost, self)._initialize(hostname=servo_host,
Garry Wangebc015b2019-06-06 17:45:06 -0700195 is_in_lab=is_in_lab, *args, **dargs)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800196 self._init_attributes()
Richard Barnette42f4db92018-08-23 15:05:15 -0700197 self.servo_port = int(servo_port)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700198 self.servo_board = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -0700199 self.servo_model = servo_model
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700200 self.servo_serial = servo_serial
Garry Wangcb06f3b2020-10-08 20:56:21 -0700201 self.servo_setup = servo_setup
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800202 self.servo_recovery = servo_recovery
Garry Wang6a680062020-11-03 13:40:29 -0800203 self.additional_servod_args = additional_servod_args
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800204
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800205 # The location of the log files on the servo host for this instance.
206 self.remote_log_dir = '%s_%s' % (self.SERVOD_LOG_PREFIX,
207 self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700208 # Path of the servo host lock file.
Derek Beckettf73baca2020-08-19 15:08:47 -0700209 self._lock_file = (self.TEMP_FILE_DIR + str(self.servo_port) +
210 self.LOCK_FILE_POSTFIX)
Garry Wang79e9af62019-06-12 15:19:19 -0700211 # File path to declare a reboot request.
Derek Beckettf73baca2020-08-19 15:08:47 -0700212 self._reboot_file = (self.TEMP_FILE_DIR + str(self.servo_port) +
213 self.REBOOT_FILE_POSTFIX)
Garry Wang79e9af62019-06-12 15:19:19 -0700214
215 # Lock the servo host if it's an in-lab labstation to prevent other
216 # task to reboot it until current task completes. We also wait and
217 # make sure the labstation is up here, in the case of the labstation is
218 # in the middle of reboot.
Garry Wang7c00b0f2019-06-25 17:28:17 -0700219 self._is_locked = False
Garry Wang42b4d862019-06-25 15:50:49 -0700220 if (self.wait_up(self.REBOOT_TIMEOUT) and self.is_in_lab()
221 and self.is_labstation()):
Garry Wang79e9af62019-06-12 15:19:19 -0700222 self._lock()
Garry Wang78ce64d2020-10-13 18:23:45 -0700223 try:
224 self.wait_ready()
225 except Exception as e:
226 logging.info(
227 'Unexpected error while ensure labstation'
228 ' readiness; %s', str(e))
Garry Wangebc015b2019-06-06 17:45:06 -0700229
Richard Barnette9a26ad62016-06-10 12:03:08 -0700230 self._repair_strategy = (
231 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -0700232
Dana Goyetteafa62fd2020-03-16 13:45:27 -0700233 def __str__(self):
234 return "<%s '%s:%s'>" % (
235 type(self).__name__, self.hostname, self.servo_port)
236
Richard Barnette9a26ad62016-06-10 12:03:08 -0700237 def connect_servo(self):
Garry Wang8c8dc972020-06-09 13:41:51 -0700238 """ Initialize and setup servo for later use.
239 """
240 self.initilize_servo()
241 self.initialize_dut_for_servo()
242
Garry Wang8c8dc972020-06-09 13:41:51 -0700243 def initilize_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700244 """Establish a connection to the servod server on this host.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700245
246 Initializes `self._servo` and then verifies that all network
247 connections are working. This will create an ssh tunnel if
248 it's required.
Garry Wang8c8dc972020-06-09 13:41:51 -0700249 """
250 self._servo = servo.Servo(servo_host=self,
251 servo_serial=self.servo_serial)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700252
Garry Wang8c8dc972020-06-09 13:41:51 -0700253 def initialize_dut_for_servo(self):
254 """This method will do some setup for dut control, e.g. setup
255 main servo_v4 device, and also testing the connection between servo
256 and DUT. As a side effect of testing the connection, all signals on
257 the target servo are reset to default values, and the USB stick is
Richard Barnette9a26ad62016-06-10 12:03:08 -0700258 set to the neutral (off) position.
259 """
Garry Wang8c8dc972020-06-09 13:41:51 -0700260 if not self._servo:
261 raise hosts.AutoservVerifyError('Servo object needs to be'
262 ' initialized before initialize'
263 ' DUT.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700264 timeout, _ = retry.timeout(
Garry Wang8c8dc972020-06-09 13:41:51 -0700265 self._servo.initialize_dut,
266 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700267 if timeout:
Garry Wang8c8dc972020-06-09 13:41:51 -0700268 raise hosts.AutoservVerifyError('Initialize dut for servo timed'
269 ' out.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700270
Richard Barnette9a26ad62016-06-10 12:03:08 -0700271 def disconnect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700272 """Disconnect our servo if it exists.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700273
274 If we've previously successfully connected to our servo,
275 disconnect any established ssh tunnel, and set `self._servo`
276 back to `None`.
277 """
278 if self._servo:
279 # N.B. This call is safe even without a tunnel:
280 # rpc_server_tracker.disconnect() silently ignores
281 # unknown ports.
282 self.rpc_server_tracker.disconnect(self.servo_port)
283 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700284
Andrew McRaef0679932020-08-13 09:15:23 +1000285 def _maybe_create_servod_ssh_tunnel_proxy(self):
286 """Create a xmlrpc proxy for use with a ssh tunnel.
287 A lock is used to safely create a singleton proxy.
288 """
289 with self._tunnel_proxy_lock:
290 if self._tunnel_proxy is None:
291 self._tunnel_proxy = self.rpc_server_tracker.xmlrpc_connect(
292 None,
293 self.servo_port,
294 ready_test_name=self.SERVO_READY_METHOD,
295 timeout_seconds=60,
296 request_timeout_seconds=3600,
297 server_desc=str(self))
298
Andrew McRaef0679932020-08-13 09:15:23 +1000299 def get_servod_server_proxy(self):
300 """Return a proxy if it exists; otherwise, create a new one.
301 A proxy can either be a ssh tunnel based proxy, or a httplib
302 based proxy.
Fang Deng5d518f42013-08-02 14:04:32 -0700303
304 @returns: An xmlrpclib.ServerProxy that is connected to the servod
305 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700306 """
Garry Wang11b5e872020-03-11 15:14:08 -0700307 if (servo_constants.ENABLE_SSH_TUNNEL_FOR_SERVO
308 and not self.is_localhost()):
Andrew McRaef0679932020-08-13 09:15:23 +1000309 # Check for existing ssh tunnel proxy.
310 if self._tunnel_proxy is None:
311 self._maybe_create_servod_ssh_tunnel_proxy()
312 return self._tunnel_proxy
Richard Barnette9a26ad62016-06-10 12:03:08 -0700313 else:
Andrew McRaef0679932020-08-13 09:15:23 +1000314 # xmlrpc/httplib is not thread-safe, so each thread must have its
315 # own separate proxy connection.
316 if not hasattr(self._local, "_per_thread_proxy"):
317 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
Derek Beckettf73baca2020-08-19 15:08:47 -0700318 self._local._per_thread_proxy = six.moves.xmlrpc_client.ServerProxy(remote)
Andrew McRaef0679932020-08-13 09:15:23 +1000319 return self._local._per_thread_proxy
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800320
Richard Barnette1edbb162016-11-01 11:47:50 -0700321 def verify(self, silent=False):
322 """Update the servo host and verify it's in a good state.
323
324 @param silent If true, suppress logging in `status.log`.
325 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700326 message = 'Beginning verify for servo host %s port %s serial %s'
327 message %= (self.hostname, self.servo_port, self.servo_serial)
328 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700329 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700330 self._repair_strategy.verify(self, silent)
Garry Wang11b5e872020-03-11 15:14:08 -0700331 self._servo_state = servo_constants.SERVO_STATE_WORKING
332 self.record('INFO', None, None,
333 'ServoHost verify set servo_state as WORKING')
Garry Wang63b8c382020-03-11 22:28:40 -0700334 except Exception as e:
Otabek Kasimov120b6fa2020-07-03 00:15:27 -0700335 if not self.is_localhost():
Otabek Kasimovc6f30412020-06-30 20:08:12 -0700336 self._servo_state = self.determine_servo_state()
337 self.record('INFO', None, None,
338 'ServoHost verify set servo_state as %s'
339 % self._servo_state)
Garry Wang63b8c382020-03-11 22:28:40 -0700340 if self._is_critical_error(e):
341 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700342
Garry Wang2b5eef92020-08-21 16:23:35 -0700343 def _get_default_usbkey_mount_path(self):
344 return '/media/servo_usb/%s' % self.servo_port
Fang Deng5d518f42013-08-02 14:04:32 -0700345
Garry Wang7b0e1b72020-03-25 19:08:59 -0700346 def get_image_name_from_usbkey(self, usbkey_dev):
347 """Mount usb drive and check ChromeOS image name on it if there is
348 one. This method assumes the image_usbkey_direction is already set
349 to servo side.
350
Garry Wang4b980202020-09-24 17:00:17 -0700351 @param usbkey_dev: usbkey dev path(e.g. /dev/sdb).
Garry Wang7b0e1b72020-03-25 19:08:59 -0700352
353 @returns: image_name on the usbkey, e.g. nami-release/R82.10138.0.0,
354 or empty string if no test image detected, or unexpected
355 error occurred.
Garry Wang7b0e1b72020-03-25 19:08:59 -0700356 """
Garry Wang70e5d062020-04-03 18:01:05 -0700357 logging.info('Checking ChromeOS image name on usbkey.')
Garry Wang2b5eef92020-08-21 16:23:35 -0700358 mount_dst = self._get_default_usbkey_mount_path()
Garry Wang7b0e1b72020-03-25 19:08:59 -0700359 # Unmount if there is an existing stale mount.
Garry Wang2b5eef92020-08-21 16:23:35 -0700360 self._unmount_drive(mount_dst)
361 # ChromeOS root fs is in /dev/sdx3
362 mount_src = usbkey_dev + '3'
Garry Wang7b0e1b72020-03-25 19:08:59 -0700363 try:
Garry Wang2b5eef92020-08-21 16:23:35 -0700364 if not self._mount_drive(mount_src, mount_dst):
365 logging.debug('Unexpected error occurred on mount usb drive.')
Garry Wang7b0e1b72020-03-25 19:08:59 -0700366 return ''
Garry Wang70e5d062020-04-03 18:01:05 -0700367
368 release_content = self.run(
Garry Wang2b5eef92020-08-21 16:23:35 -0700369 'cat %s/etc/lsb-release' % mount_dst,
Garry Wang70e5d062020-04-03 18:01:05 -0700370 ignore_status=True).stdout.strip()
371
372 if not re.search(r'RELEASE_TRACK=.*test', release_content):
373 logging.info('The image on usbkey is not a test image')
374 return ''
375
376 return lsbrelease_utils.get_chromeos_release_builder_path(
377 lsb_release_content=release_content)
Garry Wang7b0e1b72020-03-25 19:08:59 -0700378 finally:
Garry Wang70e5d062020-04-03 18:01:05 -0700379 logging.debug('Image check compeleted, unmounting the usb drive.')
Garry Wang2b5eef92020-08-21 16:23:35 -0700380 self._unmount_drive(mount_dst)
Garry Wang7b0e1b72020-03-25 19:08:59 -0700381
Garry Wang2b5eef92020-08-21 16:23:35 -0700382 def _extract_firmware_image_from_usbkey(self, fw_dst):
383 """Extract firmware images from the usbkey on servo, this method
384 assumes there is already a ChromeOS test image staged on servo.
385
Garry Wang4b980202020-09-24 17:00:17 -0700386 @param fw_dst: the path that we'll copy firmware images to.
Garry Wang2b5eef92020-08-21 16:23:35 -0700387
388 @returns: a json format string of firmware manifest data.
389 """
390 usbkey_dev = self._probe_and_validate_usb_dev()
391 if not usbkey_dev:
392 raise hosts.AutoservRepairError('Unexpected error occurred when'
393 ' probe usbkey dev path, please check logs for detail.')
394
395 mount_dst = self._get_default_usbkey_mount_path()
396 # Unmount if there is an existing stale mount.
397 self._unmount_drive(mount_dst)
398 # ChromeOS root fs is in /dev/sdx3
399 mount_src = usbkey_dev + '3'
400 try:
401 if not self._mount_drive(mount_src, mount_dst):
402 raise hosts.AutoservRepairError('Failed to extract firmware'
403 ' image; Unable to mount %s.' % usbkey_dev,
404 'unable to mount usbkey')
405 updater_bin = os.path.join(mount_dst,
406 'usr/sbin/chromeos-firmwareupdate')
407 self.run('%s --unpack %s' % (updater_bin, fw_dst))
408 return self.run('%s --manifest' % updater_bin).stdout
409 finally:
410 self._unmount_drive(mount_dst)
411
412 def prepare_repair_firmware_image(self, fw_dst=None):
413 """Prepare firmware image on the servohost for auto repair process
414 to consume.
415
Garry Wang4b980202020-09-24 17:00:17 -0700416 @param fw_dst: the path that we want to store firmware image on
417 the servohost.
Garry Wang2b5eef92020-08-21 16:23:35 -0700418
419 @returns: A tuple that containes ec firmware image path and bios
420 firmware image path on the servohost, or None if type of
421 image is not available based on manifest and dut's model.
422 """
423 model = self.servo_model or self._dut_host_info.model
424 if not model:
425 raise hosts.AutoservRepairError(
426 'Could not determine DUT\'s model.',
427 'model infomation unknown')
428
429 if not fw_dst:
430 fw_dst = '/tmp/firmware_image/%s' % self.servo_port
431 # Cleanup and re-create dst path to have a fresh start.
432 self.run('rm -rf %s' % fw_dst)
433 self.run('mkdir -p %s' % fw_dst)
434
435 manifest = json.loads(self._extract_firmware_image_from_usbkey(fw_dst))
Garry Wang50b56c12020-09-24 17:26:52 -0700436 # For models that have packed $MODEL_signed variant, we want use the
437 # 'signed' variant once we get DVT devices, so try to read manifest
438 # from $MODEL_signed first.
439 build = manifest.get('%s_signed' % model) or manifest.get(model)
440 if not build:
Garry Wang2b5eef92020-08-21 16:23:35 -0700441 raise hosts.AutoservRepairError('Could not find firmware manifest'
442 ' for model:%s' % model, 'model manifest not found')
443 try:
Garry Wang50b56c12020-09-24 17:26:52 -0700444 ec_image = os.path.join(fw_dst, build['ec']['image'])
Garry Wang2b5eef92020-08-21 16:23:35 -0700445 except KeyError:
446 ec_image = None
447 try:
Garry Wang50b56c12020-09-24 17:26:52 -0700448 bios_image = os.path.join(fw_dst, build['host']['image'])
Garry Wang2b5eef92020-08-21 16:23:35 -0700449 except KeyError:
450 bios_image = None
451 if not ec_image and not bios_image:
452 raise hosts.AutoservRepairError('Could not find any firmware image'
453 ' for model:%s' % model, 'cannot find firmware image')
454 return ec_image, bios_image
Garry Wang7b0e1b72020-03-25 19:08:59 -0700455
Garry Wang4b980202020-09-24 17:00:17 -0700456 def flash_ap_firmware_via_servo(self, image):
457 """Flash AP firmware by use a provided image.
458
459 This is will be a short term enhanment for infra repair use, it use
460 'futility update' which will automatically determine various parameters
461 needed for flashrom, and will preserve the GBB, VPD, and HWID for
462 AP firmware update.
463 @TODO(xianuowang@) Remove this method once b/148403277 implemented.
464
465 @param image: the firmware image path on servohost.
466 """
467 cmd = 'futility update -i %s --servo_port=%s'
468 self.run(cmd % (image, self.servo_port), timeout=900)
469
Garry Wang70e5d062020-04-03 18:01:05 -0700470 def _probe_and_validate_usb_dev(self):
471 """This method probe the usb dev path by talking to servo, and then
472 validate the dev path is valid block device to servohost.
473 Possible output:
474 1. Encounter error during probe usb dev, returns empty string.
475 2. probe usb dev completed without error but cannot find usb dev,
476 raise AutoservRepairError.
477 3. probe usb dev find a usb dev path, but failed validation in this
478 method, raise AutoservRepairError.
Garry Wang7b0e1b72020-03-25 19:08:59 -0700479
Garry Wang70e5d062020-04-03 18:01:05 -0700480 @returns: A string of usb dev path(e.g. '/dev/sdb'), or empty string
481 if unexpected error occurred during probe.
482 @raises: AutoservRepairError if servo couldn't probe the usb dev path
483 (servo.probe_host_usb_dev() returns empty string), or the dev path is
484 not valid block device to servohost.
Garry Wang7b0e1b72020-03-25 19:08:59 -0700485 """
486 logging.info('Validating image usbkey on servo.')
Garry Wang7b0e1b72020-03-25 19:08:59 -0700487 try:
Garry Wang70e5d062020-04-03 18:01:05 -0700488 usb_dev = self._servo.probe_host_usb_dev()
Garry Wang7b0e1b72020-03-25 19:08:59 -0700489 except Exception as e:
490 # We don't want any unexpected or transient servo communicating
491 # failure block usb repair, so capture all errors here.
492 logging.error(e, exc_info=True)
493 logging.error('Unexpected error occurred on get usbkey dev path,'
494 ' skipping usbkey validation.')
495 return ''
496
Garry Wang70e5d062020-04-03 18:01:05 -0700497 if usb_dev:
498 # probe_host_usb_dev() sometimes return stale record,
499 # so we need to make sure the path exists in fdisk.
Otabek Kasimov77bff672020-10-08 15:52:03 -0700500 validate_cmd = 'fdisk -l %s' % usb_dev
Garry Wang11441182020-06-16 18:34:14 -0700501 try:
Otabek Kasimov77bff672020-10-08 15:52:03 -0700502 resp = self.run(validate_cmd, ignore_status=True, timeout=30)
Garry Wang11441182020-06-16 18:34:14 -0700503 if resp.exit_status == 0:
504 return usb_dev
Garry Wang11441182020-06-16 18:34:14 -0700505 logging.error('%s is reported from "image_usbkey_dev" control'
506 ' but not detected by fdisk!', usb_dev)
507 except error.AutoservRunError as e:
508 if 'Timeout encountered' in str(e):
509 logging.warning('Timeout encountered during fdisk run,'
510 ' skipping usbkey validation.')
511 return ''
512 raise
Garry Wang70e5d062020-04-03 18:01:05 -0700513
514 raise hosts.AutoservRepairError(
515 'No usbkey detected on servo, the usbkey may be either missing'
516 ' or broken. Please replace usbkey on the servo and retry.',
517 'missing usbkey')
518
Otabek Kasimov4ea636e2020-04-14 23:35:06 -0700519 def is_ec_supported(self):
Garry Wang9b8f2342020-04-17 16:34:09 -0700520 """Check if ec is supported on the servo_board"""
Otabek Kasimov4ea636e2020-04-14 23:35:06 -0700521 if self.servo_board:
522 try:
523 frm_config = config.Config(self.servo_board, self.servo_model)
524 return frm_config.chrome_ec
525 except Exception as e:
526 logging.error('Unexpected error when read from firmware'
527 ' configs; %s', str(e))
528 return False
529
Garry Wang70e5d062020-04-03 18:01:05 -0700530 def validate_image_usbkey(self):
531 """This method first validate if there is a recover usbkey on servo
532 that accessible to servohost, and second check if a ChromeOS image is
533 already on the usb drive and return the image_name so we can avoid
534 unnecessary download and flash to the recover usbkey on servo.
535
536 Please note that, there is special error handling logic here:
537 1. If unexpected error happens, we return empty string. So repair
538 actions will not get blocked.
539 2. If no working usbkey present on servo, but no errors, we'll raise
540 AutoservRepairError here.
541
542 @returns: image_name on the usbkey, e.g. nami-release/R82.10138.0.0,
543 or empty string if no test image detected, or unexpected
544 error occurred.
545 @raises: AutoservRepairError if the usbkey is not detected on servo.
546 """
547 usb_dev = self._probe_and_validate_usb_dev()
548 if usb_dev:
549 return self.get_image_name_from_usbkey(usb_dev)
550 else:
551 return ''
Garry Wang7b0e1b72020-03-25 19:08:59 -0700552
Richard Barnette1edbb162016-11-01 11:47:50 -0700553 def repair(self, silent=False):
554 """Attempt to repair servo host.
555
556 @param silent If true, suppress logging in `status.log`.
557 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700558 message = 'Beginning repair for servo host %s port %s serial %s'
559 message %= (self.hostname, self.servo_port, self.servo_serial)
560 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700561 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700562 self._repair_strategy.repair(self, silent)
Garry Wang11b5e872020-03-11 15:14:08 -0700563 self._servo_state = servo_constants.SERVO_STATE_WORKING
564 self.record('INFO', None, None,
565 'ServoHost repair set servo_state as WORKING')
Garry Wang464ff1e2019-07-18 17:20:34 -0700566 # If target is a labstation then try to withdraw any existing
567 # reboot request created by this servo because it passed repair.
568 if self.is_labstation():
569 self.withdraw_reboot_request()
Garry Wang63b8c382020-03-11 22:28:40 -0700570 except Exception as e:
Otabek Kasimov120b6fa2020-07-03 00:15:27 -0700571 if not self.is_localhost():
Otabek Kasimovc6f30412020-06-30 20:08:12 -0700572 self._servo_state = self.determine_servo_state()
573 self.record('INFO', None, None,
574 'ServoHost repair set servo_state as %s'
575 % self._servo_state)
Garry Wang63b8c382020-03-11 22:28:40 -0700576 if self._is_critical_error(e):
577 self.disconnect_servo()
578 self.stop_servod()
579 raise
580
Garry Wang63b8c382020-03-11 22:28:40 -0700581 def _is_critical_error(self, error):
582 if (isinstance(error, hosts.AutoservVerifyDependencyError)
583 and not error.is_critical()):
584 logging.warning('Non-critical verify failure(s) detected during'
585 ' verify/repair servo, servo connection will'
Evan Benn2c41c262020-10-28 11:34:27 +1100586 ' still be up but may not be fully functional.'
587 ' Some repair actions and servo dependent'
Garry Wang63b8c382020-03-11 22:28:40 -0700588 ' tests may not run.')
589 return False
Evan Benn2c41c262020-10-28 11:34:27 +1100590 logging.info(
591 'Critical verify failure(s) detected during repair/verify '
592 'servo. Disconnecting servo and running `stop servod`, all'
593 ' repair actions and tests that depends on servo will not '
594 'run.')
Garry Wang63b8c382020-03-11 22:28:40 -0700595 return True
Fang Deng5d518f42013-08-02 14:04:32 -0700596
Dan Shi4d478522014-02-14 13:46:32 -0800597 def get_servo(self):
598 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700599
Dan Shi4d478522014-02-14 13:46:32 -0800600 @return: a servo.Servo object.
Dana Goyette353d1d92019-06-27 10:43:59 -0700601 @rtype: autotest_lib.server.cros.servo.servo.Servo
Fang Deng5d518f42013-08-02 14:04:32 -0700602 """
Dan Shi4d478522014-02-14 13:46:32 -0800603 return self._servo
604
Garry Wang79e9af62019-06-12 15:19:19 -0700605 def request_reboot(self):
606 """Request servohost to be rebooted when it's safe to by touch a file.
607 """
608 logging.debug('Request to reboot servohost %s has been created by '
Garry Wang464ff1e2019-07-18 17:20:34 -0700609 'servo with port # %s', self.hostname, self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700610 self.run('touch %s' % self._reboot_file, ignore_status=True)
611
Garry Wang464ff1e2019-07-18 17:20:34 -0700612 def withdraw_reboot_request(self):
613 """Withdraw a servohost reboot request if exists by remove the flag
614 file.
615 """
616 logging.debug('Withdrawing request to reboot servohost %s that created'
617 ' by servo with port # %s if exists.',
618 self.hostname, self.servo_port)
619 self.run('rm -f %s' % self._reboot_file, ignore_status=True)
620
Garry Wangc1288cf2019-12-17 14:58:00 -0800621 def start_servod(self, quick_startup=False):
622 """Start the servod process on servohost.
623 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800624 # Skip if running on the localhost.(crbug.com/1038168)
625 if self.is_localhost():
626 logging.debug("Servohost is a localhost, skipping start servod.")
627 return
628
629 cmd = 'start servod'
Garry Wangc1288cf2019-12-17 14:58:00 -0800630 if self.servo_board:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800631 cmd += ' BOARD=%s' % self.servo_board
Garry Wangc1288cf2019-12-17 14:58:00 -0800632 if self.servo_model:
633 cmd += ' MODEL=%s' % self.servo_model
Garry Wangc1288cf2019-12-17 14:58:00 -0800634 else:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800635 logging.warning('Board for DUT is unknown; starting servod'
636 ' assuming a pre-configured board.')
637
638 cmd += ' PORT=%d' % self.servo_port
639 if self.servo_serial:
640 cmd += ' SERIAL=%s' % self.servo_serial
Garry Wangd7367482020-02-27 13:52:40 -0800641
Garry Wangcb06f3b2020-10-08 20:56:21 -0700642 # Start servod with dual_v4 based on servo_setup.
Otabek Kasimov382c3bb2020-10-28 13:22:45 -0700643 if self.is_dual_setup():
Garry Wangcb06f3b2020-10-08 20:56:21 -0700644 cmd += ' DUAL_V4=1'
Garry Wangd7367482020-02-27 13:52:40 -0800645
Garry Wangcb06f3b2020-10-08 20:56:21 -0700646 # Start servod with CONFIG=cr50.xml which required for some pools.
647 if self._require_cr50_servod_config():
648 cmd += ' CONFIG=cr50.xml'
Garry Wangb5cee3e2020-09-16 14:58:13 -0700649
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800650 if self.servo_recovery == True:
651 cmd += ' REC_MODE=1'
652
Garry Wang6a680062020-11-03 13:40:29 -0800653 # Adding customized args if any.
654 if self.additional_servod_args:
655 cmd += ' ' + self.additional_servod_args
656
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800657 # Remove the symbolic links from the logs. This helps ensure that
658 # a failed servod instantiation does not cause us to grab old logs
659 # by mistake.
660 self.remove_latest_log_symlinks()
Garry Wangcdd27b22020-01-13 14:59:11 -0800661 self.run(cmd, timeout=60)
Garry Wangc1288cf2019-12-17 14:58:00 -0800662
663 # There's a lag between when `start servod` completes and when
664 # the _ServodConnectionVerifier trigger can actually succeed.
665 # The call to time.sleep() below gives time to make sure that
666 # the trigger won't fail after we return.
667
668 # Normally servod on servo_v3 and labstation take ~10 seconds to ready,
669 # But in the rare case all servo on a labstation are in heavy use they
670 # may take ~30 seconds. So the timeout value will double these value,
671 # and we'll try quick start up when first time initialize servohost,
672 # and use standard start up timeout in repair.
673 if quick_startup:
Garry Wang11b5e872020-03-11 15:14:08 -0700674 timeout = servo_constants.SERVOD_QUICK_STARTUP_TIMEOUT
Garry Wangc1288cf2019-12-17 14:58:00 -0800675 else:
Garry Wang11b5e872020-03-11 15:14:08 -0700676 timeout = servo_constants.SERVOD_STARTUP_TIMEOUT
Garry Wangc1288cf2019-12-17 14:58:00 -0800677 logging.debug('Wait %s seconds for servod process fully up.', timeout)
678 time.sleep(timeout)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700679 # Cache the initial instance timestamp to check against servod restarts
680 self._initial_instance_ts = self.get_instance_logs_ts()
Garry Wangc1288cf2019-12-17 14:58:00 -0800681
Garry Wangc1288cf2019-12-17 14:58:00 -0800682 def stop_servod(self):
683 """Stop the servod process on servohost.
684 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800685 # Skip if running on the localhost.(crbug.com/1038168)
686 if self.is_localhost():
687 logging.debug("Servohost is a localhost, skipping stop servod.")
688 return
689
Garry Wangc1288cf2019-12-17 14:58:00 -0800690 logging.debug('Stopping servod on port %s', self.servo_port)
Garry Wangcdd27b22020-01-13 14:59:11 -0800691 self.run('stop servod PORT=%d' % self.servo_port,
692 timeout=60, ignore_status=True)
Garry Wangc1288cf2019-12-17 14:58:00 -0800693 logging.debug('Wait %s seconds for servod process fully teardown.',
Garry Wang11b5e872020-03-11 15:14:08 -0700694 servo_constants.SERVOD_TEARDOWN_TIMEOUT)
695 time.sleep(servo_constants.SERVOD_TEARDOWN_TIMEOUT)
Garry Wangc1288cf2019-12-17 14:58:00 -0800696
Garry Wangc1288cf2019-12-17 14:58:00 -0800697 def restart_servod(self, quick_startup=False):
698 """Restart the servod process on servohost.
699 """
700 self.stop_servod()
701 self.start_servod(quick_startup)
702
Garry Wangffbd2162020-04-17 16:13:48 -0700703 def _process_servodtool_error(self, response):
704 """Helper function to handle non-zero servodtool response.
705 """
706 if re.search(servo_constants.ERROR_MESSAGE_USB_HUB_NOT_COMPATIBLE,
Garry Wangad245002020-05-15 15:20:23 -0700707 response.stdout):
Garry Wangffbd2162020-04-17 16:13:48 -0700708 logging.error('The servo is not plugged on a usb hub that supports'
709 ' power-cycle!')
Garry Wang000c6c02020-05-11 21:27:23 -0700710 # change the flag so we can update this label in later process.
711 self.smart_usbhub = False
Garry Wangffbd2162020-04-17 16:13:48 -0700712 return
713
714 if re.search(servo_constants.ERROR_MESSAGE_DEVICE_NOT_FOUND %
715 self.servo_serial, response.stdout):
716 logging.error('No servo with serial %s found!', self.servo_serial)
717 return
718
719 logging.error('Unexpected error occurred from usbhub control, please'
720 ' file a bug and inform chrome-fleet-software@ team!')
721
Otabek Kasimov86062d02020-11-17 13:30:22 -0800722 def get_main_servo_usb_path(self):
723 """Helper function to collect current usb-path to main servo.
724
725 The usb-path is path to the folder where usb-device was enumerated.
726 If fail then will return an empty string ('').
727
728 @returns: string, usb-path to the main servo device.
729 e.g.: '/sys/bus/usb/devices/1-6.1.3.1'
Garry Wangffbd2162020-04-17 16:13:48 -0700730 """
Otabek Kasimov09192682020-06-01 18:17:44 -0700731 # TODO remove try-except when fix crbug.com/1087964
732 try:
733 cmd = 'servodtool device -s %s usb-path' % self.servo_serial
734 resp = self.run(cmd, ignore_status=True, timeout=30)
735 except Exception as e:
736 # Here we catch only timeout errors.
737 # Other errors is filtered by ignore_status=True
738 logging.debug('Attempt to get servo usb-path failed due to '
739 'timeout; %s', e)
740 return ''
Garry Wangffbd2162020-04-17 16:13:48 -0700741 if resp.exit_status != 0:
742 self._process_servodtool_error(resp)
743 return ''
744 usb_path = resp.stdout.strip()
745 logging.info('Usb path of servo %s is %s', self.servo_serial, usb_path)
Otabek Kasimov86062d02020-11-17 13:30:22 -0800746 return usb_path
Garry Wangffbd2162020-04-17 16:13:48 -0700747
Otabek Kasimov86062d02020-11-17 13:30:22 -0800748 def _get_servo_usb_devnum(self):
749 """Helper function to collect current usb devnum of servo."""
750 usb_path = self.get_main_servo_usb_path()
751 if not usb_path:
752 return ''
753 resp = self.run('cat %s/devnum' % usb_path, ignore_status=True)
Garry Wangffbd2162020-04-17 16:13:48 -0700754 if resp.exit_status != 0:
755 self._process_servodtool_error(resp)
756 return ''
757 return resp.stdout.strip()
758
Garry Wang358aad42020-08-02 20:56:04 -0700759 def reboot_servo_v3_on_need(self):
760 """Check and reboot servo_v3 based on below conditions.
761 1. If there is an update pending on reboot.
762 2. Servo_v3 has been up for more than 96 hours.
763 """
764 if self.get_board() != 'beaglebone_servo':
765 logging.info('Servo reboot is only applicable for servo V3.')
Otabek Kasimove6df8102020-07-21 20:15:25 -0700766 return
767
Garry Wang358aad42020-08-02 20:56:04 -0700768 update_pending_reboot = (self._check_update_status() ==
769 self.UPDATE_STATE.PENDING_REBOOT)
770 uptime_hours = float(self.check_uptime())/3600
771 logging.info('Uptime of servo_v3: %s hour(s)', uptime_hours)
772 long_up_time = uptime_hours > 96
773
774 # Skip reboot if neither condition are met.
775 if not (update_pending_reboot or long_up_time):
Otabek Kasimove6df8102020-07-21 20:15:25 -0700776 return
777
Garry Wang358aad42020-08-02 20:56:04 -0700778 if update_pending_reboot:
779 message = 'Starting reboot servo_v3 because an update is pending.'
780 reboot_method = self._post_update_reboot
781 elif long_up_time:
782 message = 'Starting reboot servo_v3 because uptime > 96 hours.'
783 reboot_method = self._servo_host_reboot
784 self.record('INFO', None, None, message)
785 logging.info(message)
Otabek Kasimove6df8102020-07-21 20:15:25 -0700786 try:
Garry Wang358aad42020-08-02 20:56:04 -0700787 reboot_method()
Otabek Kasimove6df8102020-07-21 20:15:25 -0700788 message = 'Servo_v3 reboot completed successfully.'
789 except Exception as e:
790 logging.debug("Fail to reboot servo_v3; %s", e)
791 message = ('Servo_v3 reboot failed, please check debug log '
792 'for details.')
793 logging.info(message)
794 self.record('INFO', None, None, message)
Garry Wangffbd2162020-04-17 16:13:48 -0700795
796 def _reset_servo(self):
797 logging.info('Resetting servo through smart usbhub.')
Otabek Kasimov09192682020-06-01 18:17:44 -0700798 # TODO remove try-except when fix crbug.com/1087964
799 try:
800 resp = self.run('servodtool device -s %s power-cycle' %
801 self.servo_serial, ignore_status=True,
802 timeout=30)
803 if resp.exit_status != 0:
804 self._process_servodtool_error(resp)
805 return False
806 except Exception as e:
807 # Here we catch only timeout errors.
808 # Other errors is filtered by ignore_status=True
809 logging.debug('Attempt to reset servo failed due to timeout;'
810 ' %s', e)
Garry Wangffbd2162020-04-17 16:13:48 -0700811 return False
812
813 logging.debug('Wait %s seconds for servo to come back from reset.',
814 servo_constants.SERVO_RESET_TIMEOUT_SECONDS)
815 time.sleep(servo_constants.SERVO_RESET_TIMEOUT_SECONDS)
Garry Wang000c6c02020-05-11 21:27:23 -0700816 # change the flag so we can update this label in later process.
817 self.smart_usbhub = True
Garry Wangffbd2162020-04-17 16:13:48 -0700818 return True
819
Garry Wangffbd2162020-04-17 16:13:48 -0700820 def reset_servo(self):
821 """Reset(power-cycle) the servo via smart usbhub.
822 """
823 if not self.is_labstation():
824 logging.info('Servo reset is not applicable to servo_v3.')
825 return
826
827 pre_reset_devnum = self._get_servo_usb_devnum()
828 logging.info('Servo usb devnum before reset: %s', pre_reset_devnum)
829 result = self._reset_servo()
830 if not result:
Garry Wangfd5c8b62020-06-08 15:36:54 -0700831 message = ('Failed to reset servo with serial: %s. (Please ignore'
832 ' this error if the DUT is not connected to a smart'
833 ' usbhub).' % self.servo_serial)
Garry Wangffbd2162020-04-17 16:13:48 -0700834 logging.warning(message)
835 self.record('INFO', None, None, message)
836 return
837
838 post_reset_devnum = self._get_servo_usb_devnum()
839 logging.info('Servo usb devnum after reset: %s', post_reset_devnum)
840 if not (pre_reset_devnum and post_reset_devnum):
841 message = ('Servo reset completed but unable to verify'
842 ' devnum change!')
843 elif pre_reset_devnum != post_reset_devnum:
844 message = ('Reset servo with serial %s completed successfully!'
845 % self.servo_serial)
846 else:
847 message = 'Servo reset completed but devnum is still not changed!'
848 logging.info(message)
849 self.record('INFO', None, None, message)
850
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800851 def _extract_compressed_logs(self, logdir, relevant_files):
852 """Decompress servod logs in |logdir|.
853
854 @param logdir: directory containing compressed servod logs.
855 @param relevant_files: list of files in |logdir| to consider.
856
857 @returns: tuple, (tarfiles, files) where
858 tarfiles: list of the compressed filenames that have been
859 extracted and deleted
860 files: list of the uncompressed files that were generated
861 """
862 # For all tar-files, first extract them to the directory, and
863 # then let the common flow handle them.
864 tarfiles = [cf for cf in relevant_files if
865 cf.endswith(self.COMPRESSION_SUFFIX)]
866 files = []
867 for f in tarfiles:
868 norm_name = os.path.basename(f)[:-len(self.COMPRESSION_SUFFIX)]
869 with tarfile.open(f) as tf:
870 # Each tarfile has only one member, as
871 # that's the compressed log.
872 member = tf.members[0]
873 # Manipulate so that it only extracts the basename, and not
874 # the directories etc.
875 member.name = norm_name
876 files.append(os.path.join(logdir, member.name))
877 tf.extract(member, logdir)
878 # File has been extracted: remove the compressed file.
879 os.remove(f)
880 return tarfiles, files
881
882 def _extract_mcu_logs(self, log_subdir):
883 """Extract MCU (EC, Cr50, etc) console output from servod debug logs.
884
885 Using the MCU_EXTRACTOR regex (above) extract and split out MCU console
886 lines from the logs to generate invidiual console logs e.g. after
887 this method, you can find an ec.txt and servo_v4.txt in |log_dir| if
888 those MCUs had any console input/output.
889
890 @param log_subdir: directory with log.DEBUG.txt main servod debug logs.
891 """
892 # Extract the MCU for each one. The MCU logs are only in the .DEBUG
893 # files
894 mcu_lines_file = os.path.join(log_subdir, 'log.DEBUG.txt')
895 if not os.path.exists(mcu_lines_file):
896 logging.info('No DEBUG logs found to extract MCU logs from.')
897 return
898 mcu_files = {}
899 mcu_file_template = '%s.txt'
900 with open(mcu_lines_file, 'r') as f:
901 for line in f:
902 match = self.MCU_EXTRACTOR.match(line)
903 if match:
904 mcu = match.group(self.MCU_GROUP).lower()
905 line = match.group(self.LINE_GROUP)
906 if mcu not in mcu_files:
907 mcu_file = os.path.join(log_subdir,
908 mcu_file_template % mcu)
909 mcu_files[mcu] = open(mcu_file, 'a')
910 fd = mcu_files[mcu]
911 fd.write(line + '\n')
912 for f in mcu_files:
913 mcu_files[f].close()
914
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800915 def remove_latest_log_symlinks(self):
916 """Remove the conveninence symlinks 'latest' servod logs."""
917 symlink_wildcard = '%s/latest*' % self.remote_log_dir
918 cmd = 'rm ' + symlink_wildcard
919 self.run(cmd, stderr_tee=None, ignore_status=True)
920
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700921 def probe_servod_restart(self, instance_ts, outdir):
922 """Grab servod logs from previous instances if part of this session.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800923
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700924 If since the last time this host called start_servod() servod crashed
925 and restarted, this helper finds those logs as well, and stores them
926 with the |OLD_LOG_SUFFIX| to investigate if necessary.
Prasad Vuppalapu5bd9da12020-03-31 01:46:47 +0000927
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700928 It also issues a panicinfo command to servo devices after the restart
929 to try and collect reboot information for debugging.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800930
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700931 @param instance_ts: the log timestamp that the current instance uses
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800932 @param outdir: directory to create a subdirectory into to place the
933 servod logs into.
934 """
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700935 if self._initial_instance_ts is None:
936 logging.info('No log timestamp grabbed successfully on servod '
937 'startup. Cannot check device restarts. Ignoring.')
938 return
939 if instance_ts == self._initial_instance_ts:
940 logging.debug('Servod appears to have run without restarting')
941 return
942 # Servod seems to have restarted (at least once). |_initial_instance_ts|
943 # is the first timestamp, and instance_ts is the current timestamp. Find
944 # all timestamps in between them, and grab the logs for each.
945 tss = self._find_instance_timestamps_between(self._initial_instance_ts,
946 instance_ts)
947 logging.info('Servod has restarted %d times between the start and the '
948 'end of this servo_host.', len(tss))
949 logging.info('This might be an issue. Will extract all logs from each '
950 'instance.')
951 logging.info('Logs that are not the currently running (about to turn '
952 'down) instance are maked with a .%s in their folder.',
953 self.OLD_LOG_SUFFIX)
954 for ts in tss:
955 self.get_instance_logs(ts, outdir, old=True)
956 # Lastly, servod has restarted due to a potential issue. Try to get
957 # panic information from servo micro and servo v4 for the current logs.
958 # This can only happen if the |_servo| attribute is initialized.
959 if self._servo:
960 for mcu in ['servo_micro', 'servo_v4']:
961 ctrl = '%s_uart_cmd' % mcu
962 if self._servo.has_control(ctrl):
963 logging.info('Trying to retrieve %r panicinfo into logs',
964 mcu)
965 try:
966 self._servo.set_nocheck(ctrl, 'panicinfo')
967 except error.TestFail as e:
968 logging.error('Failed to generate panicinfo for %r '
969 'logs. %s', mcu, str(e))
970
971 def _find_instance_timestamps_between(self, start_ts, end_ts):
972 """Find all log timestamps between [start_ts, end_ts).
973
974 @param start_ts: str, earliest log timestamp of interest
975 @param end_ts: str, latest log timestamp of interest
976
977 @returns: list, all timestamps between start_ts and end_ts, end_ts
978 exclusive, on the servo_host. An empty list on errors
979 """
980 # Simply get all timestamp, and then sort and remove
981 cmd = 'ls %s' % self.remote_log_dir
982 res = self.run(cmd, stderr_tee=None, ignore_status=True)
983 if res.exit_status != 0:
984 # Here we failed to find anything.
985 logging.info('Failed to find remote servod logs. Ignoring.')
986 return []
987 logfiles = res.stdout.strip().split()
988 timestamps = set()
989 for logfile in logfiles:
990 ts_match = self.TS_EXTRACTOR.match(logfile)
991 if not ts_match:
992 # Simply ignore files that fail the check. It might be the
993 # 'latest' symlinks or random files.
994 continue
995 timestamps.add(ts_match.group(self.TS_GROUP))
996 # At this point we have all unique timestamps.
997 timestamps = sorted(timestamps)
998 for ts in [start_ts, end_ts]:
999 if ts not in timestamps:
1000 logging.error('Timestamp %r not in servod logs. Cannot query '
1001 'for timestamps in between %r and %r', ts,
1002 start_ts, end_ts)
1003 return []
1004 return timestamps[timestamps.index(start_ts):timestamps.index(end_ts)]
1005
1006 def get_instance_logs_ts(self):
1007 """Retrieve the currently running servod instance's log timestamp
1008
1009 @returns: str, timestamp for current instance, or None on failure
1010 """
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001011 # First, extract the timestamp. This cmd gives the real filename of
1012 # the latest aka current log file.
1013 cmd = ('if [ -f %(dir)s/latest.DEBUG ];'
1014 'then realpath %(dir)s/latest.DEBUG;'
1015 'elif [ -f %(dir)s/latest ];'
1016 'then realpath %(dir)s/latest;'
1017 'else exit %(code)d;'
1018 'fi' % {'dir': self.remote_log_dir,
1019 'code': self.NO_SYMLINKS_CODE})
1020 res = self.run(cmd, stderr_tee=None, ignore_status=True)
1021 if res.exit_status != 0:
1022 if res.exit_status == self.NO_SYMLINKS_CODE:
1023 logging.warning('servod log latest symlinks not found. '
1024 'This is likely due to an error starting up '
1025 'servod. Ignoring..')
1026 else:
1027 logging.warning('Failed to find servod logs on servo host.')
1028 logging.warning(res.stderr.strip())
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001029 return None
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001030 fname = os.path.basename(res.stdout.strip())
1031 # From the fname, ought to extract the timestamp using the TS_EXTRACTOR
Ruben Rodriguez Buchillone9aa2b02020-03-04 12:14:28 -08001032 ts_match = self.TS_EXTRACTOR.match(fname)
1033 if not ts_match:
1034 logging.warning('Failed to extract timestamp from servod log file '
1035 '%r. Skipping. The servo host is using outdated '
1036 'servod logging and needs to be updated.', fname)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001037 return None
1038 return ts_match.group(self.TS_GROUP)
1039
1040 def get_instance_logs(self, instance_ts, outdir, old=False):
1041 """Collect all logs with |instance_ts| and dump into a dir in |outdir|
1042
1043 This method first collects all logs on the servo_host side pertaining
1044 to this servod instance (port, instatiation). It glues them together
1045 into combined log.[level].txt files and extracts all available MCU
1046 console I/O from the logs into individual files e.g. servo_v4.txt
1047
1048 All the output can be found in a directory inside |outdir| that
1049 this generates based on |LOG_DIR|, the servod port, and the instance
1050 timestamp on the servo_host side.
1051
1052 @param instance_ts: log timestamp to grab logfiles for
1053 @param outdir: directory to create a subdirectory into to place the
1054 servod logs into.
1055 @param old: bool, whether to append |OLD_LOG_SUFFIX| to output dir
1056 """
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001057 # Create the local results log dir.
1058 log_dir = os.path.join(outdir, '%s_%s.%s' % (self.LOG_DIR,
1059 str(self.servo_port),
1060 instance_ts))
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001061 if old:
Garry Wang22f2e842020-09-09 20:19:19 -07001062 log_dir = '%s.%s' % (log_dir, self.OLD_LOG_SUFFIX)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001063 logging.info('Saving servod logs to %r.', log_dir)
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001064 os.mkdir(log_dir)
1065 # Now, get all files with that timestamp.
1066 cmd = 'find %s -maxdepth 1 -name "log.%s*"' % (self.remote_log_dir,
1067 instance_ts)
1068 res = self.run(cmd, stderr_tee=None, ignore_status=True)
1069 files = res.stdout.strip().split()
1070 try:
1071 self.get_file(files, log_dir, try_rsync=False)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001072 if not os.listdir(log_dir):
1073 logging.info('No servod logs retrieved. Ignoring, and removing '
1074 '%r again.', log_dir)
1075 os.rmdir(log_dir)
1076 return
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001077 except error.AutoservRunError as e:
1078 result = e.result_obj
1079 if result.exit_status != 0:
1080 stderr = result.stderr.strip()
1081 logging.warning("Couldn't retrieve servod logs. Ignoring: %s",
1082 stderr or '\n%s' % result)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001083 # Remove the log_dir as nothing was added to it.
1084 os.rmdir(log_dir)
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001085 return
1086 local_files = [os.path.join(log_dir, f) for f in os.listdir(log_dir)]
1087 # TODO(crrev.com/c/1793030): remove no-level case once CL is pushed
1088 for level_name in ('DEBUG', 'INFO', 'WARNING', ''):
1089 # Create the joint files for each loglevel. i.e log.DEBUG
1090 joint_file = self.JOINT_LOG_PREFIX
1091 if level_name:
1092 joint_file = '%s.%s' % (self.JOINT_LOG_PREFIX, level_name)
1093 # This helps with some online tools to avoid complaints about an
1094 # unknown filetype.
1095 joint_file = joint_file + '.txt'
1096 joint_path = os.path.join(log_dir, joint_file)
1097 files = [f for f in local_files if level_name in f]
1098 if not files:
1099 # TODO(crrev.com/c/1793030): remove no-level case once CL
1100 # is pushed
1101 continue
1102 # Extract compressed logs if any.
1103 compressed, extracted = self._extract_compressed_logs(log_dir,
1104 files)
1105 files = list(set(files) - set(compressed))
1106 files.extend(extracted)
1107 # Need to sort. As they all share the same timestamp, and
1108 # loglevel, the index itself is sufficient. The highest index
1109 # is the oldest file, therefore we need a descending sort.
1110 def sortkey(f, level=level_name):
1111 """Custom sortkey to sort based on rotation number int."""
1112 if f.endswith(level_name): return 0
1113 return int(f.split('.')[-1])
1114
1115 files.sort(reverse=True, key=sortkey)
1116 # Just rename the first file rather than building from scratch.
1117 os.rename(files[0], joint_path)
1118 with open(joint_path, 'a') as joint_f:
1119 for logfile in files[1:]:
1120 # Transfer the file to the joint file line by line.
1121 with open(logfile, 'r') as log_f:
1122 for line in log_f:
1123 joint_f.write(line)
1124 # File has been written over. Delete safely.
1125 os.remove(logfile)
1126 # Need to remove all files form |local_files| so we don't
1127 # analyze them again.
1128 local_files = list(set(local_files) - set(files) - set(compressed))
1129 # Lastly, extract MCU logs from the joint logs.
1130 self._extract_mcu_logs(log_dir)
1131
Garry Wang79e9af62019-06-12 15:19:19 -07001132 def _lock(self):
1133 """lock servohost by touching a file.
1134 """
1135 logging.debug('Locking servohost %s by touching %s file',
1136 self.hostname, self._lock_file)
1137 self.run('touch %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -07001138 self._is_locked = True
Garry Wang79e9af62019-06-12 15:19:19 -07001139
Garry Wang79e9af62019-06-12 15:19:19 -07001140 def _unlock(self):
1141 """Unlock servohost by removing the lock file.
1142 """
1143 logging.debug('Unlocking servohost by removing %s file',
1144 self._lock_file)
1145 self.run('rm %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -07001146 self._is_locked = False
Garry Wang79e9af62019-06-12 15:19:19 -07001147
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001148 def close(self):
Congbin Guofc3b8962019-03-22 17:38:46 -07001149 """Close the associated servo and the host object."""
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001150 # NOTE: throughout this method there are multiple attempts to catch
1151 # all errors. This is WAI as log grabbing should not fail tests.
1152 # However, the goal is to catch and handle/process all errors, thus
1153 # we print the traceback and ask for a bug.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001154 if self._closed:
1155 logging.debug('ServoHost is already closed.')
1156 return
Garry Wang22f2e842020-09-09 20:19:19 -07001157
1158 # Only attempt ssh related actions if servohost is sshable. We call
1159 # check_cached_up_status() first because it's lightweighted and return
1160 # much faster in the case servohost is down, however, we still want
1161 # to call is_up() later since check_cached_up_status() is ping based check
1162 # and not guarantee the servohost is sshable.
1163 servo_host_ready = self.check_cached_up_status() and self.is_up()
1164
1165 if servo_host_ready:
1166 instance_ts = self.get_instance_logs_ts()
1167 else:
1168 logging.info('Servohost is down, will skip servod log collecting.')
1169 instance_ts = None
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001170 # TODO(crbug.com/1011516): once enabled, remove the check against
1171 # localhost and instead check against log-rotiation enablement.
1172 logs_available = (instance_ts is not None and
1173 self.job and
1174 not self.is_localhost())
1175 if logs_available:
1176 # Probe whether there was a servod restart, and grab those old
1177 # logs as well.
1178 try:
1179 self.probe_servod_restart(instance_ts, self.job.resultdir)
1180 except (error.AutoservRunError, error.TestFail) as e:
1181 logging.info('Failed to grab servo logs due to: %s. '
1182 'This error is forgiven.', str(e))
1183 except Exception as e:
1184 logging.error('Unexpected error probing for old logs. %s. '
1185 'Forgiven. Please file a bug and fix or catch '
1186 'in log probing function', str(e),
1187 exc_info=True)
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001188 if self._servo:
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001189 outdir = None if not self.job else self.job.resultdir
Congbin Guo2e5e2a22018-07-27 10:32:48 -07001190 # In some cases when we run as lab-tools, the job object is None.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001191 self._servo.close(outdir)
1192
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001193 if logs_available:
1194 # Grab current (not old like above) logs after the servo instance
1195 # was closed out.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001196 try:
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001197 self.get_instance_logs(instance_ts, self.job.resultdir)
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001198 except error.AutoservRunError as e:
1199 logging.info('Failed to grab servo logs due to: %s. '
1200 'This error is forgiven.', str(e))
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001201 except Exception as e:
1202 logging.error('Unexpected error grabbing servod logs. %s. '
1203 'Forgiven. Please file a bug and fix or catch '
1204 'in log grabbing function', str(e), exc_info=True)
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001205
Garry Wang22f2e842020-09-09 20:19:19 -07001206 if self._is_locked and servo_host_ready:
Garry Wang7c00b0f2019-06-25 17:28:17 -07001207 # Remove the lock if the servohost has been locked.
Garry Wang79e9af62019-06-12 15:19:19 -07001208 try:
1209 self._unlock()
1210 except error.AutoservSSHTimeout:
1211 logging.error('Unlock servohost failed due to ssh timeout.'
1212 ' It may caused by servohost went down during'
1213 ' the task.')
Garry Wangc1288cf2019-12-17 14:58:00 -08001214 # We want always stop servod after task to minimum the impact of bad
1215 # servod process interfere other servods.(see crbug.com/1028665)
Garry Wang22f2e842020-09-09 20:19:19 -07001216 if servo_host_ready:
1217 try:
1218 self.stop_servod()
1219 except error.AutoservRunError as e:
1220 logging.info(
1221 "Failed to stop servod due to:\n%s\n"
1222 "This error is forgiven.", str(e))
Garry Wangc1288cf2019-12-17 14:58:00 -08001223
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001224 super(ServoHost, self).close()
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001225 # Mark closed.
1226 self._closed = True
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001227
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001228 def get_servo_state(self):
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001229 return self._servo_state
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001230
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001231 def _get_host_metrics_data(self):
1232 return {'port': self.servo_port,
Otabek Kasimov0ea47362020-07-11 20:55:09 -07001233 'host': self.get_dut_hostname() or self.hostname,
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001234 'board': self.servo_board or ''}
1235
Otabek Kasimov15aafec2021-03-05 16:22:42 -08001236 def is_servo_board_present_on_servo_v3(self):
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001237 """Check if servo board is detected on servo_v3"""
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001238 logging.debug('Started to detect servo board on servo_v3')
Otabek Kasimov15aafec2021-03-05 16:22:42 -08001239 vid_pids = ['18d1:5004', '0403:6014']
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001240 not_detected = 'The servo board is not detected on servo_v3'
1241 try:
1242 cmd = 'lsusb | grep "%s"' % "\|".join(vid_pids)
1243 result = self.run(cmd, ignore_status=True, timeout=30)
1244 if result.exit_status == 0 and result.stdout.strip():
1245 logging.debug('The servo board is detected on servo_v3')
1246 return True
1247 logging.debug('%s; %s', not_detected, result)
1248 return False
1249 except Exception as e:
1250 # can be triggered by timeout issue due running the script
1251 metrics.Counter(
1252 'chromeos/autotest/repair/servo_detection/timeout'
1253 ).increment(fields=self._get_host_metrics_data())
1254 logging.error('%s; %s', not_detected, str(e))
1255 return None
1256
Garry Wangb5cee3e2020-09-16 14:58:13 -07001257 def _require_cr50_servod_config(self):
1258 """Check whether we need start servod with CONFIG=cr50.xml"""
1259 dut_host_info = self.get_dut_host_info()
1260 if not dut_host_info:
1261 return False
1262 for pool in dut_host_info.pools:
1263 if pool.startswith(servo_constants.CR50_CONFIG_POOL_PREFIX):
1264 return True
1265 return False
1266
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001267 def get_verifier_state(self, tag):
Otabek Kasimov15963492020-06-23 21:10:51 -07001268 """Return the state of servo verifier.
1269
1270 @returns: bool or None
1271 """
1272 return self._repair_strategy.verifier_is_good(tag)
1273
1274 def determine_servo_state(self):
1275 """Determine servo state based on the failed verifier.
1276
1277 @returns: servo state value
1278 The state detecting based on first fail verifier or collecting of
1279 them.
1280 """
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001281 ssh = self.get_verifier_state('servo_ssh')
Otabek Kasimov15aafec2021-03-05 16:22:42 -08001282 servo_root_present = self.get_verifier_state('servo_root_present')
1283 servo_v3_present = self.get_verifier_state('servo_v3_root_present')
Otabek Kasimov79be61b2021-02-18 12:30:44 -08001284 servo_fw = self.get_verifier_state('servo_fw')
Otabek Kasimov1614e2e2021-01-27 22:14:36 -08001285 disk_space = self.get_verifier_state('servo_disk_space')
1286 start_servod = self.get_verifier_state('servod_started')
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001287 create_servo = self.get_verifier_state('servod_connection')
1288 init_servo = self.get_verifier_state('servod_control')
Otabek Kasimov1614e2e2021-01-27 22:14:36 -08001289 cr50_low_sbu = self.get_verifier_state('servo_cr50_low_sbu')
1290 cr50_off = self.get_verifier_state('servo_cr50_off')
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001291 servo_topology = self.get_verifier_state('servo_topology')
Otabek Kasimov1614e2e2021-01-27 22:14:36 -08001292 dut_connected = self.get_verifier_state('servo_dut_connected')
1293 hub_connected = self.get_verifier_state('servo_hub_connected')
1294 pwr_button = self.get_verifier_state('servo_pwr_button')
1295 lid_open = self.get_verifier_state('servo_lid_open')
1296 ec_board = self.get_verifier_state('servo_ec_board')
1297 cr50_console = self.get_verifier_state('servo_cr50_console')
1298 ccd_testlab = self.get_verifier_state('servo_ccd_testlab')
Otabek Kasimov15963492020-06-23 21:10:51 -07001299
1300 if not ssh:
1301 return servo_constants.SERVO_STATE_NO_SSH
Otabek Kasimov15aafec2021-03-05 16:22:42 -08001302 if servo_root_present == hosts.VERIFY_FAILED:
1303 if not self.servo_serial:
1304 return servo_constants.SERVO_STATE_WRONG_CONFIG
1305 return servo_constants.SERVO_STATE_NOT_CONNECTED
1306 if servo_v3_present == hosts.VERIFY_FAILED:
1307 # if we cannot find required board on servo_v3
1308 return servo_constants.SERVO_STATE_NEED_REPLACEMENT
Otabek Kasimov79be61b2021-02-18 12:30:44 -08001309 if servo_fw == hosts.VERIFY_FAILED:
1310 return servo_constants.SERVO_STATE_NEED_REPLACEMENT
Otabek Kasimov15963492020-06-23 21:10:51 -07001311
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001312 if dut_connected == hosts.VERIFY_FAILED:
1313 return servo_constants.SERVO_STATE_DUT_NOT_CONNECTED
1314 if hub_connected == hosts.VERIFY_FAILED:
1315 logging.info('Servo HUB not connected')
1316 return servo_constants.SERVO_STATE_DUT_NOT_CONNECTED
Otabek Kasimov15aafec2021-03-05 16:22:42 -08001317
Otabek Kasimov8e88a742021-01-11 18:03:13 -08001318 if cr50_low_sbu == hosts.VERIFY_FAILED:
1319 return servo_constants.SERVO_STATE_SBU_LOW_VOLTAGE
1320 if cr50_off == hosts.VERIFY_FAILED:
1321 return servo_constants.SERVO_STATE_CR50_NOT_ENUMERATED
Otabek Kasimov15aafec2021-03-05 16:22:42 -08001322
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001323 if servo_topology == hosts.VERIFY_FAILED:
1324 return servo_constants.SERVO_STATE_TOPOLOGY_ISSUE
1325
Otabek Kasimovd5065bd2020-11-23 23:32:36 -08001326 # TODO(otabek@): detect special cases detected by pwr_button
1327 if dut_connected == hosts.VERIFY_SUCCESS:
1328 if pwr_button == hosts.VERIFY_FAILED:
1329 metrics.Counter(
1330 'chromeos/autotest/repair/servo_unexpected/pwr_button2'
1331 ).increment(fields=self._get_host_metrics_data())
Otabek Kasimova7eb4dc2020-09-16 10:25:17 -07001332
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001333 if start_servod == hosts.VERIFY_FAILED:
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001334 return servo_constants.SERVO_STATE_SERVOD_ISSUE
1335
Otabek Kasimov15963492020-06-23 21:10:51 -07001336 # one of the reason why servo can not initialized
Otabek Kasimovbb3bc462020-11-03 16:40:33 -08001337 if cr50_console == hosts.VERIFY_FAILED:
1338 return servo_constants.SERVO_STATE_CR50_CONSOLE_MISSING
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001339 if ccd_testlab == hosts.VERIFY_FAILED:
Otabek Kasimov15963492020-06-23 21:10:51 -07001340 return servo_constants.SERVO_STATE_CCD_TESTLAB_ISSUE
1341
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001342 if (create_servo == hosts.VERIFY_FAILED
1343 or init_servo == hosts.VERIFY_FAILED):
Otabek Kasimov15963492020-06-23 21:10:51 -07001344 return servo_constants.SERVO_STATE_SERVOD_ISSUE
1345
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001346 if ec_board == hosts.VERIFY_FAILED:
Otabek Kasimov015c15c2020-08-20 00:40:42 -07001347 return servo_constants.SERVO_STATE_EC_BROKEN
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001348 if pwr_button == hosts.VERIFY_FAILED:
Otabek Kasimov15963492020-06-23 21:10:51 -07001349 return servo_constants.SERVO_STATE_BAD_RIBBON_CABLE
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001350 if lid_open == hosts.VERIFY_FAILED:
Otabek Kasimov15963492020-06-23 21:10:51 -07001351 return servo_constants.SERVO_STATE_LID_OPEN_FAILED
Otabek Kasimov15963492020-06-23 21:10:51 -07001352
Otabek Kasimov15963492020-06-23 21:10:51 -07001353 metrics.Counter(
1354 'chromeos/autotest/repair/unknown_servo_state'
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001355 ).increment(fields=self._get_host_metrics_data())
Otabek Kasimov15963492020-06-23 21:10:51 -07001356 logging.info('We do not have special state for this failure yet :)')
1357 return servo_constants.SERVO_STATE_BROKEN
1358
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001359 def is_servo_topology_supported(self):
1360 """Check if servo_topology is supported."""
Otabek Kasimovda994012020-11-25 15:23:04 -08001361 if not self.is_up_fast():
1362 logging.info('Servo-Host is not reachable.')
1363 return False
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001364 if not self.is_labstation():
1365 logging.info('Servo-topology supported only for labstation.')
1366 return False
1367 if not self.servo_serial:
1368 logging.info('Servo-topology required a servo serial.')
1369 return False
1370 return True
1371
1372 def get_topology(self):
1373 """Get servo topology."""
Otabek Kasimovfe41e2d2021-02-14 20:48:52 -08001374 if not self._topology:
1375 self._topology = servo_topology.ServoTopology(self)
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001376 return self._topology
1377
1378 def is_dual_setup(self):
1379 """Check is servo will run in dual setup.
1380
1381 Dual setup used only for servo_v4 when used ccd_cr50 and servo_micro
1382 at the same time.
1383 """
1384 return self.servo_setup == servo_constants.SERVO_SETUP_VALUE_DUAL_V4
1385
Otabek Kasimov39637412020-11-23 19:09:27 -08001386 def set_dut_health_profile(self, dut_health_profile):
1387 """
1388 @param dut_health_profile: A DeviceHealthProfile object.
1389 """
1390 logging.debug('setting dut_health_profile field to (%s)',
1391 dut_health_profile)
1392 self._dut_health_profile = dut_health_profile
1393
1394 def get_dut_health_profile(self):
1395 """
1396 @return A DeviceHealthProfile object.
1397 """
1398 return self._dut_health_profile
1399
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001400
Richard Barnetteea3e4602016-06-10 12:36:41 -07001401def make_servo_hostname(dut_hostname):
1402 """Given a DUT's hostname, return the hostname of its servo.
1403
1404 @param dut_hostname: hostname of a DUT.
1405
1406 @return hostname of the DUT's servo.
1407
1408 """
1409 host_parts = dut_hostname.split('.')
1410 host_parts[0] = host_parts[0] + '-servo'
1411 return '.'.join(host_parts)
1412
1413
Richard Barnettee519dcd2016-08-15 17:37:17 -07001414def _map_afe_board_to_servo_board(afe_board):
1415 """Map a board we get from the AFE to a servo appropriate value.
1416
1417 Many boards are identical to other boards for servo's purposes.
1418 This function makes that mapping.
1419
1420 @param afe_board string board name received from AFE.
1421 @return board we expect servo to have.
1422
1423 """
1424 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
1425 BOARD_MAP = {'gizmo': 'panther'}
1426 mapped_board = afe_board
1427 if afe_board in BOARD_MAP:
1428 mapped_board = BOARD_MAP[afe_board]
1429 else:
1430 for suffix in KNOWN_SUFFIXES:
1431 if afe_board.endswith(suffix):
1432 mapped_board = afe_board[0:-len(suffix)]
1433 break
1434 if mapped_board != afe_board:
1435 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
1436 return mapped_board
1437
1438
Prathmesh Prabhub4810232018-09-07 13:24:08 -07001439def get_servo_args_for_host(dut_host):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -07001440 """Return servo data associated with a given DUT.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001441
Richard Barnetteea3e4602016-06-10 12:36:41 -07001442 @param dut_host Instance of `Host` on which to find the servo
1443 attributes.
Prathmesh Prabhuf605dd32018-08-28 17:09:04 -07001444 @return `servo_args` dict with host and an optional port.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001445 """
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001446 info = dut_host.host_info_store.get()
Derek Beckettf73baca2020-08-19 15:08:47 -07001447 servo_args = {k: v for k, v in six.iteritems(info.attributes)
Garry Wang11b5e872020-03-11 15:14:08 -07001448 if k in servo_constants.SERVO_ATTR_KEYS}
Richard Barnetteea3e4602016-06-10 12:36:41 -07001449
Andrew Luo4be621d2020-03-21 07:01:13 -07001450 if servo_constants.SERVO_HOST_SSH_PORT_ATTR in servo_args:
1451 try:
1452 servo_args[servo_constants.SERVO_HOST_SSH_PORT_ATTR] = int(
1453 servo_args[servo_constants.SERVO_HOST_SSH_PORT_ATTR])
1454 except ValueError:
1455 logging.error('servo host port is not an int: %s',
1456 servo_args[servo_constants.SERVO_HOST_SSH_PORT_ATTR])
1457 # Reset servo_args because we don't want to use an invalid port.
1458 servo_args.pop(servo_constants.SERVO_HOST_SSH_PORT_ATTR, None)
1459
Garry Wang11b5e872020-03-11 15:14:08 -07001460 if servo_constants.SERVO_PORT_ATTR in servo_args:
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001461 try:
Garry Wang11b5e872020-03-11 15:14:08 -07001462 servo_args[servo_constants.SERVO_PORT_ATTR] = int(
1463 servo_args[servo_constants.SERVO_PORT_ATTR])
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001464 except ValueError:
1465 logging.error('servo port is not an int: %s',
Garry Wang11b5e872020-03-11 15:14:08 -07001466 servo_args[servo_constants.SERVO_PORT_ATTR])
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001467 # Reset servo_args because we don't want to use an invalid port.
Garry Wang11b5e872020-03-11 15:14:08 -07001468 servo_args.pop(servo_constants.SERVO_HOST_ATTR, None)
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001469
1470 if info.board:
Garry Wang11b5e872020-03-11 15:14:08 -07001471 servo_board = _map_afe_board_to_servo_board(info.board)
1472 servo_args[servo_constants.SERVO_BOARD_ATTR] = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -07001473 if info.model:
Garry Wang11b5e872020-03-11 15:14:08 -07001474 servo_args[servo_constants.SERVO_MODEL_ATTR] = info.model
1475 return servo_args if servo_constants.SERVO_HOST_ATTR in servo_args else None
Richard Barnetteea3e4602016-06-10 12:36:41 -07001476
1477
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -07001478def _tweak_args_for_ssp_moblab(servo_args):
Garry Wang11b5e872020-03-11 15:14:08 -07001479 if (servo_args[servo_constants.SERVO_HOST_ATTR]
1480 in ['localhost', '127.0.0.1']):
1481 servo_args[servo_constants.SERVO_HOST_ATTR] = _CONFIG.get_config_value(
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -07001482 'SSP', 'host_container_ip', type=str, default=None)
1483
1484
Otabek Kasimov39637412020-11-23 19:09:27 -08001485def create_servo_host(dut,
1486 servo_args,
1487 try_lab_servo=False,
1488 try_servo_repair=False,
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001489 try_servo_recovery=False,
Otabek Kasimov39637412020-11-23 19:09:27 -08001490 dut_host_info=None,
1491 dut_health_profile=None):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -07001492 """Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -08001493
Richard Barnette9a26ad62016-06-10 12:03:08 -07001494 This function attempts to create and verify or repair a `ServoHost`
1495 object for a servo connected to the given `dut`, subject to various
1496 constraints imposed by the parameters:
1497 * When the `servo_args` parameter is not `None`, a servo
1498 host must be created, and must be checked with `repair()`.
1499 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
1500 true:
1501 * If `try_servo_repair` is true, then create a servo host and
1502 check it with `repair()`.
1503 * Otherwise, if the servo responds to `ping` then create a
1504 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -08001505
Richard Barnette9a26ad62016-06-10 12:03:08 -07001506 In cases where `servo_args` was not `None`, repair failure
1507 exceptions are passed back to the caller; otherwise, exceptions
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001508 are logged and then discarded. Note that this only happens in cases
1509 where we're called from a test (not special task) control file that
1510 has an explicit dependency on servo. In that case, we require that
1511 repair not write to `status.log`, so as to avoid polluting test
1512 results.
1513
1514 TODO(jrbarnette): The special handling for servo in test control
1515 files is a thorn in my flesh; I dearly hope to see it cut out before
1516 my retirement.
Richard Barnette9a26ad62016-06-10 12:03:08 -07001517
1518 Parameters for a servo host consist of a host name, port number, and
1519 DUT board, and are determined from one of these sources, in order of
1520 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -07001521 * Servo attributes from the `dut` parameter take precedence over
1522 all other sources of information.
1523 * If a DNS entry for the servo based on the DUT hostname exists in
1524 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -07001525 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001526 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -07001527 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001528
1529 @param dut An instance of `Host` from which to take
1530 servo parameters (if available).
1531 @param servo_args A dictionary with servo parameters to use if
1532 they can't be found from `dut`. If this
1533 argument is supplied, unrepaired exceptions
1534 from `verify()` will be passed back to the
1535 caller.
1536 @param try_lab_servo If not true, servo host creation will be
1537 skipped unless otherwise required by the
1538 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -07001539 @param try_servo_repair If true, check a servo host with
1540 `repair()` instead of `verify()`.
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001541 @param try_servo_recovery If true, start servod in recovery mode.
Otabek Kasimov8475cce2020-07-14 12:11:31 -07001542 @param dut_host_info: A HostInfo object of the DUT that connected
1543 to this servo.
Otabek Kasimov39637412020-11-23 19:09:27 -08001544 @param dut_health_profile: DUT repair info with history.
Dan Shi4d478522014-02-14 13:46:32 -08001545
1546 @returns: A ServoHost object or None. See comments above.
1547
1548 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001549 servo_dependency = servo_args is not None
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001550 if dut is not None and (try_lab_servo or servo_dependency):
Prathmesh Prabhub4810232018-09-07 13:24:08 -07001551 servo_args_override = get_servo_args_for_host(dut)
Richard Barnetteea3e4602016-06-10 12:36:41 -07001552 if servo_args_override is not None:
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -07001553 if utils.in_moblab_ssp():
1554 _tweak_args_for_ssp_moblab(servo_args_override)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001555 logging.debug(
1556 'Overriding provided servo_args (%s) with arguments'
1557 ' determined from the host (%s)',
1558 servo_args,
1559 servo_args_override,
1560 )
Richard Barnetteea3e4602016-06-10 12:36:41 -07001561 servo_args = servo_args_override
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001562
Richard Barnetteea3e4602016-06-10 12:36:41 -07001563 if servo_args is None:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001564 logging.debug('No servo_args provided, and failed to find overrides.')
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001565 if try_lab_servo or servo_dependency:
Otabek Kasimov646812c2020-06-23 20:01:36 -07001566 return None, servo_constants.SERVO_STATE_MISSING_CONFIG
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001567 else:
1568 # For regular test case which not required the servo
1569 return None, None
1570
Garry Wang11b5e872020-03-11 15:14:08 -07001571 servo_hostname = servo_args.get(servo_constants.SERVO_HOST_ATTR)
1572 servo_port = servo_args.get(servo_constants.SERVO_PORT_ATTR)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001573 if not _is_servo_host_information_exist(servo_hostname, servo_port):
1574 logging.debug(
1575 'Servo connection info missed hostname: %s , port: %s',
1576 servo_hostname, servo_port)
Otabek Kasimov646812c2020-06-23 20:01:36 -07001577 return None, servo_constants.SERVO_STATE_MISSING_CONFIG
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001578 if not is_servo_host_information_valid(servo_hostname, servo_port):
1579 logging.debug(
1580 'Servo connection info is incorrect hostname: %s , port: %s',
1581 servo_hostname, servo_port)
Garry Wang11b5e872020-03-11 15:14:08 -07001582 return None, servo_constants.SERVO_STATE_WRONG_CONFIG
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001583
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001584 if try_servo_recovery == True:
1585 servo_args[servo_constants.SERVO_RECOVERY_MODE] = True
1586
Garry Wangebc015b2019-06-06 17:45:06 -07001587 newhost = ServoHost(**servo_args)
Andrew Luo4be621d2020-03-21 07:01:13 -07001588 if newhost.use_icmp and not newhost.is_up_fast(count=3):
Otabek Kasimov32cafe92020-12-14 16:58:12 -08001589 # ServoHost has internal check to wait if servo-host is in reboot
1590 # process. If servo-host still is not available this check will stop
1591 # further attempts as we do not have any option to recover servo_host.
Otabek Kasimov39637412020-11-23 19:09:27 -08001592 return None, servo_constants.SERVO_STATE_NO_SSH
Garry Wangffbd2162020-04-17 16:13:48 -07001593
Otabek Kasimove6df8102020-07-21 20:15:25 -07001594 # Reset or reboot servo device only during AdminRepair tasks.
1595 if try_servo_repair:
1596 if newhost._is_locked:
1597 # Reset servo if the servo is locked, as we check if the servohost
1598 # is up, if the servohost is labstation and if the servohost is in
1599 # lab inside the locking logic.
1600 newhost.reset_servo()
1601 else:
Garry Wang358aad42020-08-02 20:56:04 -07001602 try:
1603 newhost.reboot_servo_v3_on_need()
Garry Wang1f0d5332020-08-10 19:32:32 -07001604 except Exception as e:
1605 logging.info('[Non-critical] Unexpected error while trying to'
1606 ' reboot servo_v3, skipping the reboot; %s', e)
Otabek Kasimove6df8102020-07-21 20:15:25 -07001607
Otabek Kasimov2b50cdb2020-07-06 19:16:06 -07001608 if dut:
1609 newhost.set_dut_hostname(dut.hostname)
Otabek Kasimov9e90ae12020-08-14 03:01:19 -07001610 if dut_host_info:
1611 newhost.set_dut_host_info(dut_host_info)
Otabek Kasimov39637412020-11-23 19:09:27 -08001612 if dut_health_profile and (try_lab_servo or try_servo_repair):
1613 try:
1614 if newhost.is_localhost():
1615 logging.info('Servohost is a localhost, skip device'
1616 ' health profile setup...')
1617 else:
1618 dut_health_profile.init_profile(newhost)
1619 newhost.set_dut_health_profile(dut_health_profile)
1620 except Exception as e:
1621 logging.info(
1622 '[Non-critical] Unexpected error while trying to'
1623 ' load device health profile; %s', e)
Garry Wangffbd2162020-04-17 16:13:48 -07001624
Richard Barnette9a26ad62016-06-10 12:03:08 -07001625 # Note that the logic of repair() includes everything done
1626 # by verify(). It's sufficient to call one or the other;
1627 # we don't need both.
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001628 if servo_dependency:
1629 newhost.repair(silent=True)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001630 return newhost, newhost.get_servo_state()
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001631
1632 if try_servo_repair:
1633 try:
1634 newhost.repair()
1635 except Exception:
1636 logging.exception('servo repair failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -07001637 else:
1638 try:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001639 newhost.verify()
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -07001640 except Exception:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001641 logging.exception('servo verify failed for %s', newhost.hostname)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001642 return newhost, newhost.get_servo_state()
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001643
1644
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001645def _is_servo_host_information_exist(hostname, port):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001646 if hostname is None or len(hostname.strip()) == 0:
1647 return False
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001648 if port is None:
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001649 return False
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001650 if not type(port) is int:
1651 try:
1652 int(port)
1653 except ValueError:
1654 return False
1655
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001656 return True
1657
1658
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001659def is_servo_host_information_valid(hostname, port):
Garry Wang9b8f2342020-04-17 16:34:09 -07001660 """Check if provided servo attributes are valid.
1661
1662 @param hostname Hostname of the servohost.
1663 @param port servo port number.
1664
1665 @returns: A bool value to indicate if provided servo attribute valid.
1666 """
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001667 if not _is_servo_host_information_exist(hostname, port):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001668 return False
1669 # checking range and correct of the port
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001670 port_int = int(port)
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001671 if port_int < 1 or port_int > 65000:
1672 return False
1673 # we expecting host contain only latters, digits and '-' or '_'
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001674 if not re.match('[a-zA-Z0-9-_\.]*$', hostname) or len(hostname) < 5:
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001675 return False
1676 return True