blob: 13f202da9ca99ba0cea1fd4a23d22c3d1cea67a1 [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
Christopher Wileycef1f902014-06-19 11:11:23 -070033from autotest_lib.client.common_lib.cros.network import ping_runner
Richard Barnette9a26ad62016-06-10 12:03:08 -070034from autotest_lib.server.cros.servo import servo
Richard Barnetted31580e2018-05-14 19:58:00 +000035from autotest_lib.server.hosts import servo_repair
Garry Wangebc015b2019-06-06 17:45:06 -070036from autotest_lib.server.hosts import base_servohost
Garry Wang11b5e872020-03-11 15:14:08 -070037from autotest_lib.server.hosts import servo_constants
Otabek Kasimov4ea636e2020-04-14 23:35:06 -070038from autotest_lib.server.cros.faft.utils import config
Garry Wang11b5e872020-03-11 15:14:08 -070039from autotest_lib.client.common_lib import global_config
Otabek Kasimov8475cce2020-07-14 12:11:31 -070040from autotest_lib.site_utils.admin_audit import servo_updater
Otabek Kasimov382c3bb2020-10-28 13:22:45 -070041from autotest_lib.server.cros.servo.topology import servo_topology
Garry Wangd7367482020-02-27 13:52:40 -080042
Otabek Kasimov15963492020-06-23 21:10:51 -070043try:
44 from chromite.lib import metrics
45except ImportError:
46 metrics = utils.metrics_mock
47
Dan Shi3b2adf62015-09-02 17:46:54 -070048_CONFIG = global_config.global_config
Fang Deng5d518f42013-08-02 14:04:32 -070049
Otabek Kasimova7ba91a2020-03-09 08:31:01 -070050
Garry Wangebc015b2019-06-06 17:45:06 -070051class ServoHost(base_servohost.BaseServoHost):
52 """Host class for a servo host(e.g. beaglebone, labstation)
Dana Goyette0b6e6402019-10-04 11:09:24 -070053 that with a servo instance for a specific port.
54
55 @type _servo: servo.Servo | None
56 """
Fang Deng5d518f42013-08-02 14:04:32 -070057
Raul E Rangel52ca2e82018-07-03 14:10:14 -060058 DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999'))
Richard Barnette9a26ad62016-06-10 12:03:08 -070059
Dan Shie5b3c512014-08-21 12:12:09 -070060 # Timeout for initializing servo signals.
Wai-Hong Tam37b6ed32017-09-19 15:52:39 -070061 INITIALIZE_SERVO_TIMEOUT_SECS = 60
Richard Barnette9a26ad62016-06-10 12:03:08 -070062
Otabek Kasimov545739c2020-08-20 00:24:21 -070063 # Default timeout for run terminal command.
64 DEFAULT_TERMINAL_TIMEOUT = 30
65
xixuan6cf6d2f2016-01-29 15:29:00 -080066 # Ready test function
67 SERVO_READY_METHOD = 'get_version'
Fang Deng5d518f42013-08-02 14:04:32 -070068
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -080069 # Directory prefix on the servo host where the servod logs are stored.
70 SERVOD_LOG_PREFIX = '/var/log/servod'
71
72 # Exit code to use when symlinks for servod logs are not found.
73 NO_SYMLINKS_CODE = 9
74
75 # Directory in the job's results directory to dump the logs into.
76 LOG_DIR = 'servod'
77
78 # Prefix for joint loglevel files in the logs.
79 JOINT_LOG_PREFIX = 'log'
80
81 # Regex group to extract timestamp from logfile name.
82 TS_GROUP = 'ts'
83
84 # This regex is used to extract the timestamp from servod logs.
Garry Wang22f2e842020-09-09 20:19:19 -070085 # files always start with log.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -080086 TS_RE = (r'log.'
87 # The timestamp is of format %Y-%m-%d--%H-%M-%S.MS
88 r'(?P<%s>\d{4}(\-\d{2}){2}\-(-\d{2}){3}.\d{3})'
89 # The loglevel is optional depending on labstation version.
90 r'(.(INFO|DEBUG|WARNING))?' % TS_GROUP)
91 TS_EXTRACTOR = re.compile(TS_RE)
92
93 # Regex group to extract MCU name from logline in servod logs.
94 MCU_GROUP = 'mcu'
95
96 # Regex group to extract logline from MCU logline in servod logs.
97 LINE_GROUP = 'line'
98
99 # This regex is used to extract the mcu and the line content from an
100 # MCU logline in servod logs. e.g. EC or servo_v4 console logs.
101 # Here is an example log-line:
102 #
103 # 2020-01-23 13:15:12,223 - servo_v4 - EC3PO.Console - DEBUG -
104 # console.py:219:LogConsoleOutput - /dev/pts/9 - cc polarity: cc1
105 #
106 # Here is conceptually how they are formatted:
107 #
108 # <time> - <MCU> - EC3PO.Console - <LVL> - <file:line:func> - <pts> -
109 # <output>
110 #
Garry Wang22f2e842020-09-09 20:19:19 -0700111 # The log format starts with a timestamp
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800112 MCU_RE = (r'[\d\-]+ [\d:,]+ '
113 # The mcu that is logging this is next.
114 r'- (?P<%s>\w+) - '
115 # Next, we have more log outputs before the actual line.
116 # Information about the file line, logging function etc.
117 # Anchor on EC3PO Console, LogConsoleOutput and dev/pts.
118 # NOTE: if the log format changes, this regex needs to be
119 # adjusted.
120 r'EC3PO\.Console[\s\-\w\d:.]+LogConsoleOutput - /dev/pts/\d+ - '
121 # Lastly, we get the MCU's console line.
122 r'(?P<%s>.+$)' % (MCU_GROUP, LINE_GROUP))
123 MCU_EXTRACTOR = re.compile(MCU_RE)
124
Otabek Kasimov545739c2020-08-20 00:24:21 -0700125 # Regex to detect timeout messages when USBC pigtail has timeout issue.
126 # e.g.: [475635.427072 PD TMOUT RX 1/1]
127 USBC_PIGTAIL_TIMEOUT_RE = r'\[[\d \.]{1,20}(PD TMOUT RX 1\/1)\]'
128
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800129 # Suffix to identify compressed logfiles.
130 COMPRESSION_SUFFIX = '.tbz2'
131
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700132 # A suffix to mark servod log directories that came from instance that
133 # ran during this servo_host, but are not the last one running e.g. when
134 # an instance (on purpose, or due to a bug) restarted in the middle of the
135 # run.
136 OLD_LOG_SUFFIX = 'old'
137
Otabek Kasimovc6f30412020-06-30 20:08:12 -0700138 # Mapping servo board with their vid-pid
139 SERVO_VID_PID = {
Evan Green817a8d02020-12-01 16:45:36 -0800140 'servo_v4': '18d1:501b',
141 'ccd_cr50': '18d1:5014',
142 'servo_micro': '18d1:501a',
143 'servo_v3': ['18d1:5004', '0403:6014'],
144 'c2d2': '1d81:5041',
Otabek Kasimovc6f30412020-06-30 20:08:12 -0700145 }
146
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800147 def _init_attributes(self):
148 self._servo_state = None
149 self.servo_port = None
150 self.servo_board = None
151 self.servo_model = None
152 self.servo_serial = None
Garry Wangcb06f3b2020-10-08 20:56:21 -0700153 self.servo_setup = None
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800154 self.servo_recovery = None
Garry Wang6a680062020-11-03 13:40:29 -0800155 self.additional_servod_args = None
Otabek Kasimov39637412020-11-23 19:09:27 -0800156 self._dut_health_profile = None
Garry Wang000c6c02020-05-11 21:27:23 -0700157 # The flag that indicate if a servo is connected to a smart usbhub.
158 # TODO(xianuowang@) remove this flag once all usbhubs in the lab
159 # get replaced.
160 self.smart_usbhub = None
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800161 self._servo = None
Otabek Kasimov382c3bb2020-10-28 13:22:45 -0700162 self._topology = None
Andrew McRaef0679932020-08-13 09:15:23 +1000163 self._tunnel_proxy = None
164 self._tunnel_proxy_lock = threading.Lock()
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700165 self._initial_instance_ts = None
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800166 # Flag to make sure that multiple calls to close do not result in the
167 # logic executing multiple times.
168 self._closed = False
Andrew McRaef0679932020-08-13 09:15:23 +1000169 # Per-thread local data
170 self._local = threading.local()
Fang Deng5d518f42013-08-02 14:04:32 -0700171
Garry Wangcb06f3b2020-10-08 20:56:21 -0700172 def _initialize(self,
173 servo_host='localhost',
174 servo_port=DEFAULT_PORT,
175 servo_board=None,
176 servo_model=None,
177 servo_serial=None,
178 servo_setup=None,
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800179 servo_recovery=None,
Garry Wang6a680062020-11-03 13:40:29 -0800180 additional_servod_args=None,
Garry Wangcb06f3b2020-10-08 20:56:21 -0700181 is_in_lab=None,
182 *args,
183 **dargs):
Fang Deng5d518f42013-08-02 14:04:32 -0700184 """Initialize a ServoHost instance.
185
186 A ServoHost instance represents a host that controls a servo.
187
188 @param servo_host: Name of the host where the servod process
189 is running.
Raul E Rangel52ca2e82018-07-03 14:10:14 -0600190 @param servo_port: Port the servod process is listening on. Defaults
191 to the SERVOD_PORT environment variable if set,
192 otherwise 9999.
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700193 @param servo_board: Board that the servo is connected to.
Nick Sanders2f3c9852018-10-24 12:10:24 -0700194 @param servo_model: Model that the servo is connected to.
Garry Wangcb06f3b2020-10-08 20:56:21 -0700195 @param servo_serial: Serial number of the servo device.
196 @param servo_setup: Type of servo setup, e.g. REGULAR or DUAL_V4.
Garry Wang6a680062020-11-03 13:40:29 -0800197 @param additional_servod_args: Additional args that will append to
198 servod start command.
Dan Shi4d478522014-02-14 13:46:32 -0800199 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
200 to None, for which utils.host_is_in_lab_zone will be
201 called to check if the servo host is in Cros lab.
Fang Deng5d518f42013-08-02 14:04:32 -0700202
203 """
204 super(ServoHost, self)._initialize(hostname=servo_host,
Garry Wangebc015b2019-06-06 17:45:06 -0700205 is_in_lab=is_in_lab, *args, **dargs)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -0800206 self._init_attributes()
Richard Barnette42f4db92018-08-23 15:05:15 -0700207 self.servo_port = int(servo_port)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700208 self.servo_board = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -0700209 self.servo_model = servo_model
Kevin Cheng643ce8a2016-09-15 15:42:12 -0700210 self.servo_serial = servo_serial
Garry Wangcb06f3b2020-10-08 20:56:21 -0700211 self.servo_setup = servo_setup
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800212 self.servo_recovery = servo_recovery
Garry Wang6a680062020-11-03 13:40:29 -0800213 self.additional_servod_args = additional_servod_args
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800214
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800215 # The location of the log files on the servo host for this instance.
216 self.remote_log_dir = '%s_%s' % (self.SERVOD_LOG_PREFIX,
217 self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700218 # Path of the servo host lock file.
Derek Beckettf73baca2020-08-19 15:08:47 -0700219 self._lock_file = (self.TEMP_FILE_DIR + str(self.servo_port) +
220 self.LOCK_FILE_POSTFIX)
Garry Wang79e9af62019-06-12 15:19:19 -0700221 # File path to declare a reboot request.
Derek Beckettf73baca2020-08-19 15:08:47 -0700222 self._reboot_file = (self.TEMP_FILE_DIR + str(self.servo_port) +
223 self.REBOOT_FILE_POSTFIX)
Garry Wang79e9af62019-06-12 15:19:19 -0700224
225 # Lock the servo host if it's an in-lab labstation to prevent other
226 # task to reboot it until current task completes. We also wait and
227 # make sure the labstation is up here, in the case of the labstation is
228 # in the middle of reboot.
Garry Wang7c00b0f2019-06-25 17:28:17 -0700229 self._is_locked = False
Garry Wang42b4d862019-06-25 15:50:49 -0700230 if (self.wait_up(self.REBOOT_TIMEOUT) and self.is_in_lab()
231 and self.is_labstation()):
Garry Wang79e9af62019-06-12 15:19:19 -0700232 self._lock()
Garry Wang78ce64d2020-10-13 18:23:45 -0700233 try:
234 self.wait_ready()
235 except Exception as e:
236 logging.info(
237 'Unexpected error while ensure labstation'
238 ' readiness; %s', str(e))
Garry Wangebc015b2019-06-06 17:45:06 -0700239
Richard Barnette9a26ad62016-06-10 12:03:08 -0700240 self._repair_strategy = (
241 servo_repair.create_servo_repair_strategy())
Richard Barnettee519dcd2016-08-15 17:37:17 -0700242
Dana Goyetteafa62fd2020-03-16 13:45:27 -0700243 def __str__(self):
244 return "<%s '%s:%s'>" % (
245 type(self).__name__, self.hostname, self.servo_port)
246
Richard Barnette9a26ad62016-06-10 12:03:08 -0700247 def connect_servo(self):
Garry Wang8c8dc972020-06-09 13:41:51 -0700248 """ Initialize and setup servo for later use.
249 """
250 self.initilize_servo()
251 self.initialize_dut_for_servo()
252
Garry Wang8c8dc972020-06-09 13:41:51 -0700253 def initilize_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700254 """Establish a connection to the servod server on this host.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700255
256 Initializes `self._servo` and then verifies that all network
257 connections are working. This will create an ssh tunnel if
258 it's required.
Garry Wang8c8dc972020-06-09 13:41:51 -0700259 """
260 self._servo = servo.Servo(servo_host=self,
261 servo_serial=self.servo_serial)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700262
Garry Wang8c8dc972020-06-09 13:41:51 -0700263 def initialize_dut_for_servo(self):
264 """This method will do some setup for dut control, e.g. setup
265 main servo_v4 device, and also testing the connection between servo
266 and DUT. As a side effect of testing the connection, all signals on
267 the target servo are reset to default values, and the USB stick is
Richard Barnette9a26ad62016-06-10 12:03:08 -0700268 set to the neutral (off) position.
269 """
Garry Wang8c8dc972020-06-09 13:41:51 -0700270 if not self._servo:
271 raise hosts.AutoservVerifyError('Servo object needs to be'
272 ' initialized before initialize'
273 ' DUT.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700274 timeout, _ = retry.timeout(
Garry Wang8c8dc972020-06-09 13:41:51 -0700275 self._servo.initialize_dut,
276 timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700277 if timeout:
Garry Wang8c8dc972020-06-09 13:41:51 -0700278 raise hosts.AutoservVerifyError('Initialize dut for servo timed'
279 ' out.')
Richard Barnette9a26ad62016-06-10 12:03:08 -0700280
Richard Barnette9a26ad62016-06-10 12:03:08 -0700281 def disconnect_servo(self):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -0700282 """Disconnect our servo if it exists.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700283
284 If we've previously successfully connected to our servo,
285 disconnect any established ssh tunnel, and set `self._servo`
286 back to `None`.
287 """
288 if self._servo:
289 # N.B. This call is safe even without a tunnel:
290 # rpc_server_tracker.disconnect() silently ignores
291 # unknown ports.
292 self.rpc_server_tracker.disconnect(self.servo_port)
293 self._servo = None
Fang Deng5d518f42013-08-02 14:04:32 -0700294
Andrew McRaef0679932020-08-13 09:15:23 +1000295 def _maybe_create_servod_ssh_tunnel_proxy(self):
296 """Create a xmlrpc proxy for use with a ssh tunnel.
297 A lock is used to safely create a singleton proxy.
298 """
299 with self._tunnel_proxy_lock:
300 if self._tunnel_proxy is None:
301 self._tunnel_proxy = self.rpc_server_tracker.xmlrpc_connect(
302 None,
303 self.servo_port,
304 ready_test_name=self.SERVO_READY_METHOD,
305 timeout_seconds=60,
306 request_timeout_seconds=3600,
307 server_desc=str(self))
308
Andrew McRaef0679932020-08-13 09:15:23 +1000309 def get_servod_server_proxy(self):
310 """Return a proxy if it exists; otherwise, create a new one.
311 A proxy can either be a ssh tunnel based proxy, or a httplib
312 based proxy.
Fang Deng5d518f42013-08-02 14:04:32 -0700313
314 @returns: An xmlrpclib.ServerProxy that is connected to the servod
315 server on the host.
Fang Deng5d518f42013-08-02 14:04:32 -0700316 """
Garry Wang11b5e872020-03-11 15:14:08 -0700317 if (servo_constants.ENABLE_SSH_TUNNEL_FOR_SERVO
318 and not self.is_localhost()):
Andrew McRaef0679932020-08-13 09:15:23 +1000319 # Check for existing ssh tunnel proxy.
320 if self._tunnel_proxy is None:
321 self._maybe_create_servod_ssh_tunnel_proxy()
322 return self._tunnel_proxy
Richard Barnette9a26ad62016-06-10 12:03:08 -0700323 else:
Andrew McRaef0679932020-08-13 09:15:23 +1000324 # xmlrpc/httplib is not thread-safe, so each thread must have its
325 # own separate proxy connection.
326 if not hasattr(self._local, "_per_thread_proxy"):
327 remote = 'http://%s:%s' % (self.hostname, self.servo_port)
Derek Beckettf73baca2020-08-19 15:08:47 -0700328 self._local._per_thread_proxy = six.moves.xmlrpc_client.ServerProxy(remote)
Andrew McRaef0679932020-08-13 09:15:23 +1000329 return self._local._per_thread_proxy
Wai-Hong Tam3a8a2552019-11-19 14:28:04 +0800330
Richard Barnette1edbb162016-11-01 11:47:50 -0700331 def verify(self, silent=False):
332 """Update the servo host and verify it's in a good state.
333
334 @param silent If true, suppress logging in `status.log`.
335 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700336 message = 'Beginning verify for servo host %s port %s serial %s'
337 message %= (self.hostname, self.servo_port, self.servo_serial)
338 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700339 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700340 self._repair_strategy.verify(self, silent)
Garry Wang11b5e872020-03-11 15:14:08 -0700341 self._servo_state = servo_constants.SERVO_STATE_WORKING
342 self.record('INFO', None, None,
343 'ServoHost verify set servo_state as WORKING')
Otabek Kasimovda994012020-11-25 15:23:04 -0800344 if self.is_servo_topology_supported():
345 self._topology = servo_topology.ServoTopology(self)
Otabek Kasimov382c3bb2020-10-28 13:22:45 -0700346 self._topology.generate()
Garry Wang63b8c382020-03-11 22:28:40 -0700347 except Exception as e:
Otabek Kasimov120b6fa2020-07-03 00:15:27 -0700348 if not self.is_localhost():
Otabek Kasimovc6f30412020-06-30 20:08:12 -0700349 self._servo_state = self.determine_servo_state()
350 self.record('INFO', None, None,
351 'ServoHost verify set servo_state as %s'
352 % self._servo_state)
Garry Wang63b8c382020-03-11 22:28:40 -0700353 if self._is_critical_error(e):
354 raise
Fang Deng5d518f42013-08-02 14:04:32 -0700355
Garry Wang2b5eef92020-08-21 16:23:35 -0700356 def _get_default_usbkey_mount_path(self):
357 return '/media/servo_usb/%s' % self.servo_port
Fang Deng5d518f42013-08-02 14:04:32 -0700358
Garry Wang7b0e1b72020-03-25 19:08:59 -0700359 def get_image_name_from_usbkey(self, usbkey_dev):
360 """Mount usb drive and check ChromeOS image name on it if there is
361 one. This method assumes the image_usbkey_direction is already set
362 to servo side.
363
Garry Wang4b980202020-09-24 17:00:17 -0700364 @param usbkey_dev: usbkey dev path(e.g. /dev/sdb).
Garry Wang7b0e1b72020-03-25 19:08:59 -0700365
366 @returns: image_name on the usbkey, e.g. nami-release/R82.10138.0.0,
367 or empty string if no test image detected, or unexpected
368 error occurred.
Garry Wang7b0e1b72020-03-25 19:08:59 -0700369 """
Garry Wang70e5d062020-04-03 18:01:05 -0700370 logging.info('Checking ChromeOS image name on usbkey.')
Garry Wang2b5eef92020-08-21 16:23:35 -0700371 mount_dst = self._get_default_usbkey_mount_path()
Garry Wang7b0e1b72020-03-25 19:08:59 -0700372 # Unmount if there is an existing stale mount.
Garry Wang2b5eef92020-08-21 16:23:35 -0700373 self._unmount_drive(mount_dst)
374 # ChromeOS root fs is in /dev/sdx3
375 mount_src = usbkey_dev + '3'
Garry Wang7b0e1b72020-03-25 19:08:59 -0700376 try:
Garry Wang2b5eef92020-08-21 16:23:35 -0700377 if not self._mount_drive(mount_src, mount_dst):
378 logging.debug('Unexpected error occurred on mount usb drive.')
Garry Wang7b0e1b72020-03-25 19:08:59 -0700379 return ''
Garry Wang70e5d062020-04-03 18:01:05 -0700380
381 release_content = self.run(
Garry Wang2b5eef92020-08-21 16:23:35 -0700382 'cat %s/etc/lsb-release' % mount_dst,
Garry Wang70e5d062020-04-03 18:01:05 -0700383 ignore_status=True).stdout.strip()
384
385 if not re.search(r'RELEASE_TRACK=.*test', release_content):
386 logging.info('The image on usbkey is not a test image')
387 return ''
388
389 return lsbrelease_utils.get_chromeos_release_builder_path(
390 lsb_release_content=release_content)
Garry Wang7b0e1b72020-03-25 19:08:59 -0700391 finally:
Garry Wang70e5d062020-04-03 18:01:05 -0700392 logging.debug('Image check compeleted, unmounting the usb drive.')
Garry Wang2b5eef92020-08-21 16:23:35 -0700393 self._unmount_drive(mount_dst)
Garry Wang7b0e1b72020-03-25 19:08:59 -0700394
Garry Wang2b5eef92020-08-21 16:23:35 -0700395 def _extract_firmware_image_from_usbkey(self, fw_dst):
396 """Extract firmware images from the usbkey on servo, this method
397 assumes there is already a ChromeOS test image staged on servo.
398
Garry Wang4b980202020-09-24 17:00:17 -0700399 @param fw_dst: the path that we'll copy firmware images to.
Garry Wang2b5eef92020-08-21 16:23:35 -0700400
401 @returns: a json format string of firmware manifest data.
402 """
403 usbkey_dev = self._probe_and_validate_usb_dev()
404 if not usbkey_dev:
405 raise hosts.AutoservRepairError('Unexpected error occurred when'
406 ' probe usbkey dev path, please check logs for detail.')
407
408 mount_dst = self._get_default_usbkey_mount_path()
409 # Unmount if there is an existing stale mount.
410 self._unmount_drive(mount_dst)
411 # ChromeOS root fs is in /dev/sdx3
412 mount_src = usbkey_dev + '3'
413 try:
414 if not self._mount_drive(mount_src, mount_dst):
415 raise hosts.AutoservRepairError('Failed to extract firmware'
416 ' image; Unable to mount %s.' % usbkey_dev,
417 'unable to mount usbkey')
418 updater_bin = os.path.join(mount_dst,
419 'usr/sbin/chromeos-firmwareupdate')
420 self.run('%s --unpack %s' % (updater_bin, fw_dst))
421 return self.run('%s --manifest' % updater_bin).stdout
422 finally:
423 self._unmount_drive(mount_dst)
424
425 def prepare_repair_firmware_image(self, fw_dst=None):
426 """Prepare firmware image on the servohost for auto repair process
427 to consume.
428
Garry Wang4b980202020-09-24 17:00:17 -0700429 @param fw_dst: the path that we want to store firmware image on
430 the servohost.
Garry Wang2b5eef92020-08-21 16:23:35 -0700431
432 @returns: A tuple that containes ec firmware image path and bios
433 firmware image path on the servohost, or None if type of
434 image is not available based on manifest and dut's model.
435 """
436 model = self.servo_model or self._dut_host_info.model
437 if not model:
438 raise hosts.AutoservRepairError(
439 'Could not determine DUT\'s model.',
440 'model infomation unknown')
441
442 if not fw_dst:
443 fw_dst = '/tmp/firmware_image/%s' % self.servo_port
444 # Cleanup and re-create dst path to have a fresh start.
445 self.run('rm -rf %s' % fw_dst)
446 self.run('mkdir -p %s' % fw_dst)
447
448 manifest = json.loads(self._extract_firmware_image_from_usbkey(fw_dst))
Garry Wang50b56c12020-09-24 17:26:52 -0700449 # For models that have packed $MODEL_signed variant, we want use the
450 # 'signed' variant once we get DVT devices, so try to read manifest
451 # from $MODEL_signed first.
452 build = manifest.get('%s_signed' % model) or manifest.get(model)
453 if not build:
Garry Wang2b5eef92020-08-21 16:23:35 -0700454 raise hosts.AutoservRepairError('Could not find firmware manifest'
455 ' for model:%s' % model, 'model manifest not found')
456 try:
Garry Wang50b56c12020-09-24 17:26:52 -0700457 ec_image = os.path.join(fw_dst, build['ec']['image'])
Garry Wang2b5eef92020-08-21 16:23:35 -0700458 except KeyError:
459 ec_image = None
460 try:
Garry Wang50b56c12020-09-24 17:26:52 -0700461 bios_image = os.path.join(fw_dst, build['host']['image'])
Garry Wang2b5eef92020-08-21 16:23:35 -0700462 except KeyError:
463 bios_image = None
464 if not ec_image and not bios_image:
465 raise hosts.AutoservRepairError('Could not find any firmware image'
466 ' for model:%s' % model, 'cannot find firmware image')
467 return ec_image, bios_image
Garry Wang7b0e1b72020-03-25 19:08:59 -0700468
Garry Wang4b980202020-09-24 17:00:17 -0700469 def flash_ap_firmware_via_servo(self, image):
470 """Flash AP firmware by use a provided image.
471
472 This is will be a short term enhanment for infra repair use, it use
473 'futility update' which will automatically determine various parameters
474 needed for flashrom, and will preserve the GBB, VPD, and HWID for
475 AP firmware update.
476 @TODO(xianuowang@) Remove this method once b/148403277 implemented.
477
478 @param image: the firmware image path on servohost.
479 """
480 cmd = 'futility update -i %s --servo_port=%s'
481 self.run(cmd % (image, self.servo_port), timeout=900)
482
Garry Wang70e5d062020-04-03 18:01:05 -0700483 def _probe_and_validate_usb_dev(self):
484 """This method probe the usb dev path by talking to servo, and then
485 validate the dev path is valid block device to servohost.
486 Possible output:
487 1. Encounter error during probe usb dev, returns empty string.
488 2. probe usb dev completed without error but cannot find usb dev,
489 raise AutoservRepairError.
490 3. probe usb dev find a usb dev path, but failed validation in this
491 method, raise AutoservRepairError.
Garry Wang7b0e1b72020-03-25 19:08:59 -0700492
Garry Wang70e5d062020-04-03 18:01:05 -0700493 @returns: A string of usb dev path(e.g. '/dev/sdb'), or empty string
494 if unexpected error occurred during probe.
495 @raises: AutoservRepairError if servo couldn't probe the usb dev path
496 (servo.probe_host_usb_dev() returns empty string), or the dev path is
497 not valid block device to servohost.
Garry Wang7b0e1b72020-03-25 19:08:59 -0700498 """
499 logging.info('Validating image usbkey on servo.')
Garry Wang7b0e1b72020-03-25 19:08:59 -0700500 try:
Garry Wang70e5d062020-04-03 18:01:05 -0700501 usb_dev = self._servo.probe_host_usb_dev()
Garry Wang7b0e1b72020-03-25 19:08:59 -0700502 except Exception as e:
503 # We don't want any unexpected or transient servo communicating
504 # failure block usb repair, so capture all errors here.
505 logging.error(e, exc_info=True)
506 logging.error('Unexpected error occurred on get usbkey dev path,'
507 ' skipping usbkey validation.')
508 return ''
509
Garry Wang70e5d062020-04-03 18:01:05 -0700510 if usb_dev:
511 # probe_host_usb_dev() sometimes return stale record,
512 # so we need to make sure the path exists in fdisk.
Otabek Kasimov77bff672020-10-08 15:52:03 -0700513 validate_cmd = 'fdisk -l %s' % usb_dev
Garry Wang11441182020-06-16 18:34:14 -0700514 try:
Otabek Kasimov77bff672020-10-08 15:52:03 -0700515 resp = self.run(validate_cmd, ignore_status=True, timeout=30)
Garry Wang11441182020-06-16 18:34:14 -0700516 if resp.exit_status == 0:
517 return usb_dev
Garry Wang11441182020-06-16 18:34:14 -0700518 logging.error('%s is reported from "image_usbkey_dev" control'
519 ' but not detected by fdisk!', usb_dev)
520 except error.AutoservRunError as e:
521 if 'Timeout encountered' in str(e):
522 logging.warning('Timeout encountered during fdisk run,'
523 ' skipping usbkey validation.')
524 return ''
525 raise
Garry Wang70e5d062020-04-03 18:01:05 -0700526
527 raise hosts.AutoservRepairError(
528 'No usbkey detected on servo, the usbkey may be either missing'
529 ' or broken. Please replace usbkey on the servo and retry.',
530 'missing usbkey')
531
Otabek Kasimov4ea636e2020-04-14 23:35:06 -0700532 def is_ec_supported(self):
Garry Wang9b8f2342020-04-17 16:34:09 -0700533 """Check if ec is supported on the servo_board"""
Otabek Kasimov4ea636e2020-04-14 23:35:06 -0700534 if self.servo_board:
535 try:
536 frm_config = config.Config(self.servo_board, self.servo_model)
537 return frm_config.chrome_ec
538 except Exception as e:
539 logging.error('Unexpected error when read from firmware'
540 ' configs; %s', str(e))
541 return False
542
Garry Wang70e5d062020-04-03 18:01:05 -0700543 def validate_image_usbkey(self):
544 """This method first validate if there is a recover usbkey on servo
545 that accessible to servohost, and second check if a ChromeOS image is
546 already on the usb drive and return the image_name so we can avoid
547 unnecessary download and flash to the recover usbkey on servo.
548
549 Please note that, there is special error handling logic here:
550 1. If unexpected error happens, we return empty string. So repair
551 actions will not get blocked.
552 2. If no working usbkey present on servo, but no errors, we'll raise
553 AutoservRepairError here.
554
555 @returns: image_name on the usbkey, e.g. nami-release/R82.10138.0.0,
556 or empty string if no test image detected, or unexpected
557 error occurred.
558 @raises: AutoservRepairError if the usbkey is not detected on servo.
559 """
560 usb_dev = self._probe_and_validate_usb_dev()
561 if usb_dev:
562 return self.get_image_name_from_usbkey(usb_dev)
563 else:
564 return ''
Garry Wang7b0e1b72020-03-25 19:08:59 -0700565
Richard Barnette1edbb162016-11-01 11:47:50 -0700566 def repair(self, silent=False):
567 """Attempt to repair servo host.
568
569 @param silent If true, suppress logging in `status.log`.
570 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700571 message = 'Beginning repair for servo host %s port %s serial %s'
572 message %= (self.hostname, self.servo_port, self.servo_serial)
573 self.record('INFO', None, None, message)
Richard Barnette9a26ad62016-06-10 12:03:08 -0700574 try:
Richard Barnette1edbb162016-11-01 11:47:50 -0700575 self._repair_strategy.repair(self, silent)
Garry Wang11b5e872020-03-11 15:14:08 -0700576 self._servo_state = servo_constants.SERVO_STATE_WORKING
577 self.record('INFO', None, None,
578 'ServoHost repair set servo_state as WORKING')
Garry Wang464ff1e2019-07-18 17:20:34 -0700579 # If target is a labstation then try to withdraw any existing
580 # reboot request created by this servo because it passed repair.
581 if self.is_labstation():
582 self.withdraw_reboot_request()
Otabek Kasimovda994012020-11-25 15:23:04 -0800583 if self.is_servo_topology_supported():
584 self._topology = servo_topology.ServoTopology(self)
Otabek Kasimov382c3bb2020-10-28 13:22:45 -0700585 self._topology.generate()
Garry Wang63b8c382020-03-11 22:28:40 -0700586 except Exception as e:
Otabek Kasimov120b6fa2020-07-03 00:15:27 -0700587 if not self.is_localhost():
Otabek Kasimovc6f30412020-06-30 20:08:12 -0700588 self._servo_state = self.determine_servo_state()
589 self.record('INFO', None, None,
590 'ServoHost repair set servo_state as %s'
591 % self._servo_state)
Garry Wang63b8c382020-03-11 22:28:40 -0700592 if self._is_critical_error(e):
593 self.disconnect_servo()
594 self.stop_servod()
595 raise
596
Garry Wang63b8c382020-03-11 22:28:40 -0700597 def _is_critical_error(self, error):
598 if (isinstance(error, hosts.AutoservVerifyDependencyError)
599 and not error.is_critical()):
600 logging.warning('Non-critical verify failure(s) detected during'
601 ' verify/repair servo, servo connection will'
Evan Benn2c41c262020-10-28 11:34:27 +1100602 ' still be up but may not be fully functional.'
603 ' Some repair actions and servo dependent'
Garry Wang63b8c382020-03-11 22:28:40 -0700604 ' tests may not run.')
605 return False
Evan Benn2c41c262020-10-28 11:34:27 +1100606 logging.info(
607 'Critical verify failure(s) detected during repair/verify '
608 'servo. Disconnecting servo and running `stop servod`, all'
609 ' repair actions and tests that depends on servo will not '
610 'run.')
Garry Wang63b8c382020-03-11 22:28:40 -0700611 return True
Fang Deng5d518f42013-08-02 14:04:32 -0700612
Dan Shi4d478522014-02-14 13:46:32 -0800613 def get_servo(self):
614 """Get the cached servo.Servo object.
Fang Deng5d518f42013-08-02 14:04:32 -0700615
Dan Shi4d478522014-02-14 13:46:32 -0800616 @return: a servo.Servo object.
Dana Goyette353d1d92019-06-27 10:43:59 -0700617 @rtype: autotest_lib.server.cros.servo.servo.Servo
Fang Deng5d518f42013-08-02 14:04:32 -0700618 """
Dan Shi4d478522014-02-14 13:46:32 -0800619 return self._servo
620
Garry Wang79e9af62019-06-12 15:19:19 -0700621 def request_reboot(self):
622 """Request servohost to be rebooted when it's safe to by touch a file.
623 """
624 logging.debug('Request to reboot servohost %s has been created by '
Garry Wang464ff1e2019-07-18 17:20:34 -0700625 'servo with port # %s', self.hostname, self.servo_port)
Garry Wang79e9af62019-06-12 15:19:19 -0700626 self.run('touch %s' % self._reboot_file, ignore_status=True)
627
Garry Wang464ff1e2019-07-18 17:20:34 -0700628 def withdraw_reboot_request(self):
629 """Withdraw a servohost reboot request if exists by remove the flag
630 file.
631 """
632 logging.debug('Withdrawing request to reboot servohost %s that created'
633 ' by servo with port # %s if exists.',
634 self.hostname, self.servo_port)
635 self.run('rm -f %s' % self._reboot_file, ignore_status=True)
636
Garry Wangc1288cf2019-12-17 14:58:00 -0800637 def start_servod(self, quick_startup=False):
638 """Start the servod process on servohost.
639 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800640 # Skip if running on the localhost.(crbug.com/1038168)
641 if self.is_localhost():
642 logging.debug("Servohost is a localhost, skipping start servod.")
643 return
644
645 cmd = 'start servod'
Garry Wangc1288cf2019-12-17 14:58:00 -0800646 if self.servo_board:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800647 cmd += ' BOARD=%s' % self.servo_board
Garry Wangc1288cf2019-12-17 14:58:00 -0800648 if self.servo_model:
649 cmd += ' MODEL=%s' % self.servo_model
Garry Wangc1288cf2019-12-17 14:58:00 -0800650 else:
Garry Wang2ac15ee2019-12-30 19:03:02 -0800651 logging.warning('Board for DUT is unknown; starting servod'
652 ' assuming a pre-configured board.')
653
654 cmd += ' PORT=%d' % self.servo_port
655 if self.servo_serial:
656 cmd += ' SERIAL=%s' % self.servo_serial
Garry Wangd7367482020-02-27 13:52:40 -0800657
Garry Wangcb06f3b2020-10-08 20:56:21 -0700658 # Start servod with dual_v4 based on servo_setup.
Otabek Kasimov382c3bb2020-10-28 13:22:45 -0700659 if self.is_dual_setup():
Garry Wangcb06f3b2020-10-08 20:56:21 -0700660 cmd += ' DUAL_V4=1'
Garry Wangd7367482020-02-27 13:52:40 -0800661
Garry Wangcb06f3b2020-10-08 20:56:21 -0700662 # Start servod with CONFIG=cr50.xml which required for some pools.
663 if self._require_cr50_servod_config():
664 cmd += ' CONFIG=cr50.xml'
Garry Wangb5cee3e2020-09-16 14:58:13 -0700665
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800666 if self.servo_recovery == True:
667 cmd += ' REC_MODE=1'
668
Garry Wang6a680062020-11-03 13:40:29 -0800669 # Adding customized args if any.
670 if self.additional_servod_args:
671 cmd += ' ' + self.additional_servod_args
672
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800673 # Remove the symbolic links from the logs. This helps ensure that
674 # a failed servod instantiation does not cause us to grab old logs
675 # by mistake.
676 self.remove_latest_log_symlinks()
Garry Wangcdd27b22020-01-13 14:59:11 -0800677 self.run(cmd, timeout=60)
Garry Wangc1288cf2019-12-17 14:58:00 -0800678
679 # There's a lag between when `start servod` completes and when
680 # the _ServodConnectionVerifier trigger can actually succeed.
681 # The call to time.sleep() below gives time to make sure that
682 # the trigger won't fail after we return.
683
684 # Normally servod on servo_v3 and labstation take ~10 seconds to ready,
685 # But in the rare case all servo on a labstation are in heavy use they
686 # may take ~30 seconds. So the timeout value will double these value,
687 # and we'll try quick start up when first time initialize servohost,
688 # and use standard start up timeout in repair.
689 if quick_startup:
Garry Wang11b5e872020-03-11 15:14:08 -0700690 timeout = servo_constants.SERVOD_QUICK_STARTUP_TIMEOUT
Garry Wangc1288cf2019-12-17 14:58:00 -0800691 else:
Garry Wang11b5e872020-03-11 15:14:08 -0700692 timeout = servo_constants.SERVOD_STARTUP_TIMEOUT
Garry Wangc1288cf2019-12-17 14:58:00 -0800693 logging.debug('Wait %s seconds for servod process fully up.', timeout)
694 time.sleep(timeout)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700695 # Cache the initial instance timestamp to check against servod restarts
696 self._initial_instance_ts = self.get_instance_logs_ts()
Garry Wangc1288cf2019-12-17 14:58:00 -0800697
Garry Wangc1288cf2019-12-17 14:58:00 -0800698 def stop_servod(self):
699 """Stop the servod process on servohost.
700 """
Garry Wang2ac15ee2019-12-30 19:03:02 -0800701 # Skip if running on the localhost.(crbug.com/1038168)
702 if self.is_localhost():
703 logging.debug("Servohost is a localhost, skipping stop servod.")
704 return
705
Garry Wangc1288cf2019-12-17 14:58:00 -0800706 logging.debug('Stopping servod on port %s', self.servo_port)
Garry Wangcdd27b22020-01-13 14:59:11 -0800707 self.run('stop servod PORT=%d' % self.servo_port,
708 timeout=60, ignore_status=True)
Garry Wangc1288cf2019-12-17 14:58:00 -0800709 logging.debug('Wait %s seconds for servod process fully teardown.',
Garry Wang11b5e872020-03-11 15:14:08 -0700710 servo_constants.SERVOD_TEARDOWN_TIMEOUT)
711 time.sleep(servo_constants.SERVOD_TEARDOWN_TIMEOUT)
Garry Wangc1288cf2019-12-17 14:58:00 -0800712
Garry Wangc1288cf2019-12-17 14:58:00 -0800713 def restart_servod(self, quick_startup=False):
714 """Restart the servod process on servohost.
715 """
716 self.stop_servod()
717 self.start_servod(quick_startup)
718
Garry Wangffbd2162020-04-17 16:13:48 -0700719 def _process_servodtool_error(self, response):
720 """Helper function to handle non-zero servodtool response.
721 """
722 if re.search(servo_constants.ERROR_MESSAGE_USB_HUB_NOT_COMPATIBLE,
Garry Wangad245002020-05-15 15:20:23 -0700723 response.stdout):
Garry Wangffbd2162020-04-17 16:13:48 -0700724 logging.error('The servo is not plugged on a usb hub that supports'
725 ' power-cycle!')
Garry Wang000c6c02020-05-11 21:27:23 -0700726 # change the flag so we can update this label in later process.
727 self.smart_usbhub = False
Garry Wangffbd2162020-04-17 16:13:48 -0700728 return
729
730 if re.search(servo_constants.ERROR_MESSAGE_DEVICE_NOT_FOUND %
731 self.servo_serial, response.stdout):
732 logging.error('No servo with serial %s found!', self.servo_serial)
733 return
734
735 logging.error('Unexpected error occurred from usbhub control, please'
736 ' file a bug and inform chrome-fleet-software@ team!')
737
Otabek Kasimov86062d02020-11-17 13:30:22 -0800738 def get_main_servo_usb_path(self):
739 """Helper function to collect current usb-path to main servo.
740
741 The usb-path is path to the folder where usb-device was enumerated.
742 If fail then will return an empty string ('').
743
744 @returns: string, usb-path to the main servo device.
745 e.g.: '/sys/bus/usb/devices/1-6.1.3.1'
Garry Wangffbd2162020-04-17 16:13:48 -0700746 """
Otabek Kasimov09192682020-06-01 18:17:44 -0700747 # TODO remove try-except when fix crbug.com/1087964
748 try:
749 cmd = 'servodtool device -s %s usb-path' % self.servo_serial
750 resp = self.run(cmd, ignore_status=True, timeout=30)
751 except Exception as e:
752 # Here we catch only timeout errors.
753 # Other errors is filtered by ignore_status=True
754 logging.debug('Attempt to get servo usb-path failed due to '
755 'timeout; %s', e)
756 return ''
Garry Wangffbd2162020-04-17 16:13:48 -0700757 if resp.exit_status != 0:
758 self._process_servodtool_error(resp)
759 return ''
760 usb_path = resp.stdout.strip()
761 logging.info('Usb path of servo %s is %s', self.servo_serial, usb_path)
Otabek Kasimov86062d02020-11-17 13:30:22 -0800762 return usb_path
Garry Wangffbd2162020-04-17 16:13:48 -0700763
Otabek Kasimov86062d02020-11-17 13:30:22 -0800764 def _get_servo_usb_devnum(self):
765 """Helper function to collect current usb devnum of servo."""
766 usb_path = self.get_main_servo_usb_path()
767 if not usb_path:
768 return ''
769 resp = self.run('cat %s/devnum' % usb_path, ignore_status=True)
Garry Wangffbd2162020-04-17 16:13:48 -0700770 if resp.exit_status != 0:
771 self._process_servodtool_error(resp)
772 return ''
773 return resp.stdout.strip()
774
Garry Wang358aad42020-08-02 20:56:04 -0700775 def reboot_servo_v3_on_need(self):
776 """Check and reboot servo_v3 based on below conditions.
777 1. If there is an update pending on reboot.
778 2. Servo_v3 has been up for more than 96 hours.
779 """
780 if self.get_board() != 'beaglebone_servo':
781 logging.info('Servo reboot is only applicable for servo V3.')
Otabek Kasimove6df8102020-07-21 20:15:25 -0700782 return
783
Garry Wang358aad42020-08-02 20:56:04 -0700784 update_pending_reboot = (self._check_update_status() ==
785 self.UPDATE_STATE.PENDING_REBOOT)
786 uptime_hours = float(self.check_uptime())/3600
787 logging.info('Uptime of servo_v3: %s hour(s)', uptime_hours)
788 long_up_time = uptime_hours > 96
789
790 # Skip reboot if neither condition are met.
791 if not (update_pending_reboot or long_up_time):
Otabek Kasimove6df8102020-07-21 20:15:25 -0700792 return
793
Garry Wang358aad42020-08-02 20:56:04 -0700794 if update_pending_reboot:
795 message = 'Starting reboot servo_v3 because an update is pending.'
796 reboot_method = self._post_update_reboot
797 elif long_up_time:
798 message = 'Starting reboot servo_v3 because uptime > 96 hours.'
799 reboot_method = self._servo_host_reboot
800 self.record('INFO', None, None, message)
801 logging.info(message)
Otabek Kasimove6df8102020-07-21 20:15:25 -0700802 try:
Garry Wang358aad42020-08-02 20:56:04 -0700803 reboot_method()
Otabek Kasimove6df8102020-07-21 20:15:25 -0700804 message = 'Servo_v3 reboot completed successfully.'
805 except Exception as e:
806 logging.debug("Fail to reboot servo_v3; %s", e)
807 message = ('Servo_v3 reboot failed, please check debug log '
808 'for details.')
809 logging.info(message)
810 self.record('INFO', None, None, message)
Garry Wangffbd2162020-04-17 16:13:48 -0700811
812 def _reset_servo(self):
813 logging.info('Resetting servo through smart usbhub.')
Otabek Kasimov09192682020-06-01 18:17:44 -0700814 # TODO remove try-except when fix crbug.com/1087964
815 try:
816 resp = self.run('servodtool device -s %s power-cycle' %
817 self.servo_serial, ignore_status=True,
818 timeout=30)
819 if resp.exit_status != 0:
820 self._process_servodtool_error(resp)
821 return False
822 except Exception as e:
823 # Here we catch only timeout errors.
824 # Other errors is filtered by ignore_status=True
825 logging.debug('Attempt to reset servo failed due to timeout;'
826 ' %s', e)
Garry Wangffbd2162020-04-17 16:13:48 -0700827 return False
828
829 logging.debug('Wait %s seconds for servo to come back from reset.',
830 servo_constants.SERVO_RESET_TIMEOUT_SECONDS)
831 time.sleep(servo_constants.SERVO_RESET_TIMEOUT_SECONDS)
Garry Wang000c6c02020-05-11 21:27:23 -0700832 # change the flag so we can update this label in later process.
833 self.smart_usbhub = True
Garry Wangffbd2162020-04-17 16:13:48 -0700834 return True
835
Garry Wangffbd2162020-04-17 16:13:48 -0700836 def reset_servo(self):
837 """Reset(power-cycle) the servo via smart usbhub.
838 """
839 if not self.is_labstation():
840 logging.info('Servo reset is not applicable to servo_v3.')
841 return
842
843 pre_reset_devnum = self._get_servo_usb_devnum()
844 logging.info('Servo usb devnum before reset: %s', pre_reset_devnum)
845 result = self._reset_servo()
846 if not result:
Garry Wangfd5c8b62020-06-08 15:36:54 -0700847 message = ('Failed to reset servo with serial: %s. (Please ignore'
848 ' this error if the DUT is not connected to a smart'
849 ' usbhub).' % self.servo_serial)
Garry Wangffbd2162020-04-17 16:13:48 -0700850 logging.warning(message)
851 self.record('INFO', None, None, message)
852 return
853
854 post_reset_devnum = self._get_servo_usb_devnum()
855 logging.info('Servo usb devnum after reset: %s', post_reset_devnum)
856 if not (pre_reset_devnum and post_reset_devnum):
857 message = ('Servo reset completed but unable to verify'
858 ' devnum change!')
859 elif pre_reset_devnum != post_reset_devnum:
860 message = ('Reset servo with serial %s completed successfully!'
861 % self.servo_serial)
862 else:
863 message = 'Servo reset completed but devnum is still not changed!'
864 logging.info(message)
865 self.record('INFO', None, None, message)
866
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800867 def _extract_compressed_logs(self, logdir, relevant_files):
868 """Decompress servod logs in |logdir|.
869
870 @param logdir: directory containing compressed servod logs.
871 @param relevant_files: list of files in |logdir| to consider.
872
873 @returns: tuple, (tarfiles, files) where
874 tarfiles: list of the compressed filenames that have been
875 extracted and deleted
876 files: list of the uncompressed files that were generated
877 """
878 # For all tar-files, first extract them to the directory, and
879 # then let the common flow handle them.
880 tarfiles = [cf for cf in relevant_files if
881 cf.endswith(self.COMPRESSION_SUFFIX)]
882 files = []
883 for f in tarfiles:
884 norm_name = os.path.basename(f)[:-len(self.COMPRESSION_SUFFIX)]
885 with tarfile.open(f) as tf:
886 # Each tarfile has only one member, as
887 # that's the compressed log.
888 member = tf.members[0]
889 # Manipulate so that it only extracts the basename, and not
890 # the directories etc.
891 member.name = norm_name
892 files.append(os.path.join(logdir, member.name))
893 tf.extract(member, logdir)
894 # File has been extracted: remove the compressed file.
895 os.remove(f)
896 return tarfiles, files
897
898 def _extract_mcu_logs(self, log_subdir):
899 """Extract MCU (EC, Cr50, etc) console output from servod debug logs.
900
901 Using the MCU_EXTRACTOR regex (above) extract and split out MCU console
902 lines from the logs to generate invidiual console logs e.g. after
903 this method, you can find an ec.txt and servo_v4.txt in |log_dir| if
904 those MCUs had any console input/output.
905
906 @param log_subdir: directory with log.DEBUG.txt main servod debug logs.
907 """
908 # Extract the MCU for each one. The MCU logs are only in the .DEBUG
909 # files
910 mcu_lines_file = os.path.join(log_subdir, 'log.DEBUG.txt')
911 if not os.path.exists(mcu_lines_file):
912 logging.info('No DEBUG logs found to extract MCU logs from.')
913 return
914 mcu_files = {}
915 mcu_file_template = '%s.txt'
916 with open(mcu_lines_file, 'r') as f:
917 for line in f:
918 match = self.MCU_EXTRACTOR.match(line)
919 if match:
920 mcu = match.group(self.MCU_GROUP).lower()
921 line = match.group(self.LINE_GROUP)
922 if mcu not in mcu_files:
923 mcu_file = os.path.join(log_subdir,
924 mcu_file_template % mcu)
925 mcu_files[mcu] = open(mcu_file, 'a')
926 fd = mcu_files[mcu]
927 fd.write(line + '\n')
928 for f in mcu_files:
929 mcu_files[f].close()
930
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800931 def remove_latest_log_symlinks(self):
932 """Remove the conveninence symlinks 'latest' servod logs."""
933 symlink_wildcard = '%s/latest*' % self.remote_log_dir
934 cmd = 'rm ' + symlink_wildcard
935 self.run(cmd, stderr_tee=None, ignore_status=True)
936
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700937 def probe_servod_restart(self, instance_ts, outdir):
938 """Grab servod logs from previous instances if part of this session.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800939
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700940 If since the last time this host called start_servod() servod crashed
941 and restarted, this helper finds those logs as well, and stores them
942 with the |OLD_LOG_SUFFIX| to investigate if necessary.
Prasad Vuppalapu5bd9da12020-03-31 01:46:47 +0000943
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700944 It also issues a panicinfo command to servo devices after the restart
945 to try and collect reboot information for debugging.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800946
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700947 @param instance_ts: the log timestamp that the current instance uses
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -0800948 @param outdir: directory to create a subdirectory into to place the
949 servod logs into.
950 """
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -0700951 if self._initial_instance_ts is None:
952 logging.info('No log timestamp grabbed successfully on servod '
953 'startup. Cannot check device restarts. Ignoring.')
954 return
955 if instance_ts == self._initial_instance_ts:
956 logging.debug('Servod appears to have run without restarting')
957 return
958 # Servod seems to have restarted (at least once). |_initial_instance_ts|
959 # is the first timestamp, and instance_ts is the current timestamp. Find
960 # all timestamps in between them, and grab the logs for each.
961 tss = self._find_instance_timestamps_between(self._initial_instance_ts,
962 instance_ts)
963 logging.info('Servod has restarted %d times between the start and the '
964 'end of this servo_host.', len(tss))
965 logging.info('This might be an issue. Will extract all logs from each '
966 'instance.')
967 logging.info('Logs that are not the currently running (about to turn '
968 'down) instance are maked with a .%s in their folder.',
969 self.OLD_LOG_SUFFIX)
970 for ts in tss:
971 self.get_instance_logs(ts, outdir, old=True)
972 # Lastly, servod has restarted due to a potential issue. Try to get
973 # panic information from servo micro and servo v4 for the current logs.
974 # This can only happen if the |_servo| attribute is initialized.
975 if self._servo:
976 for mcu in ['servo_micro', 'servo_v4']:
977 ctrl = '%s_uart_cmd' % mcu
978 if self._servo.has_control(ctrl):
979 logging.info('Trying to retrieve %r panicinfo into logs',
980 mcu)
981 try:
982 self._servo.set_nocheck(ctrl, 'panicinfo')
983 except error.TestFail as e:
984 logging.error('Failed to generate panicinfo for %r '
985 'logs. %s', mcu, str(e))
986
987 def _find_instance_timestamps_between(self, start_ts, end_ts):
988 """Find all log timestamps between [start_ts, end_ts).
989
990 @param start_ts: str, earliest log timestamp of interest
991 @param end_ts: str, latest log timestamp of interest
992
993 @returns: list, all timestamps between start_ts and end_ts, end_ts
994 exclusive, on the servo_host. An empty list on errors
995 """
996 # Simply get all timestamp, and then sort and remove
997 cmd = 'ls %s' % self.remote_log_dir
998 res = self.run(cmd, stderr_tee=None, ignore_status=True)
999 if res.exit_status != 0:
1000 # Here we failed to find anything.
1001 logging.info('Failed to find remote servod logs. Ignoring.')
1002 return []
1003 logfiles = res.stdout.strip().split()
1004 timestamps = set()
1005 for logfile in logfiles:
1006 ts_match = self.TS_EXTRACTOR.match(logfile)
1007 if not ts_match:
1008 # Simply ignore files that fail the check. It might be the
1009 # 'latest' symlinks or random files.
1010 continue
1011 timestamps.add(ts_match.group(self.TS_GROUP))
1012 # At this point we have all unique timestamps.
1013 timestamps = sorted(timestamps)
1014 for ts in [start_ts, end_ts]:
1015 if ts not in timestamps:
1016 logging.error('Timestamp %r not in servod logs. Cannot query '
1017 'for timestamps in between %r and %r', ts,
1018 start_ts, end_ts)
1019 return []
1020 return timestamps[timestamps.index(start_ts):timestamps.index(end_ts)]
1021
1022 def get_instance_logs_ts(self):
1023 """Retrieve the currently running servod instance's log timestamp
1024
1025 @returns: str, timestamp for current instance, or None on failure
1026 """
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001027 # First, extract the timestamp. This cmd gives the real filename of
1028 # the latest aka current log file.
1029 cmd = ('if [ -f %(dir)s/latest.DEBUG ];'
1030 'then realpath %(dir)s/latest.DEBUG;'
1031 'elif [ -f %(dir)s/latest ];'
1032 'then realpath %(dir)s/latest;'
1033 'else exit %(code)d;'
1034 'fi' % {'dir': self.remote_log_dir,
1035 'code': self.NO_SYMLINKS_CODE})
1036 res = self.run(cmd, stderr_tee=None, ignore_status=True)
1037 if res.exit_status != 0:
1038 if res.exit_status == self.NO_SYMLINKS_CODE:
1039 logging.warning('servod log latest symlinks not found. '
1040 'This is likely due to an error starting up '
1041 'servod. Ignoring..')
1042 else:
1043 logging.warning('Failed to find servod logs on servo host.')
1044 logging.warning(res.stderr.strip())
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001045 return None
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001046 fname = os.path.basename(res.stdout.strip())
1047 # From the fname, ought to extract the timestamp using the TS_EXTRACTOR
Ruben Rodriguez Buchillone9aa2b02020-03-04 12:14:28 -08001048 ts_match = self.TS_EXTRACTOR.match(fname)
1049 if not ts_match:
1050 logging.warning('Failed to extract timestamp from servod log file '
1051 '%r. Skipping. The servo host is using outdated '
1052 'servod logging and needs to be updated.', fname)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001053 return None
1054 return ts_match.group(self.TS_GROUP)
1055
1056 def get_instance_logs(self, instance_ts, outdir, old=False):
1057 """Collect all logs with |instance_ts| and dump into a dir in |outdir|
1058
1059 This method first collects all logs on the servo_host side pertaining
1060 to this servod instance (port, instatiation). It glues them together
1061 into combined log.[level].txt files and extracts all available MCU
1062 console I/O from the logs into individual files e.g. servo_v4.txt
1063
1064 All the output can be found in a directory inside |outdir| that
1065 this generates based on |LOG_DIR|, the servod port, and the instance
1066 timestamp on the servo_host side.
1067
1068 @param instance_ts: log timestamp to grab logfiles for
1069 @param outdir: directory to create a subdirectory into to place the
1070 servod logs into.
1071 @param old: bool, whether to append |OLD_LOG_SUFFIX| to output dir
1072 """
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001073 # Create the local results log dir.
1074 log_dir = os.path.join(outdir, '%s_%s.%s' % (self.LOG_DIR,
1075 str(self.servo_port),
1076 instance_ts))
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001077 if old:
Garry Wang22f2e842020-09-09 20:19:19 -07001078 log_dir = '%s.%s' % (log_dir, self.OLD_LOG_SUFFIX)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001079 logging.info('Saving servod logs to %r.', log_dir)
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001080 os.mkdir(log_dir)
1081 # Now, get all files with that timestamp.
1082 cmd = 'find %s -maxdepth 1 -name "log.%s*"' % (self.remote_log_dir,
1083 instance_ts)
1084 res = self.run(cmd, stderr_tee=None, ignore_status=True)
1085 files = res.stdout.strip().split()
1086 try:
1087 self.get_file(files, log_dir, try_rsync=False)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001088 if not os.listdir(log_dir):
1089 logging.info('No servod logs retrieved. Ignoring, and removing '
1090 '%r again.', log_dir)
1091 os.rmdir(log_dir)
1092 return
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001093 except error.AutoservRunError as e:
1094 result = e.result_obj
1095 if result.exit_status != 0:
1096 stderr = result.stderr.strip()
1097 logging.warning("Couldn't retrieve servod logs. Ignoring: %s",
1098 stderr or '\n%s' % result)
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001099 # Remove the log_dir as nothing was added to it.
1100 os.rmdir(log_dir)
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001101 return
1102 local_files = [os.path.join(log_dir, f) for f in os.listdir(log_dir)]
1103 # TODO(crrev.com/c/1793030): remove no-level case once CL is pushed
1104 for level_name in ('DEBUG', 'INFO', 'WARNING', ''):
1105 # Create the joint files for each loglevel. i.e log.DEBUG
1106 joint_file = self.JOINT_LOG_PREFIX
1107 if level_name:
1108 joint_file = '%s.%s' % (self.JOINT_LOG_PREFIX, level_name)
1109 # This helps with some online tools to avoid complaints about an
1110 # unknown filetype.
1111 joint_file = joint_file + '.txt'
1112 joint_path = os.path.join(log_dir, joint_file)
1113 files = [f for f in local_files if level_name in f]
1114 if not files:
1115 # TODO(crrev.com/c/1793030): remove no-level case once CL
1116 # is pushed
1117 continue
1118 # Extract compressed logs if any.
1119 compressed, extracted = self._extract_compressed_logs(log_dir,
1120 files)
1121 files = list(set(files) - set(compressed))
1122 files.extend(extracted)
1123 # Need to sort. As they all share the same timestamp, and
1124 # loglevel, the index itself is sufficient. The highest index
1125 # is the oldest file, therefore we need a descending sort.
1126 def sortkey(f, level=level_name):
1127 """Custom sortkey to sort based on rotation number int."""
1128 if f.endswith(level_name): return 0
1129 return int(f.split('.')[-1])
1130
1131 files.sort(reverse=True, key=sortkey)
1132 # Just rename the first file rather than building from scratch.
1133 os.rename(files[0], joint_path)
1134 with open(joint_path, 'a') as joint_f:
1135 for logfile in files[1:]:
1136 # Transfer the file to the joint file line by line.
1137 with open(logfile, 'r') as log_f:
1138 for line in log_f:
1139 joint_f.write(line)
1140 # File has been written over. Delete safely.
1141 os.remove(logfile)
1142 # Need to remove all files form |local_files| so we don't
1143 # analyze them again.
1144 local_files = list(set(local_files) - set(files) - set(compressed))
1145 # Lastly, extract MCU logs from the joint logs.
1146 self._extract_mcu_logs(log_dir)
1147
Garry Wang79e9af62019-06-12 15:19:19 -07001148 def _lock(self):
1149 """lock servohost by touching a file.
1150 """
1151 logging.debug('Locking servohost %s by touching %s file',
1152 self.hostname, self._lock_file)
1153 self.run('touch %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -07001154 self._is_locked = True
Garry Wang79e9af62019-06-12 15:19:19 -07001155
Garry Wang79e9af62019-06-12 15:19:19 -07001156 def _unlock(self):
1157 """Unlock servohost by removing the lock file.
1158 """
1159 logging.debug('Unlocking servohost by removing %s file',
1160 self._lock_file)
1161 self.run('rm %s' % self._lock_file, ignore_status=True)
Garry Wang7c00b0f2019-06-25 17:28:17 -07001162 self._is_locked = False
Garry Wang79e9af62019-06-12 15:19:19 -07001163
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001164 def close(self):
Congbin Guofc3b8962019-03-22 17:38:46 -07001165 """Close the associated servo and the host object."""
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001166 # NOTE: throughout this method there are multiple attempts to catch
1167 # all errors. This is WAI as log grabbing should not fail tests.
1168 # However, the goal is to catch and handle/process all errors, thus
1169 # we print the traceback and ask for a bug.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001170 if self._closed:
1171 logging.debug('ServoHost is already closed.')
1172 return
Garry Wang22f2e842020-09-09 20:19:19 -07001173
1174 # Only attempt ssh related actions if servohost is sshable. We call
1175 # check_cached_up_status() first because it's lightweighted and return
1176 # much faster in the case servohost is down, however, we still want
1177 # to call is_up() later since check_cached_up_status() is ping based check
1178 # and not guarantee the servohost is sshable.
1179 servo_host_ready = self.check_cached_up_status() and self.is_up()
1180
1181 if servo_host_ready:
1182 instance_ts = self.get_instance_logs_ts()
1183 else:
1184 logging.info('Servohost is down, will skip servod log collecting.')
1185 instance_ts = None
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001186 # TODO(crbug.com/1011516): once enabled, remove the check against
1187 # localhost and instead check against log-rotiation enablement.
1188 logs_available = (instance_ts is not None and
1189 self.job and
1190 not self.is_localhost())
1191 if logs_available:
1192 # Probe whether there was a servod restart, and grab those old
1193 # logs as well.
1194 try:
1195 self.probe_servod_restart(instance_ts, self.job.resultdir)
1196 except (error.AutoservRunError, error.TestFail) as e:
1197 logging.info('Failed to grab servo logs due to: %s. '
1198 'This error is forgiven.', str(e))
1199 except Exception as e:
1200 logging.error('Unexpected error probing for old logs. %s. '
1201 'Forgiven. Please file a bug and fix or catch '
1202 'in log probing function', str(e),
1203 exc_info=True)
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001204 if self._servo:
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001205 outdir = None if not self.job else self.job.resultdir
Congbin Guo2e5e2a22018-07-27 10:32:48 -07001206 # In some cases when we run as lab-tools, the job object is None.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001207 self._servo.close(outdir)
1208
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001209 if logs_available:
1210 # Grab current (not old like above) logs after the servo instance
1211 # was closed out.
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001212 try:
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001213 self.get_instance_logs(instance_ts, self.job.resultdir)
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001214 except error.AutoservRunError as e:
1215 logging.info('Failed to grab servo logs due to: %s. '
1216 'This error is forgiven.', str(e))
Ruben Rodriguez Buchillon5bac3062020-03-25 21:32:58 -07001217 except Exception as e:
1218 logging.error('Unexpected error grabbing servod logs. %s. '
1219 'Forgiven. Please file a bug and fix or catch '
1220 'in log grabbing function', str(e), exc_info=True)
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001221
Garry Wang22f2e842020-09-09 20:19:19 -07001222 if self._is_locked and servo_host_ready:
Garry Wang7c00b0f2019-06-25 17:28:17 -07001223 # Remove the lock if the servohost has been locked.
Garry Wang79e9af62019-06-12 15:19:19 -07001224 try:
1225 self._unlock()
1226 except error.AutoservSSHTimeout:
1227 logging.error('Unlock servohost failed due to ssh timeout.'
1228 ' It may caused by servohost went down during'
1229 ' the task.')
Garry Wangc1288cf2019-12-17 14:58:00 -08001230 # We want always stop servod after task to minimum the impact of bad
1231 # servod process interfere other servods.(see crbug.com/1028665)
Garry Wang22f2e842020-09-09 20:19:19 -07001232 if servo_host_ready:
1233 try:
1234 self.stop_servod()
1235 except error.AutoservRunError as e:
1236 logging.info(
1237 "Failed to stop servod due to:\n%s\n"
1238 "This error is forgiven.", str(e))
Garry Wangc1288cf2019-12-17 14:58:00 -08001239
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001240 super(ServoHost, self).close()
Ruben Rodriguez Buchillon93084d02020-01-21 15:17:36 -08001241 # Mark closed.
1242 self._closed = True
Congbin Guoa1f9cba2018-07-03 11:36:59 -07001243
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001244 def get_servo_state(self):
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001245 return self._servo_state
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001246
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001247 def _get_host_metrics_data(self):
1248 return {'port': self.servo_port,
Otabek Kasimov0ea47362020-07-11 20:55:09 -07001249 'host': self.get_dut_hostname() or self.hostname,
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001250 'board': self.servo_board or ''}
1251
1252 def _is_servo_device_connected(self, servo_type, serial):
1253 """Check if device is connected to the labstation.
1254
1255 Works for all servo devices connected to the labstation.
1256 For servo_v3 please use 'self._is_servo_board_present_on_servo_v3'
1257
1258 @param servo_type: The type of servo device. Expecting value can be
1259 servo_v4 or servo_micro.
1260 @param serial: The serial number of the device to detect it.
1261 """
1262 vid_pid = self.SERVO_VID_PID.get(servo_type)
1263 if not vid_pid or not serial:
1264 # device cannot detected without VID/PID or serial number
1265 return False
1266 logging.debug('Started to detect %s', servo_type)
1267 try:
1268 cmd = 'lsusb -v -d %s |grep iSerial |grep %s' % (vid_pid, serial)
1269 result = self.run(cmd, ignore_status=True, timeout=30)
1270 if result.exit_status == 0 and result.stdout.strip():
1271 logging.debug('The %s is plugged in to the host.', servo_type)
1272 return True
1273 logging.debug('%s device is not detected; %s', servo_type, result)
1274 return False
1275 except Exception as e:
1276 # can be triggered by timeout issue due running the script
1277 metrics.Counter(
1278 'chromeos/autotest/repair/servo_detection/timeout'
1279 ).increment(fields=self._get_host_metrics_data())
1280 logging.error('%s device is not detected; %s', servo_type, str(e))
1281 return None
1282
1283 def _is_servo_board_present_on_servo_v3(self):
1284 """Check if servo board is detected on servo_v3"""
1285 vid_pids = self.SERVO_VID_PID['servo_v3']
1286 if not vid_pids or len(vid_pids) == 0:
1287 # device cannot detected without VID/PID
1288 return False
1289 logging.debug('Started to detect servo board on servo_v3')
1290 not_detected = 'The servo board is not detected on servo_v3'
1291 try:
1292 cmd = 'lsusb | grep "%s"' % "\|".join(vid_pids)
1293 result = self.run(cmd, ignore_status=True, timeout=30)
1294 if result.exit_status == 0 and result.stdout.strip():
1295 logging.debug('The servo board is detected on servo_v3')
1296 return True
1297 logging.debug('%s; %s', not_detected, result)
1298 return False
1299 except Exception as e:
1300 # can be triggered by timeout issue due running the script
1301 metrics.Counter(
1302 'chromeos/autotest/repair/servo_detection/timeout'
1303 ).increment(fields=self._get_host_metrics_data())
1304 logging.error('%s; %s', not_detected, str(e))
1305 return None
1306
Otabek Kasimov120b6fa2020-07-03 00:15:27 -07001307 def _is_main_device_not_detected_on_servo_v4(self):
1308 """Check if servod cannot find main device on servo.
1309
1310 The check based on reading servod logs for servo_v4.
1311 """
1312 if not self._initial_instance_ts:
1313 # latest log not found
1314 return False
1315 logging.debug('latest log for servod created at %s',
1316 self._initial_instance_ts)
1317 try:
1318 log_created = calendar.timegm(time.strptime(
1319 self._initial_instance_ts,
1320 "%Y-%m-%d--%H-%M-%S.%f"))
1321 except ValueError as e:
1322 logging.debug('Cannot read time from log file name: %s',
1323 self._initial_instance_ts)
1324 return False
1325 min_time_created = calendar.timegm(time.gmtime())
1326 if min_time_created > log_created + 3600:
1327 # the log file is old we cannot use it
1328 logging.debug('log file was created more than hour ago, too old')
1329 return False
1330 logging.debug('latest log was created not longer then 1 hour ago')
1331
1332 # check if servod can detect main device by servo_v4
1333 message = 'ERROR - No servo micro or CCD detected for board'
1334 cmd = ('cat /var/log/servod_%s/log.%s.INFO |grep "%s"'
1335 % (self.servo_port, self._initial_instance_ts, message))
1336 result = self.run(cmd, ignore_status=True)
1337 if result.stdout.strip():
1338 logging.info('Servod cannot detect main device on the servo; '
1339 'Can be caused by bad hardware of servo or '
1340 'issue on the DUT side.')
1341 return True
1342 logging.debug('The main device is detected')
1343 return False
1344
Garry Wangb5cee3e2020-09-16 14:58:13 -07001345 def _require_cr50_servod_config(self):
1346 """Check whether we need start servod with CONFIG=cr50.xml"""
1347 dut_host_info = self.get_dut_host_info()
1348 if not dut_host_info:
1349 return False
1350 for pool in dut_host_info.pools:
1351 if pool.startswith(servo_constants.CR50_CONFIG_POOL_PREFIX):
1352 return True
1353 return False
1354
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001355 def get_verifier_state(self, tag):
Otabek Kasimov15963492020-06-23 21:10:51 -07001356 """Return the state of servo verifier.
1357
1358 @returns: bool or None
1359 """
1360 return self._repair_strategy.verifier_is_good(tag)
1361
1362 def determine_servo_state(self):
1363 """Determine servo state based on the failed verifier.
1364
1365 @returns: servo state value
1366 The state detecting based on first fail verifier or collecting of
1367 them.
1368 """
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001369 ssh = self.get_verifier_state('servo_ssh')
1370 disk_space = self.get_verifier_state('disk_space')
1371 start_servod = self.get_verifier_state('servod_job')
1372 create_servo = self.get_verifier_state('servod_connection')
1373 init_servo = self.get_verifier_state('servod_control')
Otabek Kasimov8e88a742021-01-11 18:03:13 -08001374 cr50_low_sbu = self.get_verifier_state('cr50_low_sbu')
1375 cr50_off = self.get_verifier_state('cr50_off')
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001376 servo_topology = self.get_verifier_state('servo_topology')
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001377 dut_connected = self.get_verifier_state('dut_connected')
Otabek Kasimov9fb2cee2020-11-23 23:06:55 -08001378 hub_connected = self.get_verifier_state('hub_connected')
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001379 pwr_button = self.get_verifier_state('pwr_button')
1380 lid_open = self.get_verifier_state('lid_open')
1381 ec_board = self.get_verifier_state('ec_board')
Otabek Kasimovbb3bc462020-11-03 16:40:33 -08001382 cr50_console = self.get_verifier_state('cr50_console')
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001383 ccd_testlab = self.get_verifier_state('ccd_testlab')
Otabek Kasimov15963492020-06-23 21:10:51 -07001384
1385 if not ssh:
1386 return servo_constants.SERVO_STATE_NO_SSH
1387
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001388 if (start_servod == hosts.VERIFY_FAILED
1389 or create_servo == hosts.VERIFY_FAILED):
Otabek Kasimov066bdb82020-08-12 15:57:44 -07001390 # sometimes servo can start with out present servo
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001391 if self.is_labstation():
1392 if not self.servo_serial:
1393 return servo_constants.SERVO_STATE_WRONG_CONFIG
1394 if self._is_servo_device_connected(
1395 'servo_v4',
1396 self.servo_serial) == False:
1397 return servo_constants.SERVO_STATE_NOT_CONNECTED
1398 elif self._is_servo_board_present_on_servo_v3() == False:
1399 return servo_constants.SERVO_STATE_NOT_CONNECTED
Otabek Kasimov066bdb82020-08-12 15:57:44 -07001400
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001401 if dut_connected == hosts.VERIFY_FAILED:
1402 return servo_constants.SERVO_STATE_DUT_NOT_CONNECTED
1403 if hub_connected == hosts.VERIFY_FAILED:
1404 logging.info('Servo HUB not connected')
1405 return servo_constants.SERVO_STATE_DUT_NOT_CONNECTED
Otabek Kasimov8e88a742021-01-11 18:03:13 -08001406 if cr50_low_sbu == hosts.VERIFY_FAILED:
1407 return servo_constants.SERVO_STATE_SBU_LOW_VOLTAGE
1408 if cr50_off == hosts.VERIFY_FAILED:
1409 return servo_constants.SERVO_STATE_CR50_NOT_ENUMERATED
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001410 if servo_topology == hosts.VERIFY_FAILED:
1411 return servo_constants.SERVO_STATE_TOPOLOGY_ISSUE
1412
Otabek Kasimovd5065bd2020-11-23 23:32:36 -08001413 # TODO(otabek@): detect special cases detected by pwr_button
1414 if dut_connected == hosts.VERIFY_SUCCESS:
1415 if pwr_button == hosts.VERIFY_FAILED:
1416 metrics.Counter(
1417 'chromeos/autotest/repair/servo_unexpected/pwr_button2'
1418 ).increment(fields=self._get_host_metrics_data())
Otabek Kasimova7eb4dc2020-09-16 10:25:17 -07001419
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001420 if start_servod == hosts.VERIFY_FAILED:
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001421 return servo_constants.SERVO_STATE_SERVOD_ISSUE
1422
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001423 if create_servo == hosts.VERIFY_FAILED:
Otabek Kasimov120b6fa2020-07-03 00:15:27 -07001424 if (self.is_labstation()
1425 and self._is_main_device_not_detected_on_servo_v4()):
1426 servo_type = None
1427 if self.get_dut_host_info():
1428 servo_type = self.get_dut_host_info().get_label_value(
1429 servo_constants.SERVO_TYPE_LABEL_PREFIX)
1430 if servo_type and 'servo_micro' in servo_type:
1431 serial = self.get_servo_micro_serial_number()
1432 logging.debug('servo_micro serial: %s', serial)
1433 if self._is_servo_device_detected('servo_micro',
1434 serial):
1435 return servo_constants.SERVO_STATE_BAD_RIBBON_CABLE
1436 # Device can be not detected because of DUT
1437 # TODO (otabek) update after b/159755652 and b/159754985
1438 metrics.Counter(
1439 'chromeos/autotest/repair/servo_state/needs_replacement'
1440 ).increment(fields=self._get_host_metrics_data())
1441 elif not self.is_labstation():
1442 # Here need logic to check if flex cable is connected
1443 pass
1444
Otabek Kasimov15963492020-06-23 21:10:51 -07001445 # one of the reason why servo can not initialized
Otabek Kasimovbb3bc462020-11-03 16:40:33 -08001446 if cr50_console == hosts.VERIFY_FAILED:
1447 return servo_constants.SERVO_STATE_CR50_CONSOLE_MISSING
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001448 if ccd_testlab == hosts.VERIFY_FAILED:
Otabek Kasimov15963492020-06-23 21:10:51 -07001449 return servo_constants.SERVO_STATE_CCD_TESTLAB_ISSUE
1450
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001451 if (create_servo == hosts.VERIFY_FAILED
1452 or init_servo == hosts.VERIFY_FAILED):
Otabek Kasimov15963492020-06-23 21:10:51 -07001453 return servo_constants.SERVO_STATE_SERVOD_ISSUE
1454
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001455 if ec_board == hosts.VERIFY_FAILED:
Otabek Kasimov015c15c2020-08-20 00:40:42 -07001456 return servo_constants.SERVO_STATE_EC_BROKEN
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001457 if pwr_button == hosts.VERIFY_FAILED:
Otabek Kasimov15963492020-06-23 21:10:51 -07001458 return servo_constants.SERVO_STATE_BAD_RIBBON_CABLE
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001459 if lid_open == hosts.VERIFY_FAILED:
Otabek Kasimov15963492020-06-23 21:10:51 -07001460 return servo_constants.SERVO_STATE_LID_OPEN_FAILED
Otabek Kasimov15963492020-06-23 21:10:51 -07001461
Otabek Kasimov15963492020-06-23 21:10:51 -07001462 metrics.Counter(
1463 'chromeos/autotest/repair/unknown_servo_state'
Otabek Kasimovc6f30412020-06-30 20:08:12 -07001464 ).increment(fields=self._get_host_metrics_data())
Otabek Kasimov15963492020-06-23 21:10:51 -07001465 logging.info('We do not have special state for this failure yet :)')
1466 return servo_constants.SERVO_STATE_BROKEN
1467
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001468 def is_servo_topology_supported(self):
1469 """Check if servo_topology is supported."""
Otabek Kasimovda994012020-11-25 15:23:04 -08001470 if not self.is_up_fast():
1471 logging.info('Servo-Host is not reachable.')
1472 return False
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001473 if not self.is_labstation():
1474 logging.info('Servo-topology supported only for labstation.')
1475 return False
1476 if not self.servo_serial:
1477 logging.info('Servo-topology required a servo serial.')
1478 return False
1479 return True
1480
1481 def get_topology(self):
1482 """Get servo topology."""
1483 return self._topology
1484
1485 def is_dual_setup(self):
1486 """Check is servo will run in dual setup.
1487
1488 Dual setup used only for servo_v4 when used ccd_cr50 and servo_micro
1489 at the same time.
1490 """
1491 return self.servo_setup == servo_constants.SERVO_SETUP_VALUE_DUAL_V4
1492
Otabek Kasimov39637412020-11-23 19:09:27 -08001493 def set_dut_health_profile(self, dut_health_profile):
1494 """
1495 @param dut_health_profile: A DeviceHealthProfile object.
1496 """
1497 logging.debug('setting dut_health_profile field to (%s)',
1498 dut_health_profile)
1499 self._dut_health_profile = dut_health_profile
1500
1501 def get_dut_health_profile(self):
1502 """
1503 @return A DeviceHealthProfile object.
1504 """
1505 return self._dut_health_profile
1506
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001507
Richard Barnetteea3e4602016-06-10 12:36:41 -07001508def make_servo_hostname(dut_hostname):
1509 """Given a DUT's hostname, return the hostname of its servo.
1510
1511 @param dut_hostname: hostname of a DUT.
1512
1513 @return hostname of the DUT's servo.
1514
1515 """
1516 host_parts = dut_hostname.split('.')
1517 host_parts[0] = host_parts[0] + '-servo'
1518 return '.'.join(host_parts)
1519
1520
Richard Barnettee519dcd2016-08-15 17:37:17 -07001521def _map_afe_board_to_servo_board(afe_board):
1522 """Map a board we get from the AFE to a servo appropriate value.
1523
1524 Many boards are identical to other boards for servo's purposes.
1525 This function makes that mapping.
1526
1527 @param afe_board string board name received from AFE.
1528 @return board we expect servo to have.
1529
1530 """
1531 KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets']
1532 BOARD_MAP = {'gizmo': 'panther'}
1533 mapped_board = afe_board
1534 if afe_board in BOARD_MAP:
1535 mapped_board = BOARD_MAP[afe_board]
1536 else:
1537 for suffix in KNOWN_SUFFIXES:
1538 if afe_board.endswith(suffix):
1539 mapped_board = afe_board[0:-len(suffix)]
1540 break
1541 if mapped_board != afe_board:
1542 logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board)
1543 return mapped_board
1544
1545
Prathmesh Prabhub4810232018-09-07 13:24:08 -07001546def get_servo_args_for_host(dut_host):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -07001547 """Return servo data associated with a given DUT.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001548
Richard Barnetteea3e4602016-06-10 12:36:41 -07001549 @param dut_host Instance of `Host` on which to find the servo
1550 attributes.
Prathmesh Prabhuf605dd32018-08-28 17:09:04 -07001551 @return `servo_args` dict with host and an optional port.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001552 """
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001553 info = dut_host.host_info_store.get()
Derek Beckettf73baca2020-08-19 15:08:47 -07001554 servo_args = {k: v for k, v in six.iteritems(info.attributes)
Garry Wang11b5e872020-03-11 15:14:08 -07001555 if k in servo_constants.SERVO_ATTR_KEYS}
Richard Barnetteea3e4602016-06-10 12:36:41 -07001556
Garry Wang11b5e872020-03-11 15:14:08 -07001557 if servo_constants.SERVO_PORT_ATTR in servo_args:
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001558 try:
Garry Wang11b5e872020-03-11 15:14:08 -07001559 servo_args[servo_constants.SERVO_PORT_ATTR] = int(
1560 servo_args[servo_constants.SERVO_PORT_ATTR])
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001561 except ValueError:
1562 logging.error('servo port is not an int: %s',
Garry Wang11b5e872020-03-11 15:14:08 -07001563 servo_args[servo_constants.SERVO_PORT_ATTR])
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001564 # Reset servo_args because we don't want to use an invalid port.
Garry Wang11b5e872020-03-11 15:14:08 -07001565 servo_args.pop(servo_constants.SERVO_HOST_ATTR, None)
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001566
1567 if info.board:
Garry Wang11b5e872020-03-11 15:14:08 -07001568 servo_board = _map_afe_board_to_servo_board(info.board)
1569 servo_args[servo_constants.SERVO_BOARD_ATTR] = servo_board
Nick Sanders2f3c9852018-10-24 12:10:24 -07001570 if info.model:
Garry Wang11b5e872020-03-11 15:14:08 -07001571 servo_args[servo_constants.SERVO_MODEL_ATTR] = info.model
1572 return servo_args if servo_constants.SERVO_HOST_ATTR in servo_args else None
Richard Barnetteea3e4602016-06-10 12:36:41 -07001573
1574
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -07001575def _tweak_args_for_ssp_moblab(servo_args):
Garry Wang11b5e872020-03-11 15:14:08 -07001576 if (servo_args[servo_constants.SERVO_HOST_ATTR]
1577 in ['localhost', '127.0.0.1']):
1578 servo_args[servo_constants.SERVO_HOST_ATTR] = _CONFIG.get_config_value(
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -07001579 'SSP', 'host_container_ip', type=str, default=None)
1580
1581
Otabek Kasimov39637412020-11-23 19:09:27 -08001582def create_servo_host(dut,
1583 servo_args,
1584 try_lab_servo=False,
1585 try_servo_repair=False,
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001586 try_servo_recovery=False,
Otabek Kasimov39637412020-11-23 19:09:27 -08001587 dut_host_info=None,
1588 dut_health_profile=None):
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -07001589 """Create a ServoHost object for a given DUT, if appropriate.
Dan Shi4d478522014-02-14 13:46:32 -08001590
Richard Barnette9a26ad62016-06-10 12:03:08 -07001591 This function attempts to create and verify or repair a `ServoHost`
1592 object for a servo connected to the given `dut`, subject to various
1593 constraints imposed by the parameters:
1594 * When the `servo_args` parameter is not `None`, a servo
1595 host must be created, and must be checked with `repair()`.
1596 * Otherwise, if a servo exists in the lab and `try_lab_servo` is
1597 true:
1598 * If `try_servo_repair` is true, then create a servo host and
1599 check it with `repair()`.
1600 * Otherwise, if the servo responds to `ping` then create a
1601 servo host and check it with `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -08001602
Richard Barnette9a26ad62016-06-10 12:03:08 -07001603 In cases where `servo_args` was not `None`, repair failure
1604 exceptions are passed back to the caller; otherwise, exceptions
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001605 are logged and then discarded. Note that this only happens in cases
1606 where we're called from a test (not special task) control file that
1607 has an explicit dependency on servo. In that case, we require that
1608 repair not write to `status.log`, so as to avoid polluting test
1609 results.
1610
1611 TODO(jrbarnette): The special handling for servo in test control
1612 files is a thorn in my flesh; I dearly hope to see it cut out before
1613 my retirement.
Richard Barnette9a26ad62016-06-10 12:03:08 -07001614
1615 Parameters for a servo host consist of a host name, port number, and
1616 DUT board, and are determined from one of these sources, in order of
1617 priority:
Richard Barnetteea3e4602016-06-10 12:36:41 -07001618 * Servo attributes from the `dut` parameter take precedence over
1619 all other sources of information.
1620 * If a DNS entry for the servo based on the DUT hostname exists in
1621 the CrOS lab network, that hostname is used with the default
Richard Barnette9a26ad62016-06-10 12:03:08 -07001622 port and the DUT's board.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001623 * If no other options are found, the parameters will be taken
Richard Barnette9a26ad62016-06-10 12:03:08 -07001624 from the `servo_args` dict passed in from the caller.
Richard Barnetteea3e4602016-06-10 12:36:41 -07001625
1626 @param dut An instance of `Host` from which to take
1627 servo parameters (if available).
1628 @param servo_args A dictionary with servo parameters to use if
1629 they can't be found from `dut`. If this
1630 argument is supplied, unrepaired exceptions
1631 from `verify()` will be passed back to the
1632 caller.
1633 @param try_lab_servo If not true, servo host creation will be
1634 skipped unless otherwise required by the
1635 caller.
Richard Barnette9a26ad62016-06-10 12:03:08 -07001636 @param try_servo_repair If true, check a servo host with
1637 `repair()` instead of `verify()`.
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001638 @param try_servo_recovery If true, start servod in recovery mode.
Otabek Kasimov8475cce2020-07-14 12:11:31 -07001639 @param dut_host_info: A HostInfo object of the DUT that connected
1640 to this servo.
Otabek Kasimov39637412020-11-23 19:09:27 -08001641 @param dut_health_profile: DUT repair info with history.
Dan Shi4d478522014-02-14 13:46:32 -08001642
1643 @returns: A ServoHost object or None. See comments above.
1644
1645 """
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001646 servo_dependency = servo_args is not None
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001647 if dut is not None and (try_lab_servo or servo_dependency):
Prathmesh Prabhub4810232018-09-07 13:24:08 -07001648 servo_args_override = get_servo_args_for_host(dut)
Richard Barnetteea3e4602016-06-10 12:36:41 -07001649 if servo_args_override is not None:
Prathmesh Prabhuefb1b482018-08-28 17:15:05 -07001650 if utils.in_moblab_ssp():
1651 _tweak_args_for_ssp_moblab(servo_args_override)
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001652 logging.debug(
1653 'Overriding provided servo_args (%s) with arguments'
1654 ' determined from the host (%s)',
1655 servo_args,
1656 servo_args_override,
1657 )
Richard Barnetteea3e4602016-06-10 12:36:41 -07001658 servo_args = servo_args_override
Prathmesh Prabhucba44292018-08-28 17:44:45 -07001659
Richard Barnetteea3e4602016-06-10 12:36:41 -07001660 if servo_args is None:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001661 logging.debug('No servo_args provided, and failed to find overrides.')
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001662 if try_lab_servo or servo_dependency:
Otabek Kasimov646812c2020-06-23 20:01:36 -07001663 return None, servo_constants.SERVO_STATE_MISSING_CONFIG
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001664 else:
1665 # For regular test case which not required the servo
1666 return None, None
1667
Garry Wang11b5e872020-03-11 15:14:08 -07001668 servo_hostname = servo_args.get(servo_constants.SERVO_HOST_ATTR)
1669 servo_port = servo_args.get(servo_constants.SERVO_PORT_ATTR)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001670 if not _is_servo_host_information_exist(servo_hostname, servo_port):
1671 logging.debug(
1672 'Servo connection info missed hostname: %s , port: %s',
1673 servo_hostname, servo_port)
Otabek Kasimov646812c2020-06-23 20:01:36 -07001674 return None, servo_constants.SERVO_STATE_MISSING_CONFIG
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001675 if not is_servo_host_information_valid(servo_hostname, servo_port):
1676 logging.debug(
1677 'Servo connection info is incorrect hostname: %s , port: %s',
1678 servo_hostname, servo_port)
Garry Wang11b5e872020-03-11 15:14:08 -07001679 return None, servo_constants.SERVO_STATE_WRONG_CONFIG
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001680
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001681 if try_servo_recovery == True:
1682 servo_args[servo_constants.SERVO_RECOVERY_MODE] = True
1683
Garry Wangebc015b2019-06-06 17:45:06 -07001684 newhost = ServoHost(**servo_args)
Otabek Kasimov0e4027f2020-12-08 16:59:56 -08001685 if not newhost.is_up_fast(count=3):
Otabek Kasimov32cafe92020-12-14 16:58:12 -08001686 # ServoHost has internal check to wait if servo-host is in reboot
1687 # process. If servo-host still is not available this check will stop
1688 # further attempts as we do not have any option to recover servo_host.
Otabek Kasimov39637412020-11-23 19:09:27 -08001689 return None, servo_constants.SERVO_STATE_NO_SSH
Garry Wangffbd2162020-04-17 16:13:48 -07001690
Otabek Kasimove6df8102020-07-21 20:15:25 -07001691 # Reset or reboot servo device only during AdminRepair tasks.
1692 if try_servo_repair:
1693 if newhost._is_locked:
1694 # Reset servo if the servo is locked, as we check if the servohost
1695 # is up, if the servohost is labstation and if the servohost is in
1696 # lab inside the locking logic.
1697 newhost.reset_servo()
1698 else:
Garry Wang358aad42020-08-02 20:56:04 -07001699 try:
1700 newhost.reboot_servo_v3_on_need()
Garry Wang1f0d5332020-08-10 19:32:32 -07001701 except Exception as e:
1702 logging.info('[Non-critical] Unexpected error while trying to'
1703 ' reboot servo_v3, skipping the reboot; %s', e)
Otabek Kasimove6df8102020-07-21 20:15:25 -07001704
Otabek Kasimov2b50cdb2020-07-06 19:16:06 -07001705 if dut:
1706 newhost.set_dut_hostname(dut.hostname)
Otabek Kasimov9e90ae12020-08-14 03:01:19 -07001707 if dut_host_info:
1708 newhost.set_dut_host_info(dut_host_info)
Otabek Kasimov39637412020-11-23 19:09:27 -08001709 if dut_health_profile and (try_lab_servo or try_servo_repair):
1710 try:
1711 if newhost.is_localhost():
1712 logging.info('Servohost is a localhost, skip device'
1713 ' health profile setup...')
1714 else:
1715 dut_health_profile.init_profile(newhost)
1716 newhost.set_dut_health_profile(dut_health_profile)
1717 except Exception as e:
1718 logging.info(
1719 '[Non-critical] Unexpected error while trying to'
1720 ' load device health profile; %s', e)
Garry Wangffbd2162020-04-17 16:13:48 -07001721
Otabek Kasimov8475cce2020-07-14 12:11:31 -07001722 if try_lab_servo or try_servo_repair:
1723 try:
1724 logging.info("Check and update servo firmware.")
1725 servo_updater.update_servo_firmware(
1726 newhost,
1727 force_update=False)
1728 except Exception as e:
1729 logging.error("Servo device update error: %s", e)
1730
Garry Wangcdd27b22020-01-13 14:59:11 -08001731 try:
1732 newhost.restart_servod(quick_startup=True)
1733 except error.AutoservSSHTimeout:
1734 logging.warning("Restart servod failed due ssh connection "
1735 "to servohost timed out. This error is forgiven"
1736 " here, we will retry in servo repair process.")
1737 except error.AutoservRunError as e:
1738 logging.warning("Restart servod failed due to:\n%s\n"
1739 "This error is forgiven here, we will retry"
1740 " in servo repair process.", str(e))
Garry Wangebc015b2019-06-06 17:45:06 -07001741
Richard Barnette9a26ad62016-06-10 12:03:08 -07001742 # Note that the logic of repair() includes everything done
1743 # by verify(). It's sufficient to call one or the other;
1744 # we don't need both.
Richard Barnette07c2e1d2016-10-26 14:24:28 -07001745 if servo_dependency:
1746 newhost.repair(silent=True)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001747 return newhost, newhost.get_servo_state()
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001748
1749 if try_servo_repair:
1750 try:
1751 newhost.repair()
1752 except Exception:
1753 logging.exception('servo repair failed for %s', newhost.hostname)
Richard Barnette9a26ad62016-06-10 12:03:08 -07001754 else:
1755 try:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001756 newhost.verify()
Kevin Cheng5f2ba6c2016-09-28 10:20:05 -07001757 except Exception:
Prathmesh Prabhu88bf6052018-08-28 16:21:26 -07001758 logging.exception('servo verify failed for %s', newhost.hostname)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001759 return newhost, newhost.get_servo_state()
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001760
1761
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001762def _is_servo_host_information_exist(hostname, port):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001763 if hostname is None or len(hostname.strip()) == 0:
1764 return False
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001765 if port is None:
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001766 return False
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001767 if not type(port) is int:
1768 try:
1769 int(port)
1770 except ValueError:
1771 return False
1772
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001773 return True
1774
1775
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001776def is_servo_host_information_valid(hostname, port):
Garry Wang9b8f2342020-04-17 16:34:09 -07001777 """Check if provided servo attributes are valid.
1778
1779 @param hostname Hostname of the servohost.
1780 @param port servo port number.
1781
1782 @returns: A bool value to indicate if provided servo attribute valid.
1783 """
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001784 if not _is_servo_host_information_exist(hostname, port):
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001785 return False
1786 # checking range and correct of the port
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001787 port_int = int(port)
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001788 if port_int < 1 or port_int > 65000:
1789 return False
1790 # we expecting host contain only latters, digits and '-' or '_'
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001791 if not re.match('[a-zA-Z0-9-_\.]*$', hostname) or len(hostname) < 5:
Otabek Kasimov7267a7a2020-03-04 11:18:45 -08001792 return False
1793 return True