blob: 4c44c218cc267194d1062f35ae7ef5756ab3efc6 [file] [log] [blame]
Derek Beckettf73baca2020-08-19 15:08:47 -07001# Lint as: python2, python3
J. Richard Barnette24adbf42012-04-11 15:04:53 -07002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Dale Curtisaa5eedb2011-08-23 16:18:52 -07003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Derek Beckettf73baca2020-08-19 15:08:47 -07006from __future__ import absolute_import
7from __future__ import division
8from __future__ import print_function
9
J. Richard Barnette1d78b012012-05-15 13:56:30 -070010import logging
Dan Shi0f466e82013-02-22 15:44:58 -080011import os
Simran Basid5e5e272012-09-24 15:23:59 -070012import re
Vincent Palatindf2372c2016-10-07 17:03:00 +020013import sys
Otabek Kasimovb7cb8422020-12-23 02:38:32 -080014import six
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070015import time
16
mussa584b4462014-06-20 15:13:28 -070017import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070018from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070019from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080020from autotest_lib.client.common_lib import error
21from autotest_lib.client.common_lib import global_config
J. Richard Barnette91137f02016-03-10 16:52:26 -080022from autotest_lib.client.common_lib import hosts
Dan Shi549fb822015-03-24 18:01:11 -070023from autotest_lib.client.common_lib import lsbrelease_utils
Otabek Kasimov6825b762020-06-23 23:42:44 -070024from autotest_lib.client.common_lib import utils as common_utils
Greg Edelstona7b05d12020-04-01 16:00:51 -060025from autotest_lib.client.common_lib.cros import cros_config
Richard Barnette03a0c132012-11-05 12:40:35 -080026from autotest_lib.client.common_lib.cros import dev_server
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -070027from autotest_lib.client.common_lib.cros import retry
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000028from autotest_lib.client.cros import constants as client_constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080029from autotest_lib.client.cros import cros_ui
Simran Basi5ace6f22016-01-06 17:30:44 -080030from autotest_lib.server import afe_utils
Dan Shia1ecd5c2013-06-06 11:21:31 -070031from autotest_lib.server import utils as server_utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070032from autotest_lib.server.cros import provision
Scott Zawalski89c44dd2013-02-26 09:28:02 -050033from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070034from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
Garry Wang1a493d82020-08-31 21:01:19 -070035from autotest_lib.server.cros.device_health_profile import device_health_profile
Garry Wanga2e78172020-09-09 23:49:07 -070036from autotest_lib.server.cros.device_health_profile import profile_constants
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -070037from autotest_lib.server.cros.servo import pdtester
Fang Deng96667ca2013-08-01 17:46:18 -070038from autotest_lib.server.hosts import abstract_ssh
Kevin Chenga2619dc2016-03-28 11:42:08 -070039from autotest_lib.server.hosts import base_label
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +080040from autotest_lib.server.hosts import chameleon_host
Otabek Kasimov832d9162020-07-27 19:24:57 -070041from autotest_lib.server.hosts import cros_constants
Richard Barnetted31580e2018-05-14 19:58:00 +000042from autotest_lib.server.hosts import cros_label
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -080043from autotest_lib.server.hosts import cros_repair
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -070044from autotest_lib.server.hosts import pdtester_host
Fang Deng5d518f42013-08-02 14:04:32 -070045from autotest_lib.server.hosts import servo_host
Garry Wang11b5e872020-03-11 15:14:08 -070046from autotest_lib.server.hosts import servo_constants
Simran Basidcff4252012-11-20 16:13:20 -080047from autotest_lib.site_utils.rpm_control_system import rpm_client
Otabek Kasimov808cd832020-05-28 18:27:46 -070048from autotest_lib.site_utils.admin_audit import constants as audit_const
Otabek Kasimov27bb2862020-08-10 14:40:45 -070049from autotest_lib.site_utils.admin_audit import verifiers as audit_verify
Derek Beckettf73baca2020-08-19 15:08:47 -070050from six.moves import zip
Simran Basid5e5e272012-09-24 15:23:59 -070051
Simran Basi382506b2016-09-13 14:58:15 -070052# In case cros_host is being ran via SSP on an older Moblab version with an
53# older chromite version.
54try:
Mike Frysinger714c5b02020-09-04 23:22:54 -040055 from autotest_lib.utils.frozen_chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080056except ImportError:
Congbin Guo42427612019-02-12 10:22:06 -080057 metrics = utils.metrics_mock
Dan Shi5e2efb72017-02-07 11:40:23 -080058
Simran Basid5e5e272012-09-24 15:23:59 -070059
Dan Shib8540a52015-07-16 14:18:23 -070060CONFIG = global_config.global_config
61
beepsc87ff602013-07-31 21:53:00 -070062class FactoryImageCheckerException(error.AutoservError):
63 """Exception raised when an image is a factory image."""
64 pass
65
66
Fang Deng0ca40e22013-08-27 17:47:44 -070067class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070068 """Chromium OS specific subclass of Host."""
69
Simran Basi5ace6f22016-01-06 17:30:44 -080070 VERSION_PREFIX = provision.CROS_VERSION_PREFIX
71
Scott Zawalski62bacae2013-03-05 10:40:32 -050072 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070073
Richard Barnette03a0c132012-11-05 12:40:35 -080074 # Timeout values (in seconds) associated with various Chrome OS
75 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070076 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080077 # In general, a good rule of thumb is that the timeout can be up
78 # to twice the typical measured value on the slowest platform.
79 # The times here have not necessarily been empirically tested to
80 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070081 #
82 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080083 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
84 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080085 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070086 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080087 # other things, this must account for the 30 second dev-mode
J. Richard Barnette417cc792015-10-01 09:56:36 -070088 # screen delay, time to start the network on the DUT, and the
89 # ssh timeout of 120 seconds.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070090 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080091 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080092 # network.
beepsf079cfb2013-09-18 17:49:51 -070093 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080094 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
95 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070096
97 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080098 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +080099 SHUTDOWN_TIMEOUT = 10
J. Richard Barnette417cc792015-10-01 09:56:36 -0700100 BOOT_TIMEOUT = 150
J. Richard Barnette5bab5f52015-08-03 13:14:38 -0700101 USB_BOOT_TIMEOUT = 300
J. Richard Barnette7817b052014-08-28 09:47:29 -0700102 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -0800103 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -0700104
Dan Shica503482015-03-30 17:23:25 -0700105 # Minimum OS version that supports server side packaging. Older builds may
106 # not have server side package built or with Autotest code change to support
107 # server-side packaging.
Dan Shib8540a52015-07-16 14:18:23 -0700108 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value(
Dan Shiced09e42015-04-17 16:09:34 -0700109 'AUTOSERV', 'min_version_support_ssp', type=int)
Dan Shica503482015-03-30 17:23:25 -0700110
Dana Goyettec172b172020-07-29 16:26:15 -0700111 USE_FSFREEZE = CONFIG.get_config_value(
Dana Goyette6242cb32020-09-23 11:02:57 -0700112 'CROS', 'enable_fs_freeze', type=bool, default=False)
Dana Goyettec172b172020-07-29 16:26:15 -0700113
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800114 # REBOOT_TIMEOUT: How long to wait for a reboot.
115 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700116 # We have a long timeout to ensure we don't flakily fail due to other
117 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700118 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
119 # return from reboot' bug is solved.
120 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700121
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800122 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
123 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
Garry Wang5e5538a2019-04-08 15:36:18 -0700124 # _CHANGE_SERVO_ROLE_TIMEOUT: Time to allow DUT regain network connection
125 # since changing servo role will reset USB state
126 # and causes temporary ethernet drop.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800127 _USB_POWER_TIMEOUT = 5
128 _POWER_CYCLE_TIMEOUT = 10
Garry Wang5e5538a2019-04-08 15:36:18 -0700129 _CHANGE_SERVO_ROLE_TIMEOUT = 180
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800130
Fang Dengdeba14f2014-11-14 11:54:09 -0800131 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
132 '-host(\d+)')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700133
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800134 # Constants used in ping_wait_up() and ping_wait_down().
135 #
136 # _PING_WAIT_COUNT is the approximate number of polling
137 # cycles to use when waiting for a host state change.
138 #
139 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
140 # for arguments to the internal _ping_wait_for_status()
141 # method.
142 _PING_WAIT_COUNT = 40
143 _PING_STATUS_DOWN = False
144 _PING_STATUS_UP = True
145
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800146 # Allowed values for the power_method argument.
147
Garry Wang5e5538a2019-04-08 15:36:18 -0700148 # POWER_CONTROL_RPM: Used in power_off/on/cycle() methods, default for all
149 # DUTs except those with servo_v4 CCD.
150 # POWER_CONTROL_CCD: Used in power_off/on/cycle() methods, default for all
151 # DUTs with servo_v4 CCD.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800152 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
153 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
154 POWER_CONTROL_RPM = 'RPM'
Garry Wang5e5538a2019-04-08 15:36:18 -0700155 POWER_CONTROL_CCD = 'CCD'
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800156 POWER_CONTROL_SERVO = 'servoj10'
157 POWER_CONTROL_MANUAL = 'manual'
158
159 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
Garry Wang5e5538a2019-04-08 15:36:18 -0700160 POWER_CONTROL_CCD,
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800161 POWER_CONTROL_SERVO,
162 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800163
Simran Basi5e6339a2013-03-21 11:34:32 -0700164 _RPM_OUTLET_CHANGED = 'outlet_changed'
165
Dan Shi9cb0eec2014-06-03 09:04:50 -0700166 # URL pattern to download firmware image.
Dan Shib8540a52015-07-16 14:18:23 -0700167 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value(
Dan Shi9cb0eec2014-06-03 09:04:50 -0700168 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700169
Brent Peterson1cb623a2020-01-09 13:14:28 -0800170 # Regular expression for extracting EC version string
171 _EC_REGEX = '(%s_\w*[-\.]\w*[-\.]\w*[-\.]\w*)'
172
173 # Regular expression for extracting BIOS version string
174 _BIOS_REGEX = '(%s\.\w*\.\w*\.\w*)'
175
Brent Petersonc70a1832020-01-24 15:54:35 -0800176 # Command to update firmware located on DUT
Namyoon Woo382e5892020-05-20 16:48:40 -0700177 _FW_UPDATE_CMD = 'chromeos-firmwareupdate --mode=recovery %s'
Brent Petersonc70a1832020-01-24 15:54:35 -0800178
J. Richard Barnette964fba02012-10-24 17:34:29 -0700179 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800180 def check_host(host, timeout=10):
181 """
182 Check if the given host is a chrome-os host.
183
184 @param host: An ssh host representing a device.
185 @param timeout: The timeout for the run command.
186
187 @return: True if the host device is chromeos.
188
beeps46dadc92013-11-07 14:07:10 -0800189 """
190 try:
Allen Liad719c12017-06-27 23:48:04 +0000191 result = host.run(
Simran Basi933c8af2015-04-29 14:05:07 -0700192 'grep -q CHROMEOS /etc/lsb-release && '
Garry Wange4b6d6e2019-06-17 17:08:46 -0700193 '! grep -q moblab /etc/lsb-release && '
Derek Beckett342e3e62021-01-05 17:17:23 -0800194 '! grep -q labstation /etc/lsb-release &&'
195 ' grep CHROMEOS_RELEASE_BOARD /etc/lsb-release',
196 ignore_status=True,
Laurence Goodby468de252017-06-08 17:22:53 -0700197 timeout=timeout).stdout
Derek Beckett342e3e62021-01-05 17:17:23 -0800198 if result:
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -0800199 return not (
200 lsbrelease_utils.is_jetstream(
Derek Beckett342e3e62021-01-05 17:17:23 -0800201 lsb_release_content=result) or
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -0800202 lsbrelease_utils.is_gce_board(
Derek Beckett342e3e62021-01-05 17:17:23 -0800203 lsb_release_content=result))
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -0800204
beeps46dadc92013-11-07 14:07:10 -0800205 except (error.AutoservRunError, error.AutoservSSHTimeout):
206 return False
Laurence Goodby468de252017-06-08 17:22:53 -0700207
208 return False
beeps46dadc92013-11-07 14:07:10 -0800209
210
211 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800212 def get_chameleon_arguments(args_dict):
213 """Extract chameleon options from `args_dict` and return the result.
214
215 Recommended usage:
216 ~~~~~~~~
217 args_dict = utils.args_to_dict(args)
218 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
219 host = hosts.create_host(machine, chameleon_args=chameleon_args)
220 ~~~~~~~~
221
222 @param args_dict Dictionary from which to extract the chameleon
223 arguments.
224 """
Sam McNally66594ca2019-12-09 12:45:44 +1100225 chameleon_args = {key: args_dict[key]
226 for key in ('chameleon_host', 'chameleon_port')
227 if key in args_dict}
228 if 'chameleon_ssh_port' in args_dict:
229 chameleon_args['port'] = int(args_dict['chameleon_ssh_port'])
230 return chameleon_args
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800231
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800232 @staticmethod
233 def get_btpeer_arguments(args_dict):
234 """Extract btpeer options from `args_dict` and return the result.
235
236 This is used to parse details of Bluetooth peer.
237 Recommended usage:
238 ~~~~~~~~
239 args_dict = utils.args_to_dict(args)
240 btpeer_args = hosts.CrosHost.get_btpeer_arguments(args_dict)
Shijin Abraham22f24cb2020-03-26 09:07:41 -0700241 host = hosts.create_host(machine, btpeer_args=btpeer_args)
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800242 ~~~~~~~~
243
244 @param args_dict: Dictionary from which to extract the btpeer
245 arguments.
246 """
247 if 'btpeer_host_list' in args_dict:
248 result = []
249 for btpeer in args_dict['btpeer_host_list'].split(','):
Claire Changd0b19842020-11-04 22:28:45 +0800250 # IPv6 addresses including a port number should be enclosed in
251 # square brackets.
252 delimiter = ']:' if re.search(r':.*:', btpeer) else ':'
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800253 result.append({key: value for key,value in
254 zip(('btpeer_host','btpeer_port'),
Claire Changd0b19842020-11-04 22:28:45 +0800255 btpeer.strip('[]').split(delimiter))})
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800256 return result
257 else:
Anand K Mistrye8933092020-08-05 14:49:41 +1000258 return {key: args_dict[key]
259 for key in ('btpeer_host', 'btpeer_port', 'btpeer_ssh_port')
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800260 if key in args_dict}
261
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800262
263 @staticmethod
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -0700264 def get_pdtester_arguments(args_dict):
Scottfe06ed82015-11-05 17:15:01 -0800265 """Extract chameleon options from `args_dict` and return the result.
266
267 Recommended usage:
268 ~~~~~~~~
269 args_dict = utils.args_to_dict(args)
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -0700270 pdtester_args = hosts.CrosHost.get_pdtester_arguments(args_dict)
271 host = hosts.create_host(machine, pdtester_args=pdtester_args)
Scottfe06ed82015-11-05 17:15:01 -0800272 ~~~~~~~~
273
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -0700274 @param args_dict Dictionary from which to extract the pdtester
Scottfe06ed82015-11-05 17:15:01 -0800275 arguments.
276 """
Allen Li083866b2016-08-18 10:07:10 -0700277 return {key: args_dict[key]
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -0700278 for key in ('pdtester_host', 'pdtester_port')
Allen Li083866b2016-08-18 10:07:10 -0700279 if key in args_dict}
Scottfe06ed82015-11-05 17:15:01 -0800280
281
282 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800283 def get_servo_arguments(args_dict):
284 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800285
286 Recommended usage:
287 ~~~~~~~~
288 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700289 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800290 host = hosts.create_host(machine, servo_args=servo_args)
291 ~~~~~~~~
292
293 @param args_dict Dictionary from which to extract the servo
294 arguments.
295 """
Garry Wang11b5e872020-03-11 15:14:08 -0700296 servo_attrs = (servo_constants.SERVO_HOST_ATTR,
297 servo_constants.SERVO_PORT_ATTR,
Otabek Kasimov382c3bb2020-10-28 13:22:45 -0700298 servo_constants.SERVO_SERIAL_ATTR,
Garry Wang11b5e872020-03-11 15:14:08 -0700299 servo_constants.SERVO_BOARD_ATTR,
300 servo_constants.SERVO_MODEL_ATTR)
Armando Miraglia2ac802e2017-08-15 14:54:47 +0200301 servo_args = {key: args_dict[key]
302 for key in servo_attrs
303 if key in args_dict}
304 return (
305 None
Garry Wang11b5e872020-03-11 15:14:08 -0700306 if servo_constants.SERVO_HOST_ATTR in servo_args
307 and not servo_args[servo_constants.SERVO_HOST_ATTR]
Armando Miraglia2ac802e2017-08-15 14:54:47 +0200308 else servo_args)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700309
J. Richard Barnette964fba02012-10-24 17:34:29 -0700310
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800311 def _initialize(self,
312 hostname,
313 chameleon_args=None,
314 servo_args=None,
315 pdtester_args=None,
316 try_lab_servo=False,
317 try_servo_repair=False,
318 ssh_verbosity_flag='',
319 ssh_options='',
320 try_servo_recovery=False,
321 *args,
322 **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800323 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700324
Fang Denge545abb2014-12-30 18:43:47 -0800325 This method will attempt to create the test-assistant object
326 (chameleon/servo) when it is needed by the test. Check
327 the docstring of chameleon_host.create_chameleon_host and
328 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700329
Fang Denge545abb2014-12-30 18:43:47 -0800330 @param hostname: Hostname of the dut.
331 @param chameleon_args: A dictionary that contains args for creating
332 a ChameleonHost. See chameleon_host for details.
333 @param servo_args: A dictionary that contains args for creating
334 a ServoHost object. See servo_host for details.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700335 @param try_lab_servo: When true, indicates that an attempt should
336 be made to create a ServoHost for a DUT in
337 the test lab, even if not required by
338 `servo_args`. See servo_host for details.
339 @param try_servo_repair: If a servo host is created, check it
340 with `repair()` rather than `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800341 See servo_host for details.
342 @param ssh_verbosity_flag: String, to pass to the ssh command to control
343 verbosity.
344 @param ssh_options: String, other ssh options to pass to the ssh
345 command.
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800346 @param try_servo_recovery: When True, start servod in recovery mode.
347 See servo_host for details.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700348 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700349 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700350 *args, **dargs)
J. Richard Barnette91137f02016-03-10 16:52:26 -0800351 self._repair_strategy = cros_repair.create_cros_repair_strategy()
Otabek Kasimov6825b762020-06-23 23:42:44 -0700352 # hold special dut_state for repair process
353 self._device_repair_state = None
Kevin Chenga2619dc2016-03-28 11:42:08 -0700354 self.labels = base_label.LabelRetriever(cros_label.CROS_LABELS)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700355 # self.env is a dictionary of environment variable settings
356 # to be exported for commands run on the host.
357 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
358 # errors that might happen.
359 self.env['LIBC_FATAL_STDERR_'] = '1'
Fang Dengd1c2b732013-08-20 12:59:46 -0700360 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700361 self._ssh_options = ssh_options
Garry Wang1a493d82020-08-31 21:01:19 -0700362 self.health_profile = None
Garry Wang5e5538a2019-04-08 15:36:18 -0700363 self._default_power_method = None
Otabek Kasimov39637412020-11-23 19:09:27 -0800364 dut_health_profile = device_health_profile.DeviceHealthProfile(
365 hostname=self.hostname,
366 host_info=self.host_info_store.get(),
367 result_dir=self.get_result_dir())
Otabek Kasimovb61729d2020-11-24 00:42:43 -0800368
369 # TODO(otabek@): remove when b/171414073 closed
Otabek Kasimov7587b902020-12-07 23:54:33 -0800370 pingable_before_servo = self.is_up_fast(count=3)
Otabek Kasimovb61729d2020-11-24 00:42:43 -0800371 if pingable_before_servo:
372 logging.info('DUT is pingable before init Servo.')
Otabek Kasimov39637412020-11-23 19:09:27 -0800373 _servo_host, servo_state = servo_host.create_servo_host(
374 dut=self,
375 servo_args=servo_args,
376 try_lab_servo=try_lab_servo,
377 try_servo_repair=try_servo_repair,
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -0800378 try_servo_recovery=try_servo_recovery,
Otabek Kasimov39637412020-11-23 19:09:27 -0800379 dut_host_info=self.host_info_store.get(),
380 dut_health_profile=dut_health_profile)
381 if dut_health_profile.is_loaded():
382 logging.info('Device health profile loaded.')
383 # The device profile is located in the servo_host which make it
384 # dependency. If profile is not loaded yet then we do not have it
385 # TODO(otabek@) persist device provide out of servo-host.
386 self.health_profile = dut_health_profile
387 self.set_servo_host(_servo_host, servo_state)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700388
Otabek Kasimovb61729d2020-11-24 00:42:43 -0800389 # TODO(otabek@): remove when b/171414073 closed
390 # Introduced to collect cases when servo made DUT not sshable
Otabek Kasimov7587b902020-12-07 23:54:33 -0800391 pingable_after_servo = self.is_up_fast(count=3)
Otabek Kasimovb61729d2020-11-24 00:42:43 -0800392 if pingable_after_servo:
393 logging.info('DUT is pingable after init Servo.')
394 elif pingable_before_servo:
395 logging.info('DUT was pingable before init Servo but not now')
Otabek Kasimov7587b902020-12-07 23:54:33 -0800396 if servo_args and self._servo_host and self._servo_host.hostname:
397 # collect stats only for tests.
398 dut_ping_servo_init_data = {
399 'host': self.hostname,
400 'servo_host': self._servo_host.hostname,
401 }
402 metrics.Counter('chromeos/autotest/dut_ping_servo_init2'
403 ).increment(fields=dut_ping_servo_init_data)
Otabek Kasimovb61729d2020-11-24 00:42:43 -0800404
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800405 # TODO(waihong): Do the simplication on Chameleon too.
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800406 self._chameleon_host = chameleon_host.create_chameleon_host(
Otabek Kasimova7ba91a2020-03-09 08:31:01 -0700407 dut=self.hostname,
408 chameleon_args=chameleon_args)
Shijin Abraham783a7dd2020-02-14 15:36:11 -0800409 if self._chameleon_host:
410 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800411 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800412 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700413
Shijin Abraham78ce4402020-09-08 22:04:27 -0700414 # Bluetooth peers will be populated by the test if needed
415 self._btpeer_host_list = []
416 self.btpeer_list = []
417 self.btpeer = None
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800418
howardchung83e55272019-08-08 14:08:05 +0800419 # Add pdtester host if pdtester args were added on command line
Wai-Hong Tam16e5edb2019-09-17 16:10:07 -0700420 self._pdtester_host = pdtester_host.create_pdtester_host(
Wai-Hong Tam90b164d2019-10-25 13:15:39 -0700421 pdtester_args, self._servo_host)
howardchung83e55272019-08-08 14:08:05 +0800422
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -0700423 if self._pdtester_host:
424 self.pdtester_servo = self._pdtester_host.get_servo()
425 logging.info('pdtester_servo: %r', self.pdtester_servo)
426 # Create the pdtester object used to access the ec uart
427 self.pdtester = pdtester.PDTester(self.pdtester_servo,
428 self._pdtester_host.get_servod_server_proxy())
Scottfe06ed82015-11-05 17:15:01 -0800429 else:
Wai-Hong Tam0ed9fe12019-05-23 11:00:58 -0700430 self.pdtester = None
Scottfe06ed82015-11-05 17:15:01 -0800431
Fang Deng5d518f42013-08-02 14:04:32 -0700432
Shijin Abraham78ce4402020-09-08 22:04:27 -0700433 def initialize_btpeer(self, btpeer_args=[]):
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800434 """ Initialize the Bluetooth peers
435
436 Initialize Bluetooth peer devices given in the arguments. Bluetooth peer
437 is chameleon host on Raspberry Pi.
438 @param btpeer_args: A dictionary that contains args for creating
439 a ChameleonHost. See chameleon_host for details.
440
441 """
Shijin Abraham78ce4402020-09-08 22:04:27 -0700442 logging.debug('Attempting to initialize bluetooth peers if available')
Shijin Abraham22f24cb2020-03-26 09:07:41 -0700443 try:
Shijin Abraham22f24cb2020-03-26 09:07:41 -0700444 if type(btpeer_args) is list:
445 btpeer_args_list = btpeer_args
446 else:
447 btpeer_args_list = [btpeer_args]
448
449 self._btpeer_host_list = chameleon_host.create_btpeer_host(
450 dut=self.hostname, btpeer_args_list=btpeer_args_list)
451 logging.debug('Bluetooth peer hosts are %s',
452 self._btpeer_host_list)
453 self.btpeer_list = [_host.create_chameleon_board() for _host in
454 self._btpeer_host_list if _host is not None]
455
456 if len(self.btpeer_list) > 0:
457 self.btpeer = self.btpeer_list[0]
458
459 logging.debug('After initialize_btpeer btpeer_list %s '
460 'btpeer_host_list is %s and btpeer is %s',
461 self.btpeer_list, self._btpeer_host_list,
462 self.btpeer)
463 except Exception as e:
464 logging.error('Exception %s in initialize_btpeer', str(e))
465
Shijin Abrahamc09587d2020-02-14 20:46:55 -0800466
467
Richard Barnetteb4b4d6f2018-05-05 01:47:41 +0000468 def get_cros_repair_image_name(self):
Ruben Rodriguez Buchilloncee72f72019-05-01 13:20:58 -0700469 """Get latest stable cros image name from AFE.
470
471 Use the board name from the info store. Should that fail, try to
472 retrieve the board name from the host's installed image itself.
473
474 @returns: current stable cros image name for this host.
475 """
Garry Wange8a8fc22020-04-13 15:04:53 -0700476 info = self.host_info_store.get()
477 if not info.board:
Ruben Rodriguez Buchilloncee72f72019-05-01 13:20:58 -0700478 logging.warn('No board label value found. Trying to infer '
479 'from the host itself.')
480 try:
Garry Wange8a8fc22020-04-13 15:04:53 -0700481 info.labels.append(self.get_board())
Ruben Rodriguez Buchilloncee72f72019-05-01 13:20:58 -0700482 except (error.AutoservRunError, error.AutoservSSHTimeout) as e:
483 logging.error('Also failed to get the board name from the DUT '
484 'itself. %s.', str(e))
Garry Wange8a8fc22020-04-13 15:04:53 -0700485 raise error.AutoservError('Cannot determine board of the DUT'
486 ' while getting repair image name.')
487 return afe_utils.get_stable_cros_image_name_v2(info)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500488
489
Rohit Makasanadf0a3a32017-06-30 13:55:18 -0700490 def host_version_prefix(self, image):
491 """Return version label prefix.
492
493 In case the CrOS provisioning version is something other than the
494 standard CrOS version e.g. CrOS TH version, this function will
495 find the prefix from provision.py.
496
497 @param image: The image name to find its version prefix.
498 @returns: A prefix string for the image type.
499 """
500 return provision.get_version_label_prefix(image)
501
Andrew Luo3332ab22020-04-28 16:42:03 -0700502 def stage_build_to_usb(self, build):
503 """Stage the current ChromeOS image on the USB stick connected to the
504 servo.
505
506 @param build: The build to download and send to USB.
507 """
508 if not self.servo:
509 raise error.TestError('Host %s does not have servo.' %
510 self.hostname)
511
512 _, update_url = self.stage_image_for_servo(build)
Andrew Luob0355ea2020-06-24 16:12:57 -0700513
514 try:
515 self.servo.image_to_servo_usb(update_url)
516 finally:
517 # servo.image_to_servo_usb turned the DUT off, so turn it back on
518 logging.debug('Turn DUT power back on.')
519 self.servo.get_power_state_controller().power_on()
520
Andrew Luo3332ab22020-04-28 16:42:03 -0700521 logging.debug('ChromeOS image %s is staged on the USB stick.',
522 build)
Rohit Makasanadf0a3a32017-06-30 13:55:18 -0700523
beepsdae65fd2013-07-26 16:24:41 -0700524 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700525 """
526 Make sure job_repo_url of this host is valid.
527
joychen03eaad92013-06-26 09:55:21 -0700528 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700529 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
530 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
531 download and extract it. If the devserver embedded in the url is
532 unresponsive, update the job_repo_url of the host after staging it on
533 another devserver.
534
535 @param job_repo_url: A url pointing to the devserver where the autotest
536 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700537 @param tag: The tag from the server job, in the format
538 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700539
540 @raises DevServerException: If we could not resolve a devserver.
541 @raises AutoservError: If we're unable to save the new job_repo_url as
542 a result of choosing a new devserver because the old one failed to
543 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700544 @raises urllib2.URLError: If the devserver embedded in job_repo_url
545 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700546 """
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800547 info = self.host_info_store.get()
548 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
beepscb6f1e22013-06-28 19:14:10 -0700549 if not job_repo_url:
550 logging.warning('No job repo url set on host %s', self.hostname)
551 return
552
553 logging.info('Verifying job repo url %s', job_repo_url)
554 devserver_url, image_name = tools.get_devserver_build_from_package_url(
555 job_repo_url)
556
beeps0c865032013-07-30 11:37:06 -0700557 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700558
559 logging.info('Staging autotest artifacts for %s on devserver %s',
560 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700561
562 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700563 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700564 stage_time = time.time() - start_time
565
566 # Record how much of the verification time comes from a devserver
567 # restage. If we're doing things right we should not see multiple
568 # devservers for a given board/build/branch path.
569 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800570 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700571 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800572 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700573 pass
574 else:
beeps0c865032013-07-30 11:37:06 -0700575 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700576 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700577 stats_key = {
578 'board': board,
579 'build_type': build_type,
580 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700581 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700582 }
Dan Shi5e2efb72017-02-07 11:40:23 -0800583
584 monarch_fields = {
585 'board': board,
586 'build_type': build_type,
Dan Shi5e2efb72017-02-07 11:40:23 -0800587 'branch': branch,
588 'dev_server': devserver,
589 }
590 metrics.Counter(
591 'chromeos/autotest/provision/verify_url'
592 ).increment(fields=monarch_fields)
593 metrics.SecondsDistribution(
594 'chromeos/autotest/provision/verify_url_duration'
595 ).add(stage_time, fields=monarch_fields)
596
597
Dan Shicf4d2032015-03-12 15:04:21 -0700598 def stage_server_side_package(self, image=None):
599 """Stage autotest server-side package on devserver.
600
601 @param image: Full path of an OS image to install or a build name.
602
603 @return: A url to the autotest server-side package.
Dan Shi14de7622016-08-22 11:09:06 -0700604
605 @raise: error.AutoservError if fail to locate the build to test with, or
606 fail to stage server-side package.
Dan Shicf4d2032015-03-12 15:04:21 -0700607 """
Dan Shid37736b2016-07-06 15:10:29 -0700608 # If enable_drone_in_restricted_subnet is False, do not set hostname
609 # in devserver.resolve call, so a devserver in non-restricted subnet
610 # is picked to stage autotest server package for drone to download.
611 hostname = self.hostname
612 if not server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
613 hostname = None
Dan Shicf4d2032015-03-12 15:04:21 -0700614 if image:
615 image_name = tools.get_build_from_image(image)
616 if not image_name:
617 raise error.AutoservError(
618 'Failed to parse build name from %s' % image)
Dan Shid37736b2016-07-06 15:10:29 -0700619 ds = dev_server.ImageServer.resolve(image_name, hostname)
Dan Shicf4d2032015-03-12 15:04:21 -0700620 else:
Prathmesh Prabhu9235e4c2017-03-28 13:16:06 -0700621 info = self.host_info_store.get()
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800622 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
Dan Shicf4d2032015-03-12 15:04:21 -0700623 if job_repo_url:
624 devserver_url, image_name = (
625 tools.get_devserver_build_from_package_url(job_repo_url))
Dan Shid37736b2016-07-06 15:10:29 -0700626 # If enable_drone_in_restricted_subnet is True, use the
627 # existing devserver. Otherwise, resolve a new one in
628 # non-restricted subnet.
629 if server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
630 ds = dev_server.ImageServer(devserver_url)
631 else:
632 ds = dev_server.ImageServer.resolve(image_name)
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800633 elif info.build is not None:
634 ds = dev_server.ImageServer.resolve(info.build, hostname)
Prathmesh Prabhu0c1dd4d2017-06-07 13:01:53 -0700635 image_name = info.build
Dan Shicf4d2032015-03-12 15:04:21 -0700636 else:
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800637 raise error.AutoservError(
638 'Failed to stage server-side package. The host has '
Garry Wang12b9baf2019-06-24 18:58:54 -0700639 'no job_repo_url attribute or cros-version label.')
Dan Shica503482015-03-30 17:23:25 -0700640
641 # Get the OS version of the build, for any build older than
642 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
643 match = re.match('.*/R\d+-(\d+)\.', image_name)
644 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
Dan Shi14de7622016-08-22 11:09:06 -0700645 raise error.AutoservError(
646 'Build %s is older than %s. Server side packaging is '
647 'disabled.' % (image_name, self.MIN_VERSION_SUPPORT_SSP))
Dan Shica503482015-03-30 17:23:25 -0700648
Dan Shicf4d2032015-03-12 15:04:21 -0700649 ds.stage_artifacts(image_name, ['autotest_server_package'])
650 return '%s/static/%s/%s' % (ds.url(), image_name,
651 'autotest_server_package.tar.bz2')
652
653
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700654 def stage_image_for_servo(self, image_name=None, artifact='test_image'):
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700655 """Stage a build on a devserver and return the update_url.
656
657 @param image_name: a name like lumpy-release/R27-3837.0.0
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700658 @param artifact: a string like 'test_image'. Requests
659 appropriate image to be staged.
Xixuan Wufee57542019-10-15 11:50:27 -0700660 @returns a tuple of (image_name, URL) like
661 (lumpy-release/R27-3837.0.0,
662 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700663 """
664 if not image_name:
Richard Barnetteb4b4d6f2018-05-05 01:47:41 +0000665 image_name = self.get_cros_repair_image_name()
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700666 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800667 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700668 devserver.stage_artifacts(image_name, [artifact])
669 if artifact == 'test_image':
Xixuan Wufee57542019-10-15 11:50:27 -0700670 return image_name, devserver.get_test_image_url(image_name)
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700671 elif artifact == 'recovery_image':
Xixuan Wufee57542019-10-15 11:50:27 -0700672 return image_name, devserver.get_recovery_image_url(image_name)
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700673 else:
674 raise error.AutoservError("Bad artifact!")
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700675
676
beepse539be02013-07-31 21:57:39 -0700677 def stage_factory_image_for_servo(self, image_name):
678 """Stage a build on a devserver and return the update_url.
679
680 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700681
beepse539be02013-07-31 21:57:39 -0700682 @return: An update URL, eg:
683 http://<devserver>/static/canary-channel/\
684 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700685
686 @raises: ValueError if the factory artifact name is missing from
687 the config.
688
beepse539be02013-07-31 21:57:39 -0700689 """
690 if not image_name:
691 logging.error('Need an image_name to stage a factory image.')
692 return
693
Dan Shib8540a52015-07-16 14:18:23 -0700694 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700695 'CROS', 'factory_artifact', type=str, default='')
696 if not factory_artifact:
697 raise ValueError('Cannot retrieve the factory artifact name from '
698 'autotest config, and hence cannot stage factory '
699 'artifacts.')
700
beepse539be02013-07-31 21:57:39 -0700701 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800702 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
beepse539be02013-07-31 21:57:39 -0700703 devserver.stage_artifacts(
704 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700705 [factory_artifact],
706 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700707
708 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
709
710
Laurence Goodby778c9a42017-05-24 19:24:07 -0700711 def prepare_for_update(self):
712 """Prepares the DUT for an update.
713
714 Subclasses may override this to perform any special actions
715 required before updating.
716 """
Laurence Goodby468de252017-06-08 17:22:53 -0700717 pass
Laurence Goodby778c9a42017-05-24 19:24:07 -0700718
719
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800720 def _clear_fw_version_labels(self, rw_only):
721 """Clear firmware version labels from the machine.
722
723 @param rw_only: True to only clear fwrw_version; otherewise, clear
724 both fwro_version and fwrw_version.
725 """
Prathmesh Prabhuf8ae9a82019-10-04 15:34:13 -0700726 info = self.host_info_store.get()
727 info.clear_version_labels(provision.FW_RW_VERSION_PREFIX)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800728 if not rw_only:
Prathmesh Prabhuf8ae9a82019-10-04 15:34:13 -0700729 info.clear_version_labels(provision.FW_RO_VERSION_PREFIX)
730 self.host_info_store.commit(info)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700731
732
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800733 def _add_fw_version_label(self, build, rw_only):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700734 """Add firmware version label to the machine.
735
736 @param build: Build of firmware.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800737 @param rw_only: True to only add fwrw_version; otherwise, add both
738 fwro_version and fwrw_version.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700739
740 """
Prathmesh Prabhuf8ae9a82019-10-04 15:34:13 -0700741 info = self.host_info_store.get()
742 info.set_version_label(provision.FW_RW_VERSION_PREFIX, build)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800743 if not rw_only:
Prathmesh Prabhuf8ae9a82019-10-04 15:34:13 -0700744 info.set_version_label(provision.FW_RO_VERSION_PREFIX, build)
745 self.host_info_store.commit(info)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700746
747
Namyoon Woo33f38852020-04-13 17:26:58 -0700748 def get_latest_release_version(self, platform, ref_board=None):
Namyoon Woo5f894662019-11-15 15:23:23 -0800749 """Search for the latest package release version from the image archive,
750 and return it.
751
Namyoon Woo33f38852020-04-13 17:26:58 -0700752 @param platform: platform name, a.k.a. board or model
753 @param ref_board: reference board name, a.k.a. baseboard, parent
Namyoon Woo5f894662019-11-15 15:23:23 -0800754
Namyoon Woo33f38852020-04-13 17:26:58 -0700755 @return 'firmware-{platform}-{branch}-firmwarebranch/{release-version}/'
756 '{platform}'
Namyoon Woo5f894662019-11-15 15:23:23 -0800757 or None if LATEST release file does not exist.
758 """
759
Namyoon Woo33f38852020-04-13 17:26:58 -0700760 platforms = [ platform ]
Namyoon Woo5f894662019-11-15 15:23:23 -0800761
Namyoon Woo33f38852020-04-13 17:26:58 -0700762 # Search the image path in reference board archive as well.
763 # For example, bob has its binary image under its reference board (gru)
764 # image archive.
765 if ref_board:
766 platforms.append(ref_board)
Namyoon Woo5f894662019-11-15 15:23:23 -0800767
Namyoon Woo33f38852020-04-13 17:26:58 -0700768 for board in platforms:
769 # Read 'LATEST-1.0.0' file
770 branch_dir = provision.FW_BRANCH_GLOB % board
771 latest_file = os.path.join(provision.CROS_IMAGE_ARCHIVE, branch_dir,
772 'LATEST-1.0.0')
Namyoon Woo406c7d42020-01-24 15:57:11 -0800773
Namyoon Woo33f38852020-04-13 17:26:58 -0700774 try:
775 # The result could be one or more.
776 result = utils.system_output('gsutil ls -d ' + latest_file)
777
778 candidates = re.findall('gs://.*', result)
779
780 # Found the directory candidates. No need to check the other
781 # board name cadidates. Let's break the loop.
782 break
783 except error.CmdError:
784 # It doesn't exist. Let's move on to the next item.
785 pass
786 else:
Namyoon Woo5f894662019-11-15 15:23:23 -0800787 logging.error('No LATEST release info is available.')
788 return None
789
Namyoon Woo406c7d42020-01-24 15:57:11 -0800790 for cand_dir in candidates:
791 result = utils.system_output('gsutil cat ' + cand_dir)
Namyoon Woo5f894662019-11-15 15:23:23 -0800792
Namyoon Woo406c7d42020-01-24 15:57:11 -0800793 release_path = cand_dir.replace('LATEST-1.0.0', result)
Namyoon Woo33f38852020-04-13 17:26:58 -0700794 release_path = os.path.join(release_path, platform)
Namyoon Woo406c7d42020-01-24 15:57:11 -0800795 try:
796 # Check if release_path does exist.
797 release = utils.system_output('gsutil ls -d ' + release_path)
798 # Now 'release' has a full directory path: e.g.
799 # gs://chromeos-image-archive/firmware-octopus-11297.B-
800 # firmwarebranch/RNone-1.0.0-b4395530/octopus/
801
802 # Remove "gs://chromeos-image-archive".
803 release = release.replace(provision.CROS_IMAGE_ARCHIVE, '')
804
805 # Remove CROS_IMAGE_ARCHIVE and any surrounding '/'s.
806 return release.strip('/')
807 except error.CmdError:
808 # The directory might not exist. Let's try next candidate.
809 pass
810 else:
811 raise error.AutoservError('Cannot find the latest firmware')
Namyoon Woo5f894662019-11-15 15:23:23 -0800812
Brent Peterson1cb623a2020-01-09 13:14:28 -0800813 @staticmethod
814 def get_version_from_image(image, version_regex):
Brent Peterson8039b472020-02-14 10:51:23 -0800815 """Get version string from binary image using regular expression.
816
817 @param image: Binary image to search
818 @param version_regex: Regular expression to search for
819
820 @return Version string
821
822 @raises TestFail if no version string is found in image
823 """
Brent Peterson1cb623a2020-01-09 13:14:28 -0800824 with open(image, 'rb') as f:
825 image_data = f.read()
Derek Beckett98345552020-08-31 16:07:22 -0700826 match = re.findall(version_regex,
827 image_data.decode('ISO-8859-1', errors='ignore'))
Brent Peterson1cb623a2020-01-09 13:14:28 -0800828 if match:
829 return match[0]
830 else:
831 raise error.TestFail('Failed to read version from %s.' % image)
832
833
Garry Wangad2a1712020-03-26 15:06:43 -0700834 def firmware_install(self, build, rw_only=False, dest=None,
Brent Petersonc70a1832020-01-24 15:54:35 -0800835 local_tarball=None, verify_version=False,
Namyoon Woo382e5892020-05-20 16:48:40 -0700836 try_scp=False, install_ec=True, install_bios=True,
837 board_as=None):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700838 """Install firmware to the DUT.
839
840 Use stateful update if the DUT is already running the same build.
841 Stateful update does not update kernel and tends to run much faster
842 than a full reimage. If the DUT is running a different build, or it
843 failed to do a stateful update, full update, including kernel update,
844 will be applied to the DUT.
845
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800846 Once a host enters firmware_install its fw[ro|rw]_version label will
847 be removed. After the firmware is updated successfully, a new
848 fw[ro|rw]_version label will be added to the host.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700849
850 @param build: The build version to which we want to provision the
851 firmware of the machine,
852 e.g. 'link-firmware/R22-2695.1.144'.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800853 @param rw_only: True to only install firmware to its RW portions. Keep
854 the RO portions unchanged.
Mary Ruthven6481a9f2019-08-23 12:46:05 -0700855 @param dest: Directory to store the firmware in.
Brent Peterson5b7c5b12020-01-09 13:14:28 -0800856 @param local_tarball: Path to local firmware image for installing
857 without devserver.
Brent Peterson1cb623a2020-01-09 13:14:28 -0800858 @param verify_version: True to verify EC and BIOS versions after
859 programming firmware, default is False.
Brent Petersonc70a1832020-01-24 15:54:35 -0800860 @param try_scp: False to always program using servo, true to try copying
861 the firmware and programming from the DUT.
Namyoon Woo382e5892020-05-20 16:48:40 -0700862 @param install_ec: True to install EC FW, and False to skip it.
863 @param install_bios: True to install BIOS, and False to skip it.
864 @param board_as: A board name to force to use.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700865
866 TODO(dshi): After bug 381718 is fixed, update here with corresponding
867 exceptions that could be raised.
868
869 """
870 if not self.servo:
871 raise error.TestError('Host %s does not have servo.' %
872 self.hostname)
873
Wai-Hong Tam3fa455a2018-07-18 14:40:43 -0700874 # Get the DUT board name from AFE.
875 info = self.host_info_store.get()
876 board = info.board
Shelley Chenac61d5a2019-06-24 15:35:46 -0700877 model = info.model
Namyoon Woo8dbfcf92019-01-15 18:37:12 -0800878
879 if board is None or board == '':
880 board = self.servo.get_board()
Dan Shi9cb0eec2014-06-03 09:04:50 -0700881
Namyoon Woo382e5892020-05-20 16:48:40 -0700882 # if board_as argument is passed, then use it instead of the original
883 # board name.
884 if board_as:
885 board = board_as
886
Mary Ruthvende14a8b2019-08-23 12:43:52 -0700887 if model is None or model == '':
Namyoon Woofb16eae2020-08-14 10:02:39 -0700888 try:
Mary Ruthven9c15a4e2020-10-23 10:10:53 -0700889 model = self.get_platform()
Namyoon Woofb16eae2020-08-14 10:02:39 -0700890 except Exception as e:
891 logging.warn('Dut is unresponsive: %s', str(e))
Mary Ruthvende14a8b2019-08-23 12:43:52 -0700892
Brent Peterson5b7c5b12020-01-09 13:14:28 -0800893 # If local firmware path not provided fetch it from the dev server
Mary Ruthven6481a9f2019-08-23 12:46:05 -0700894 tmpd = None
Brent Peterson5b7c5b12020-01-09 13:14:28 -0800895 if not local_tarball:
Garry Wangad2a1712020-03-26 15:06:43 -0700896 logging.info('Will install firmware from build %s.', build)
Brent Peterson5b7c5b12020-01-09 13:14:28 -0800897
Namyoon Woo43c1cb22020-05-28 18:58:34 -0700898 try:
899 ds = dev_server.ImageServer.resolve(build, self.hostname)
900 ds.stage_artifacts(build, ['firmware'])
Brent Peterson5b7c5b12020-01-09 13:14:28 -0800901
Namyoon Woo43c1cb22020-05-28 18:58:34 -0700902 if not dest:
903 tmpd = autotemp.tempdir(unique_id='fwimage')
904 dest = tmpd.name
Brent Peterson5b7c5b12020-01-09 13:14:28 -0800905
Namyoon Woo43c1cb22020-05-28 18:58:34 -0700906 # Download firmware image
907 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
908 local_tarball = os.path.join(dest, os.path.basename(fwurl))
909 ds.download_file(fwurl, local_tarball)
910 except Exception as e:
911 raise error.TestError('Failed to download firmware package: %s'
912 % str(e))
Dan Shi9cb0eec2014-06-03 09:04:50 -0700913
Namyoon Woo382e5892020-05-20 16:48:40 -0700914 ec_image = None
915 if install_ec:
916 # Extract EC image from tarball
917 logging.info('Extracting EC image.')
918 ec_image = self.servo.extract_ec_image(board, model, local_tarball)
Mary Ruthven9c15a4e2020-10-23 10:10:53 -0700919 logging.info('Extracted: %s', ec_image)
Brent Peterson1cb623a2020-01-09 13:14:28 -0800920
Namyoon Woo382e5892020-05-20 16:48:40 -0700921 bios_image = None
922 if install_bios:
923 # Extract BIOS image from tarball
924 logging.info('Extracting BIOS image.')
925 bios_image = self.servo.extract_bios_image(board, model,
926 local_tarball)
Mary Ruthven9c15a4e2020-10-23 10:10:53 -0700927 logging.info('Extracted: %s', bios_image)
Namyoon Woo382e5892020-05-20 16:48:40 -0700928
929 if not bios_image and not ec_image:
930 raise error.TestError('No firmware installation was processed.')
Brent Peterson1cb623a2020-01-09 13:14:28 -0800931
Brent Petersonc70a1832020-01-24 15:54:35 -0800932 # Clear firmware version labels
933 self._clear_fw_version_labels(rw_only)
934
935 # Install firmware from local tarball
Brent Peterson5b7c5b12020-01-09 13:14:28 -0800936 try:
Garry Wang50e4a492020-08-05 12:29:57 -0700937 # Check if copying to DUT is enabled and DUT is available
938 if try_scp and self.is_up():
Brent Petersonc70a1832020-01-24 15:54:35 -0800939 # DUT is available, make temp firmware directory to store images
940 logging.info('Making temp folder.')
941 dest_folder = '/tmp/firmware'
942 self.run('mkdir -p ' + dest_folder)
943
Namyoon Woo68b68082020-06-02 13:13:14 -0700944 fw_cmd = self._FW_UPDATE_CMD % ('--wp=1' if rw_only else '')
Brent Petersonc70a1832020-01-24 15:54:35 -0800945
Namyoon Woo382e5892020-05-20 16:48:40 -0700946 if bios_image:
947 # Send BIOS firmware image to DUT
948 logging.info('Sending BIOS firmware.')
949 dest_bios_path = os.path.join(dest_folder,
950 os.path.basename(bios_image))
951 self.send_file(bios_image, dest_bios_path)
952
953 # Initialize firmware update command for BIOS image
954 fw_cmd += ' -i %s' % dest_bios_path
Brent Peterson669edf42020-02-07 15:07:54 -0800955
956 # Send EC firmware image to DUT when EC image was found
957 if ec_image:
958 logging.info('Sending EC firmware.')
959 dest_ec_path = os.path.join(dest_folder,
960 os.path.basename(ec_image))
961 self.send_file(ec_image, dest_ec_path)
962
963 # Add EC image to firmware update command
964 fw_cmd += ' -e %s' % dest_ec_path
965
Dana Goyettee84ef8d2020-07-09 11:55:09 -0700966 # Make sure command is allowed to finish even if ssh fails.
967 fw_cmd = "trap '' SIGHUP; %s" % fw_cmd
968
Brent Peterson669edf42020-02-07 15:07:54 -0800969 # Update firmware on DUT
970 logging.info('Updating firmware.')
Dana Goyettee84ef8d2020-07-09 11:55:09 -0700971 try:
Dana Goyette935b3fe2020-07-23 14:19:39 -0700972 self.run(fw_cmd, options="-o LogLevel=verbose")
Dana Goyettee84ef8d2020-07-09 11:55:09 -0700973 except error.AutoservRunError as e:
974 if e.result_obj.exit_status != 255:
975 raise
976 elif ec_image:
977 logging.warn("DUT network dropped during update"
978 " (often caused by EC resetting USB)")
979 else:
980 logging.error("DUT network dropped during update"
981 " (unexpected, since no EC image)")
982 raise
Brent Petersonc70a1832020-01-24 15:54:35 -0800983 else:
984 # Host is not available, program firmware using servo
Brent Peterson669edf42020-02-07 15:07:54 -0800985 if ec_image:
986 self.servo.program_ec(ec_image, rw_only)
Namyoon Woo382e5892020-05-20 16:48:40 -0700987 if bios_image:
988 self.servo.program_bios(bios_image, rw_only)
Brent Petersonc70a1832020-01-24 15:54:35 -0800989 if utils.host_is_in_lab_zone(self.hostname):
990 self._add_fw_version_label(build, rw_only)
Brent Peterson1cb623a2020-01-09 13:14:28 -0800991
992 # Reboot and wait for DUT after installing firmware
993 logging.info('Rebooting DUT.')
994 self.servo.get_power_state_controller().reset()
995 time.sleep(self.servo.BOOT_DELAY)
996 self.test_wait_for_boot()
997
998 # When enabled verify EC and BIOS firmware version after programming
999 if verify_version:
Brent Peterson669edf42020-02-07 15:07:54 -08001000 # Check programmed EC firmware when EC image was found
1001 if ec_image:
1002 logging.info('Checking EC firmware version.')
1003 dest_ec_version = self.get_ec_version()
Brent Peterson8039b472020-02-14 10:51:23 -08001004 ec_version_prefix = dest_ec_version.split('_', 1)[0]
1005 ec_regex = self._EC_REGEX % ec_version_prefix
Brent Peterson669edf42020-02-07 15:07:54 -08001006 image_ec_version = self.get_version_from_image(ec_image,
Brent Peterson8039b472020-02-14 10:51:23 -08001007 ec_regex)
Brent Peterson669edf42020-02-07 15:07:54 -08001008 if dest_ec_version != image_ec_version:
1009 raise error.TestFail(
Namyoon Wooe2c009a2020-07-22 10:09:03 -07001010 'Failed to update EC firmware, version %s '
1011 '(expected %s)' % (dest_ec_version,
1012 image_ec_version))
Brent Peterson1cb623a2020-01-09 13:14:28 -08001013
Namyoon Woo382e5892020-05-20 16:48:40 -07001014 if bios_image:
1015 # Check programmed BIOS firmware against expected version
1016 logging.info('Checking BIOS firmware version.')
1017 dest_bios_version = self.get_firmware_version()
1018 bios_version_prefix = dest_bios_version.split('.', 1)[0]
1019 bios_regex = self._BIOS_REGEX % bios_version_prefix
1020 image_bios_version = self.get_version_from_image(bios_image,
1021 bios_regex)
1022 if dest_bios_version != image_bios_version:
1023 raise error.TestFail(
Namyoon Wooe2c009a2020-07-22 10:09:03 -07001024 'Failed to update BIOS, version %s '
Namyoon Woo382e5892020-05-20 16:48:40 -07001025 '(expected %s)' % (dest_bios_version,
1026 image_bios_version))
Dan Shi9cb0eec2014-06-03 09:04:50 -07001027 finally:
Mary Ruthven6481a9f2019-08-23 12:46:05 -07001028 if tmpd:
1029 tmpd.clean()
Dan Shi9cb0eec2014-06-03 09:04:50 -07001030
1031
Garry Wang790953f2020-10-29 21:11:57 -07001032 def servo_install(self,
1033 image_url=None,
1034 usb_boot_timeout=USB_BOOT_TIMEOUT,
1035 install_timeout=INSTALL_TIMEOUT,
1036 is_repair=False):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001037 """
1038 Re-install the OS on the DUT by:
1039 1) installing a test image on a USB storage device attached to the Servo
1040 board,
Richard Barnette03a0c132012-11-05 12:40:35 -08001041 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001042 3) installing the image with chromeos-install.
1043
Scott Zawalski62bacae2013-03-05 10:40:32 -05001044 @param image_url: If specified use as the url to install on the DUT.
1045 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -07001046 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
1047 Factory images need a longer usb_boot_timeout than regular
1048 cros images.
1049 @param install_timeout: The timeout to use when installing the chromeos
1050 image. Factory images need a longer install_timeout.
Garry Wang790953f2020-10-29 21:11:57 -07001051 @param is_repair: Indicates if the method is called from a repair task.
Richard Barnette03a0c132012-11-05 12:40:35 -08001052
Scott Zawalski62bacae2013-03-05 10:40:32 -05001053 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -07001054
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001055 """
Garry Wang7b0e1b72020-03-25 19:08:59 -07001056 if image_url:
1057 logging.info('Downloading image to USB, then booting from it.'
1058 ' Usb boot timeout = %s', usb_boot_timeout)
1059 else:
1060 logging.info('Booting from USB directly. Usb boot timeout = %s',
1061 usb_boot_timeout)
1062
1063 metrics_field = {'download': bool(image_url)}
1064 metrics.Counter(
1065 'chromeos/autotest/provision/servo_install/download_image'
1066 ).increment(fields=metrics_field)
1067
Allen Li48a13fe2016-11-22 14:10:40 -08001068 with metrics.SecondsTimer(
1069 'chromeos/autotest/provision/servo_install/boot_duration'):
Otabek Kasimovb887a002020-12-29 02:54:38 -08001070 self.servo.get_power_state_controller().power_off()
Otabek Kasimovb7cb8422020-12-23 02:38:32 -08001071 try:
1072 self.servo.image_to_servo_usb(image_path=image_url,
1073 power_off_dut=False)
1074 except error.AutotestError as e:
1075 metrics.Counter('chromeos/autotest/repair/image_to_usb_error'
1076 ).increment(
1077 fields={'host': self.hostname or ''})
1078 six.reraise(error.AutotestError, str(e), sys.exc_info()[2])
1079 # Give the DUT some time to power_off if we skip
1080 # download image to usb. (crbug.com/982993)
1081 if not image_url:
1082 time.sleep(10)
Garry Wang53fc8f32020-09-18 13:30:08 -07001083 need_snk = self.require_snk_mode_in_recovery()
Otabek Kasimovb7cb8422020-12-23 02:38:32 -08001084 self.servo.boot_in_recovery_mode(snk_mode=need_snk)
Allen Li48a13fe2016-11-22 14:10:40 -08001085 if not self.wait_up(timeout=usb_boot_timeout):
Garry Wang53fc8f32020-09-18 13:30:08 -07001086 if need_snk:
1087 # Attempt to restore servo_v4 role to 'src' mode.
1088 self.servo.set_servo_v4_role('src')
Allen Li48a13fe2016-11-22 14:10:40 -08001089 raise hosts.AutoservRepairError(
1090 'DUT failed to boot from USB after %d seconds' %
Garry Wang9ced7aa2020-04-10 17:26:35 -07001091 usb_boot_timeout, 'failed_to_boot_pre_install')
Scott Zawalski62bacae2013-03-05 10:40:32 -05001092
Garry Wang2e347df2020-10-30 14:04:26 -07001093 # Make sure the DUT is boot from an external device.
1094 if not self.is_boot_from_external_device():
1095 raise hosts.AutoservRepairError(
1096 'DUT is expected to boot from an external device(e.g. '
1097 'a usb stick), however it seems still boot from an'
1098 ' internal storage.', 'boot_from_internal_storage')
1099
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001100 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
1101 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001102 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001103 try:
1104 self.run('chromeos-tpm-recovery')
1105 except error.AutoservRunError:
1106 logging.warn('chromeos-tpm-recovery is too old.')
1107
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001108
Allen Li48a13fe2016-11-22 14:10:40 -08001109 with metrics.SecondsTimer(
1110 'chromeos/autotest/provision/servo_install/install_duration'):
1111 logging.info('Installing image through chromeos-install.')
Garry Wang033a31e2020-04-10 17:20:49 -07001112 try:
1113 self.run('chromeos-install --yes',timeout=install_timeout)
1114 self.halt()
Otabek Kasimov808cd832020-05-28 18:27:46 -07001115 except Exception as e:
1116 storage_errors = [
1117 'No space left on device',
1118 'I/O error when trying to write primary GPT',
1119 'Input/output error while writing out',
1120 'cannot read GPT header',
Otabek Kasimov2b7e8302020-08-21 09:23:31 -07001121 'can not determine destination device',
1122 'wrong fs type',
1123 'bad superblock on',
Otabek Kasimov808cd832020-05-28 18:27:46 -07001124 ]
1125 has_error = [msg for msg in storage_errors if(msg in str(e))]
1126 if has_error:
1127 info = self.host_info_store.get()
1128 info.set_version_label(
1129 audit_const.DUT_STORAGE_STATE_PREFIX,
1130 audit_const.HW_STATE_NEED_REPLACEMENT)
1131 self.host_info_store.commit(info)
Otabek Kasimov6825b762020-06-23 23:42:44 -07001132 self.set_device_repair_state(
Otabek Kasimov832d9162020-07-27 19:24:57 -07001133 cros_constants.DEVICE_STATE_NEEDS_REPLACEMENT)
Otabek Kasimov808cd832020-05-28 18:27:46 -07001134 logging.debug(
1135 'Fail install image from USB; Storage error; %s', e)
1136 raise error.AutoservError(
1137 'Failed to install image from USB due to a suspect '
1138 'disk failure, DUT storage state changed to '
1139 'need_replacement, please check debug log '
1140 'for details.')
1141 else:
Garry Wang790953f2020-10-29 21:11:57 -07001142 if is_repair:
1143 # DUT will be marked for replacement if storage is bad.
1144 audit_verify.VerifyDutStorage(self).verify()
Otabek Kasimov27bb2862020-08-10 14:40:45 -07001145
Otabek Kasimov808cd832020-05-28 18:27:46 -07001146 logging.debug('Fail install image from USB; %s', e)
1147 raise error.AutoservError(
1148 'Failed to install image from USB due to unexpected '
1149 'error, please check debug log for details.')
Garry Wang033a31e2020-04-10 17:20:49 -07001150 finally:
1151 # We need reset the DUT no matter re-install success or not,
1152 # as we don't want leave the DUT in boot from usb state.
1153 logging.info('Power cycling DUT through servo.')
1154 self.servo.get_power_state_controller().power_off()
1155 self.servo.switch_usbkey('off')
Garry Wang53fc8f32020-09-18 13:30:08 -07001156 if need_snk:
1157 # Attempt to restore servo_v4 role to 'src' mode.
1158 self.servo.set_servo_v4_role('src')
Garry Wang033a31e2020-04-10 17:20:49 -07001159 # N.B. The Servo API requires that we use power_on() here
1160 # for two reasons:
1161 # 1) After turning on a DUT in recovery mode, you must turn
1162 # it off and then on with power_on() once more to
1163 # disable recovery mode (this is a Parrot specific
1164 # requirement).
1165 # 2) After power_off(), the only way to turn on is with
1166 # power_on() (this is a Storm specific requirement).
1167 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001168
1169 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001170 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
Garry Wang9ced7aa2020-04-10 17:26:35 -07001171 raise hosts.AutoservRepairError('DUT failed to reboot installed '
1172 'test image after %d seconds' %
1173 self.BOOT_TIMEOUT,
1174 'failed_to_boot_post_install')
Scott Zawalski62bacae2013-03-05 10:40:32 -05001175
1176
Garry Wanga2e78172020-09-09 23:49:07 -07001177 def set_servo_host(self, host, servo_state=None):
Richard Barnette4aeb01c2018-09-20 09:36:12 -07001178 """Set our servo host member, and associated servo.
1179
1180 @param host Our new `ServoHost`.
1181 """
1182 self._servo_host = host
Derek Beckettb66e5c82020-08-12 15:31:02 -07001183 self.servo_pwr_supported = None
Richard Barnette4aeb01c2018-09-20 09:36:12 -07001184 if self._servo_host is not None:
1185 self.servo = self._servo_host.get_servo()
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001186 servo_state = self._servo_host.get_servo_state()
Garry Wang000c6c02020-05-11 21:27:23 -07001187 self._set_smart_usbhub_label(self._servo_host.smart_usbhub)
Derek Beckettb66e5c82020-08-12 15:31:02 -07001188 try:
1189 self.servo_pwr_supported = self.servo.has_control('power_state')
1190 except Exception as e:
1191 logging.debug(
1192 "Could not get servo power state due to {}".format(e))
Gregory Nisbet93b23e22020-10-02 20:42:16 +00001193 else:
1194 self.servo = None
Derek Beckettb66e5c82020-08-12 15:31:02 -07001195 self.servo_pwr_supported = False
Otabek Kasimov41301a22020-05-10 15:28:21 -07001196 self.set_servo_type()
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001197 self.set_servo_state(servo_state)
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07001198 self._set_servo_topology()
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001199
Richard Barnette4aeb01c2018-09-20 09:36:12 -07001200
Richard Barnette9a26ad62016-06-10 12:03:08 -07001201 def repair_servo(self):
Dan Shi90466352015-09-22 15:01:05 -07001202 """
Richard Barnette9a26ad62016-06-10 12:03:08 -07001203 Confirm that servo is initialized and verified.
Dan Shi90466352015-09-22 15:01:05 -07001204
Richard Barnette9a26ad62016-06-10 12:03:08 -07001205 If the servo object is missing, attempt to repair the servo
1206 host. Repair failures are passed back to the caller.
1207
1208 @raise AutoservError: If there is no servo host for this CrOS
1209 host.
1210 """
1211 if self.servo:
1212 return
1213 if not self._servo_host:
1214 raise error.AutoservError('No servo host for %s.' %
1215 self.hostname)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001216 try:
1217 self._servo_host.repair()
1218 except:
1219 raise
1220 finally:
1221 self.set_servo_host(self._servo_host)
1222
1223
Otabek Kasimov41301a22020-05-10 15:28:21 -07001224 def set_servo_type(self):
1225 """Set servo info labels to dut host_info"""
1226 if not self.servo:
Otabek Kasimovbea89e52020-05-12 15:40:00 -07001227 logging.debug('Servo is not initialized to get servo_type.')
Otabek Kasimov41301a22020-05-10 15:28:21 -07001228 return
Otabek Kasimov1b70e8d2020-12-30 13:51:00 -08001229 if not self.is_servo_in_working_state():
1230 logging.debug('Servo is not good, skip update servo_type.')
1231 return
Otabek Kasimov41301a22020-05-10 15:28:21 -07001232 servo_type = self.servo.get_servo_type()
1233 if not servo_type:
Otabek Kasimovbea89e52020-05-12 15:40:00 -07001234 logging.debug('Cannot collect servo_type from servo'
Otabek Kasimov41301a22020-05-10 15:28:21 -07001235 ' by `dut-control servo_type`! Please file a bug'
1236 ' and inform infra team as we are not expected '
1237 ' to reach this point.')
1238 return
1239 host_info = self.host_info_store.get()
1240 prefix = servo_constants.SERVO_TYPE_LABEL_PREFIX
1241 old_type = host_info.get_label_value(prefix)
1242 if old_type == servo_type:
1243 # do not need update
1244 return
1245 host_info.set_version_label(prefix, servo_type)
1246 self.host_info_store.commit(host_info)
1247 logging.info('ServoHost: servo_type updated to %s '
1248 '(previous: %s)', servo_type, old_type)
1249
1250
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001251 def set_servo_state(self, servo_state):
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001252 """Set servo info labels to dut host_info"""
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001253 if servo_state is not None:
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001254 host_info = self.host_info_store.get()
Garry Wang11b5e872020-03-11 15:14:08 -07001255 servo_state_prefix = servo_constants.SERVO_STATE_LABEL_PREFIX
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001256 old_state = host_info.get_label_value(servo_state_prefix)
1257 if old_state == servo_state:
1258 # do not need update
1259 return
1260 host_info.set_version_label(servo_state_prefix, servo_state)
Otabek Kasimovcc9738e2020-02-14 16:17:15 -08001261 self.host_info_store.commit(host_info)
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001262 logging.info('ServoHost: servo_state updated to %s (previous: %s)',
1263 servo_state, old_state)
Dan Shi90466352015-09-22 15:01:05 -07001264
1265
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001266 def get_servo_state(self):
1267 host_info = self.host_info_store.get()
Garry Wang11b5e872020-03-11 15:14:08 -07001268 servo_state_prefix = servo_constants.SERVO_STATE_LABEL_PREFIX
Otabek Kasimova7ba91a2020-03-09 08:31:01 -07001269 return host_info.get_label_value(servo_state_prefix)
1270
Otabek Kasimov5f039202020-10-28 15:45:29 -07001271 def is_servo_in_working_state(self):
1272 """Validate servo is in WORKING state."""
1273 servo_state = self.get_servo_state()
1274 return servo_state == servo_constants.SERVO_STATE_WORKING
1275
Dana Goyette655af512020-09-03 10:48:23 -07001276 def get_servo_usb_state(self):
1277 """Get the label value indicating the health of the USB drive.
1278
1279 @return: The label value if defined, otherwise '' (empty string).
1280 @rtype: str
1281 """
1282 host_info = self.host_info_store.get()
1283 servo_usb_state_prefix = audit_const.SERVO_USB_STATE_PREFIX
1284 return host_info.get_label_value(servo_usb_state_prefix)
1285
1286 def is_servo_usb_usable(self):
1287 """Check if the servo USB storage device is usable for FAFT.
1288
1289 @return: False if the label indicates a state that will break FAFT.
1290 True if state is okay, or if state is not defined.
1291 @rtype: bool
1292 """
1293 usb_state = self.get_servo_usb_state()
1294 return usb_state in ('', audit_const.HW_STATE_ACCEPTABLE,
1295 audit_const.HW_STATE_NORMAL,
1296 audit_const.HW_STATE_UNKNOWN)
Otabek Kasimov41301a22020-05-10 15:28:21 -07001297
Garry Wang000c6c02020-05-11 21:27:23 -07001298 def _set_smart_usbhub_label(self, smart_usbhub_detected):
1299 if smart_usbhub_detected is None:
1300 # skip the label update here as this indicate we wasn't able
1301 # to confirm usbhub type.
1302 return
1303 host_info = self.host_info_store.get()
1304 if (smart_usbhub_detected ==
1305 (servo_constants.SMART_USBHUB_LABEL in host_info.labels)):
1306 # skip label update if current label match the truth.
1307 return
1308 if smart_usbhub_detected:
1309 logging.info('Adding %s label to host %s',
1310 servo_constants.SMART_USBHUB_LABEL,
1311 self.hostname)
1312 host_info.labels.append(servo_constants.SMART_USBHUB_LABEL)
1313 else:
1314 logging.info('Removing %s label from host %s',
1315 servo_constants.SMART_USBHUB_LABEL,
1316 self.hostname)
1317 host_info.labels.remove(servo_constants.SMART_USBHUB_LABEL)
1318 self.host_info_store.commit(host_info)
1319
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -08001320 def repair(self):
1321 """Attempt to get the DUT to pass `self.verify()`.
Richard Barnette82c35912012-11-20 10:09:10 -08001322
1323 This overrides the base class function for repair; it does
J. Richard Barnette91137f02016-03-10 16:52:26 -08001324 not call back to the parent class, but instead relies on
1325 `self._repair_strategy` to coordinate the verification and
1326 repair steps needed to get the DUT working.
Richard Barnette82c35912012-11-20 10:09:10 -08001327 """
Richard Barnetteabbdc252018-07-26 16:57:42 -07001328 message = 'Beginning repair for host %s board %s model %s'
1329 info = self.host_info_store.get()
1330 message %= (self.hostname, info.board, info.model)
1331 self.record('INFO', None, None, message)
Garry Wanga2e78172020-09-09 23:49:07 -07001332 profile_state = profile_constants.DUT_STATE_READY
Shijin Abraham78ce4402020-09-08 22:04:27 -07001333 # Initialize bluetooth peers
1334 self.initialize_btpeer()
Garry Wang87af1d02020-05-26 17:55:54 -07001335 try:
1336 self._repair_strategy.repair(self)
1337 except hosts.AutoservVerifyDependencyError as e:
Otabek Kasimovd48389b2020-12-07 02:38:34 -08001338 # TODO(otabek): remove when finish b/174191325
1339 self._stat_if_pingable_but_not_sshable()
Garry Wang87af1d02020-05-26 17:55:54 -07001340 # We don't want flag a DUT as failed if only non-critical
1341 # verifier(s) failed during the repair.
1342 if e.is_critical():
Garry Wanga2e78172020-09-09 23:49:07 -07001343 profile_state = profile_constants.DUT_STATE_REPAIR_FAILED
Otabek Kasimov86062d02020-11-17 13:30:22 -08001344 self._reboot_labstation_if_needed()
Otabek Kasimov7cad9ce2020-07-28 14:58:48 -07001345 self.try_set_device_needs_manual_repair()
Garry Wang87af1d02020-05-26 17:55:54 -07001346 raise
Garry Wanga2e78172020-09-09 23:49:07 -07001347 finally:
1348 self.set_health_profile_dut_state(profile_state)
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001349
Otabek Kasimov8bb09912020-10-01 14:44:57 -07001350 def get_verifier_state(self, tag):
1351 """Return the state of servo verifier.
1352
1353 @returns: bool or None
1354 """
1355 return self._repair_strategy.verifier_is_good(tag)
Richard Barnette82c35912012-11-20 10:09:10 -08001356
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001357 def close(self):
David Rileye2c6be12017-12-11 10:20:57 -08001358 """Close connection."""
Fang Deng0ca40e22013-08-27 17:47:44 -07001359 super(CrosHost, self).close()
howardchung83e55272019-08-08 14:08:05 +08001360
Shijin Abraham783a7dd2020-02-14 15:36:11 -08001361 if self._chameleon_host:
1362 self._chameleon_host.close()
xixuand6011f12016-12-08 15:01:58 -08001363
Garry Wang1a493d82020-08-31 21:01:19 -07001364 if self.health_profile:
1365 try:
1366 self.health_profile.close()
1367 except Exception as e:
1368 logging.warning(
1369 'Failed to finalize device health profile; %s', e)
1370
xixuand6011f12016-12-08 15:01:58 -08001371 if self._servo_host:
1372 self._servo_host.close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001373
Dan Shi49ca0932014-11-14 11:22:27 -08001374 def get_power_supply_info(self):
1375 """Get the output of power_supply_info.
1376
1377 power_supply_info outputs the info of each power supply, e.g.,
1378 Device: Line Power
1379 online: no
1380 type: Mains
1381 voltage (V): 0
1382 current (A): 0
1383 Device: Battery
1384 state: Discharging
1385 percentage: 95.9276
1386 technology: Li-ion
1387
1388 Above output shows two devices, Line Power and Battery, with details of
1389 each device listed. This function parses the output into a dictionary,
1390 with key being the device name, and value being a dictionary of details
1391 of the device info.
1392
1393 @return: The dictionary of power_supply_info, e.g.,
1394 {'Line Power': {'online': 'yes', 'type': 'main'},
1395 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001396 @raise error.AutoservRunError if power_supply_info tool is not found in
1397 the DUT. Caller should handle this error to avoid false failure
1398 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001399 """
1400 result = self.run('power_supply_info').stdout.strip()
1401 info = {}
1402 device_name = None
1403 device_info = {}
1404 for line in result.split('\n'):
1405 pair = [v.strip() for v in line.split(':')]
1406 if len(pair) != 2:
1407 continue
1408 if pair[0] == 'Device':
1409 if device_name:
1410 info[device_name] = device_info
1411 device_name = pair[1]
1412 device_info = {}
1413 else:
1414 device_info[pair[0]] = pair[1]
1415 if device_name and not device_name in info:
1416 info[device_name] = device_info
1417 return info
1418
1419
1420 def get_battery_percentage(self):
1421 """Get the battery percentage.
1422
1423 @return: The percentage of battery level, value range from 0-100. Return
1424 None if the battery info cannot be retrieved.
1425 """
1426 try:
1427 info = self.get_power_supply_info()
1428 logging.info(info)
1429 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001430 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001431 return None
1432
1433
Philip Chenaf69ead2020-03-27 13:06:42 -07001434 def get_battery_state(self):
1435 """Get the battery charging state.
1436
1437 @return: A string representing the battery charging state. It can be
1438 'Charging', 'Fully charged', or 'Discharging'.
1439 """
1440 try:
1441 info = self.get_power_supply_info()
1442 logging.info(info)
1443 return info['Battery']['state']
1444 except (KeyError, ValueError, error.AutoservRunError):
1445 return None
1446
1447
Daniel Campello8ca25c22019-12-13 16:48:26 -07001448 def get_battery_display_percentage(self):
1449 """Get the battery display percentage.
1450
1451 @return: The display percentage of battery level, value range from
1452 0-100. Return None if the battery info cannot be retrieved.
1453 """
1454 try:
1455 info = self.get_power_supply_info()
1456 logging.info(info)
1457 return float(info['Battery']['display percentage'])
1458 except (KeyError, ValueError, error.AutoservRunError):
1459 return None
1460
1461
Dan Shi49ca0932014-11-14 11:22:27 -08001462 def is_ac_connected(self):
1463 """Check if the dut has power adapter connected and charging.
1464
1465 @return: True if power adapter is connected and charging.
1466 """
1467 try:
1468 info = self.get_power_supply_info()
1469 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001470 except (KeyError, error.AutoservRunError):
1471 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001472
1473
Simran Basi5e6339a2013-03-21 11:34:32 -07001474 def _cleanup_poweron(self):
1475 """Special cleanup method to make sure hosts always get power back."""
Garry Wangad4d4fd2019-01-30 17:00:38 -08001476 info = self.host_info_store.get()
1477 if self._RPM_OUTLET_CHANGED not in info.attributes:
Simran Basi5e6339a2013-03-21 11:34:32 -07001478 return
1479 logging.debug('This host has recently interacted with the RPM'
1480 ' Infrastructure. Ensuring power is on.')
1481 try:
1482 self.power_on()
Garry Wangad4d4fd2019-01-30 17:00:38 -08001483 self._remove_rpm_changed_tag()
Simran Basi5e6339a2013-03-21 11:34:32 -07001484 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001485 logging.error('Failed to turn Power On for this host after '
1486 'cleanup through the RPM Infrastructure.')
Dan Shi49ca0932014-11-14 11:22:27 -08001487
1488 battery_percentage = self.get_battery_percentage()
Otabek Kasimov58e22562020-11-03 17:17:41 -08001489 if (
1490 battery_percentage
1491 and battery_percentage < cros_constants.MIN_BATTERY_LEVEL):
Dan Shi49ca0932014-11-14 11:22:27 -08001492 raise
1493 elif self.is_ac_connected():
1494 logging.info('The device has power adapter connected and '
1495 'charging. No need to try to turn RPM on '
1496 'again.')
Garry Wangad4d4fd2019-01-30 17:00:38 -08001497 self._remove_rpm_changed_tag()
Dan Shi49ca0932014-11-14 11:22:27 -08001498 logging.info('Battery level is now at %s%%. The device may '
1499 'still have enough power to run test, so no '
1500 'exception will be raised.', battery_percentage)
1501
Simran Basi5e6339a2013-03-21 11:34:32 -07001502
Garry Wangad4d4fd2019-01-30 17:00:38 -08001503 def _remove_rpm_changed_tag(self):
1504 info = self.host_info_store.get()
1505 del info.attributes[self._RPM_OUTLET_CHANGED]
1506 self.host_info_store.commit(info)
1507
1508
1509 def _add_rpm_changed_tag(self):
1510 info = self.host_info_store.get()
Garry Wang518831d2019-02-21 15:15:36 -08001511 info.attributes[self._RPM_OUTLET_CHANGED] = 'true'
Garry Wangad4d4fd2019-01-30 17:00:38 -08001512 self.host_info_store.commit(info)
1513
1514
1515
beepsc87ff602013-07-31 21:53:00 -07001516 def _is_factory_image(self):
1517 """Checks if the image on the DUT is a factory image.
1518
1519 @return: True if the image on the DUT is a factory image.
1520 False otherwise.
1521 """
1522 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1523 return result.exit_status == 0
1524
1525
1526 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001527 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001528
1529 @raises: FactoryImageCheckerException for factory images, since
1530 we cannot attempt to restart ui on them.
1531 error.AutoservRunError for any other type of error that
1532 occurs while restarting ui.
1533 """
1534 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001535 raise FactoryImageCheckerException('Cannot restart ui on factory '
1536 'images')
beepsc87ff602013-07-31 21:53:00 -07001537
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001538 # TODO(jrbarnette): The command to stop/start the ui job
1539 # should live inside cros_ui, too. However that would seem
1540 # to imply interface changes to the existing start()/restart()
1541 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -07001542 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001543 self.run('stop ui; start ui')
1544 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001545
1546
Daniel Erat4b5f7e02018-07-04 22:05:30 -07001547 def _start_powerd_if_needed(self):
1548 """Start powerd if it isn't already running."""
1549 self.run('start powerd', ignore_status=True)
1550
Kazuhiro Inabaa7a00492020-12-17 16:05:12 +09001551 def _read_arc_prop_file(self, filename):
1552 for path in [
1553 '/usr/share/arcvm/properties/', '/usr/share/arc/properties/'
1554 ]:
1555 if self.path_exists(path + filename):
1556 return utils.parse_cmd_output('cat ' + path + filename,
1557 run_method=self.run)
1558 return None
Daniel Erat4b5f7e02018-07-04 22:05:30 -07001559
Jiyoun Hac172ee72020-12-15 08:57:29 +09001560 def _get_arc_build_info(self):
1561 """Returns a dictionary mapping build properties to their values."""
1562 build_info = None
Kazuhiro Inabaa7a00492020-12-17 16:05:12 +09001563 for filename in ['build.prop', 'vendor_build.prop']:
1564 properties = self._read_arc_prop_file(filename)
1565 if properties:
1566 if build_info:
1567 build_info.update(properties)
1568 else:
1569 build_info = properties
1570 else:
1571 logging.error('Failed to find %s in device.', filename)
Jiyoun Hac172ee72020-12-15 08:57:29 +09001572 return build_info
1573
Jiyoun Haba37f312021-01-13 09:44:16 +09001574 def get_arc_primary_abi(self):
Jiyoun Hac172ee72020-12-15 08:57:29 +09001575 """Returns the primary abi of the host."""
1576 return self._get_arc_build_info().get('ro.product.cpu.abi')
1577
Jiyoun Haba37f312021-01-13 09:44:16 +09001578 def get_arc_security_patch(self):
Jiyoun Hac172ee72020-12-15 08:57:29 +09001579 """Returns the security patch of the host."""
1580 return self._get_arc_build_info().get('ro.build.version.security_patch')
1581
Kazuhiro Inabaa7a00492020-12-17 16:05:12 +09001582 def get_arc_first_api_level(self):
1583 """Returns the security patch of the host."""
1584 return self._get_arc_build_info().get('ro.product.first_api_level')
1585
xixuana3bbc422017-05-04 15:57:21 -07001586 def _get_lsb_release_content(self):
1587 """Return the content of lsb-release file of host."""
1588 return self.run(
1589 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1590
1591
Dan Shi549fb822015-03-24 18:01:11 -07001592 def get_release_version(self):
1593 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1594
1595 @returns The version string in lsb-release, under attribute
1596 CHROMEOS_RELEASE_VERSION.
1597 """
Dan Shi549fb822015-03-24 18:01:11 -07001598 return lsbrelease_utils.get_chromeos_release_version(
xixuana3bbc422017-05-04 15:57:21 -07001599 lsb_release_content=self._get_lsb_release_content())
1600
1601
Don Garrettb9f35802018-01-22 18:25:40 -08001602 def get_release_builder_path(self):
1603 """Get the value of CHROMEOS_RELEASE_BUILDER_PATH from lsb-release.
1604
1605 @returns The version string in lsb-release, under attribute
1606 CHROMEOS_RELEASE_BUILDER_PATH.
1607 """
1608 return lsbrelease_utils.get_chromeos_release_builder_path(
1609 lsb_release_content=self._get_lsb_release_content())
1610
1611
xixuana3bbc422017-05-04 15:57:21 -07001612 def get_chromeos_release_milestone(self):
1613 """Get the value of attribute CHROMEOS_RELEASE_BUILD_TYPE
1614 from lsb-release.
1615
1616 @returns The version string in lsb-release, under attribute
1617 CHROMEOS_RELEASE_BUILD_TYPE.
1618 """
1619 return lsbrelease_utils.get_chromeos_release_milestone(
1620 lsb_release_content=self._get_lsb_release_content())
Dan Shi549fb822015-03-24 18:01:11 -07001621
1622
1623 def verify_cros_version_label(self):
Garry Wangd18e7b32020-08-07 18:31:44 -07001624 """Verify if host's cros-version label match the actual image in dut.
Dan Shi549fb822015-03-24 18:01:11 -07001625
Garry Wangd18e7b32020-08-07 18:31:44 -07001626 @returns True if the label match with image in dut, otherwise False
Dan Shi549fb822015-03-24 18:01:11 -07001627 """
Garry Wangd18e7b32020-08-07 18:31:44 -07001628 os_from_host = self.get_release_builder_path()
1629 info = self.host_info_store.get()
1630 os_from_label = info.get_label_value(self.VERSION_PREFIX)
1631 if not os_from_label:
1632 logging.debug('No existing %s label detected', self.VERSION_PREFIX)
1633 return True
1634
1635 # known cases where the version label will not match the
1636 # original CHROMEOS_RELEASE_BUILDER_PATH setting:
1637 # * Tests for the `arc-presubmit` append "-cheetsth" to the label.
1638 if os_from_label.endswith(provision.CHEETS_SUFFIX):
1639 logging.debug('%s label with %s suffix detected, this suffix will'
1640 ' be ignored when comparing label.',
1641 self.VERSION_PREFIX, provision.CHEETS_SUFFIX)
1642 os_from_label = os_from_label[:-len(provision.CHEETS_SUFFIX)]
1643 logging.debug('OS version from host: %s; OS verision cached in '
1644 'label: %s', os_from_host, os_from_label)
1645 return os_from_label == os_from_host
Dan Shi549fb822015-03-24 18:01:11 -07001646
1647
Laurence Goodby778c9a42017-05-24 19:24:07 -07001648 def cleanup_services(self):
1649 """Reinitializes the device for cleanup.
1650
1651 Subclasses may override this to customize the cleanup method.
1652
1653 To indicate failure of the reset, the implementation may raise
1654 any of:
1655 error.AutoservRunError
1656 error.AutotestRunError
1657 FactoryImageCheckerException
1658
1659 @raises error.AutoservRunError
1660 @raises error.AutotestRunError
1661 @raises error.FactoryImageCheckerException
1662 """
1663 self._restart_ui()
Daniel Erat4b5f7e02018-07-04 22:05:30 -07001664 self._start_powerd_if_needed()
Laurence Goodby778c9a42017-05-24 19:24:07 -07001665
1666
Gregory Nisbetec615d62020-12-11 17:59:20 +00001667 def cleanup(self):
1668 """Cleanup state on device."""
MK Ryu35d661e2014-09-25 17:44:10 -07001669 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001670 try:
Laurence Goodby778c9a42017-05-24 19:24:07 -07001671 self.cleanup_services()
beepsc87ff602013-07-31 21:53:00 -07001672 except (error.AutotestRunError, error.AutoservRunError,
1673 FactoryImageCheckerException):
Otabek Kasimovc0d77e82020-03-27 15:01:57 -07001674 logging.warning('Unable to restart ui.')
Namyoon Woo33f38852020-04-13 17:26:58 -07001675
Otabek Kasimovc0d77e82020-03-27 15:01:57 -07001676 # cleanup routines, i.e. reboot the machine.
Gregory Nisbetec615d62020-12-11 17:59:20 +00001677 super(CrosHost, self).cleanup()
Otabek Kasimovc0d77e82020-03-27 15:01:57 -07001678
Simran Basi5e6339a2013-03-21 11:34:32 -07001679 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001680 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001681 self._cleanup_poweron()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001682
Gregory Nisbetec615d62020-12-11 17:59:20 +00001683
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001684 def reboot(self, **dargs):
1685 """
1686 This function reboots the site host. The more generic
1687 RemoteHost.reboot() performs sync and sleeps for 5
1688 seconds. This is not necessary for Chrome OS devices as the
1689 sync should be finished in a short time during the reboot
1690 command.
1691 """
Gregory Nisbetec615d62020-12-11 17:59:20 +00001692 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001693 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001694 dargs['reboot_cmd'] = ('sleep 1; '
1695 'reboot & sleep %d; '
1696 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001697 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001698 if 'fastsync' not in dargs:
1699 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001700
Prathmesh Prabhu53a4c1c2018-09-14 16:36:27 -07001701 dargs['board'] = self.host_info_store.get().board
Vincent Palatindf2372c2016-10-07 17:03:00 +02001702 # Record who called us
1703 orig = sys._getframe(1).f_code
Vincent Palatin80780b22016-07-27 16:02:37 +02001704 metric_fields = {'board' : dargs['board'],
Vincent Palatindf2372c2016-10-07 17:03:00 +02001705 'dut_host_name' : self.hostname,
1706 'success' : True}
1707 metric_debug_fields = {'board' : dargs['board'],
Prathmesh Prabhu53a4c1c2018-09-14 16:36:27 -07001708 'caller' : "%s:%s" % (orig.co_filename,
1709 orig.co_name),
Vincent Palatindf2372c2016-10-07 17:03:00 +02001710 'success' : True,
1711 'error' : ''}
1712
Vincent Palatin80780b22016-07-27 16:02:37 +02001713 t0 = time.time()
1714 try:
1715 super(CrosHost, self).reboot(**dargs)
1716 except Exception as e:
1717 metric_fields['success'] = False
Vincent Palatindf2372c2016-10-07 17:03:00 +02001718 metric_debug_fields['success'] = False
1719 metric_debug_fields['error'] = type(e).__name__
Vincent Palatin80780b22016-07-27 16:02:37 +02001720 raise
1721 finally:
1722 duration = int(time.time() - t0)
Dan Shi5e2efb72017-02-07 11:40:23 -08001723 metrics.Counter(
1724 'chromeos/autotest/autoserv/reboot_count').increment(
1725 fields=metric_fields)
1726 metrics.Counter(
1727 'chromeos/autotest/autoserv/reboot_debug').increment(
1728 fields=metric_debug_fields)
1729 metrics.SecondsDistribution(
1730 'chromeos/autotest/autoserv/reboot_duration').add(
1731 duration, fields=metric_fields)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001732
1733
Kalin Stoyanov83d9caa2020-01-31 16:58:27 -08001734 def suspend(self, suspend_time=60, delay_seconds=0,
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001735 suspend_cmd=None, allow_early_resume=False):
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001736 """
1737 This function suspends the site host.
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001738
1739 @param suspend_time: How long to suspend as integer seconds.
1740 @param suspend_cmd: Suspend command to execute.
1741 @param allow_early_resume: If False and if device resumes before
1742 |suspend_time|, throw an error.
1743
1744 @exception AutoservSuspendError Host resumed earlier than
1745 |suspend_time|.
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001746 """
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001747
1748 if suspend_cmd is None:
1749 suspend_cmd = ' && '.join([
J. Richard Barnette9af19632015-09-25 12:18:03 -07001750 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001751 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
Kalin Stoyanov83d9caa2020-01-31 16:58:27 -08001752 'powerd_dbus_suspend --delay=%d' % delay_seconds])
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001753 super(CrosHost, self).suspend(suspend_time, suspend_cmd,
1754 allow_early_resume);
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001755
1756
Simran Basiec564392014-08-25 16:48:09 -07001757 def upstart_status(self, service_name):
1758 """Check the status of an upstart init script.
1759
1760 @param service_name: Service to look up.
1761
1762 @returns True if the service is running, False otherwise.
1763 """
Richard Barnettee204dc52017-09-26 11:02:25 -07001764 return 'start/running' in self.run('status %s' % service_name,
1765 ignore_status=True).stdout
Simran Basiec564392014-08-25 16:48:09 -07001766
Tom Hughese9552342018-12-18 14:29:25 -08001767 def upstart_stop(self, service_name):
1768 """Stops an upstart job if it's running.
1769
1770 @param service_name: Service to stop
1771
1772 @returns True if service has been stopped or was already stopped
1773 False otherwise.
1774 """
1775 if not self.upstart_status(service_name):
1776 return True
1777
1778 result = self.run('stop %s' % service_name, ignore_status=True)
1779 if result.exit_status != 0:
1780 return False
1781 return True
1782
1783 def upstart_restart(self, service_name):
1784 """Restarts (or starts) an upstart job.
1785
1786 @param service_name: Service to start/restart
1787
1788 @returns True if service has been started/restarted, False otherwise.
1789 """
1790 cmd = 'start'
1791 if self.upstart_status(service_name):
1792 cmd = 'restart'
1793 cmd = cmd + ' %s' % service_name
1794 result = self.run(cmd)
1795 if result.exit_status != 0:
1796 return False
1797 return True
Simran Basiec564392014-08-25 16:48:09 -07001798
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001799 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001800 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001801
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001802 Tests for the following conditions:
1803 1. All conditions tested by the parent version of this
1804 function.
1805 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001806 3. Sufficient space in /mnt/stateful_partition/encrypted.
1807 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001808
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001809 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001810 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001811 default_kilo_inodes_required = CONFIG.get_config_value(
1812 'SERVER', 'kilo_inodes_required', type=int, default=100)
1813 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1814 kilo_inodes_required = CONFIG.get_config_value(
1815 'SERVER', 'kilo_inodes_required_%s' % board,
1816 type=int, default=default_kilo_inodes_required)
1817 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001818 self.check_diskspace(
1819 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001820 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001821 'SERVER', 'gb_diskspace_required', type=float,
1822 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001823 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1824 # Not all targets build with encrypted stateful support.
1825 if self.path_exists(encrypted_stateful_path):
1826 self.check_diskspace(
1827 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001828 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001829 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1830 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001831
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -07001832 self.wait_for_system_services()
Prashanth B5d0a0512014-04-25 12:26:08 -07001833
beepsc87ff602013-07-31 21:53:00 -07001834 # Factory images don't run update engine,
1835 # goofy controls dbus on these DUTs.
1836 if not self._is_factory_image():
1837 self.run('update_engine_client --status')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001838
1839
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -07001840 @retry.retry(error.AutoservError, timeout_min=5, delay_sec=10)
1841 def wait_for_system_services(self):
1842 """Waits for system-services to be running.
1843
1844 Sometimes, update_engine will take a while to update firmware, so we
1845 should give this some time to finish. See crbug.com/765686#c38 for
1846 details.
1847 """
1848 if not self.upstart_status('system-services'):
1849 raise error.AutoservError('Chrome failed to reach login. '
1850 'System services not running.')
1851
1852
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001853 def verify(self):
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -08001854 """Verify Chrome OS system is in good state."""
Richard Barnetteabbdc252018-07-26 16:57:42 -07001855 message = 'Beginning verify for host %s board %s model %s'
1856 info = self.host_info_store.get()
1857 message %= (self.hostname, info.board, info.model)
1858 self.record('INFO', None, None, message)
Garry Wang87af1d02020-05-26 17:55:54 -07001859 try:
1860 self._repair_strategy.verify(self)
1861 except hosts.AutoservVerifyDependencyError as e:
1862 # We don't want flag a DUT as failed if only non-critical
1863 # verifier(s) failed during the repair.
1864 if e.is_critical():
1865 raise
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001866
1867
Fang Deng96667ca2013-08-01 17:46:18 -07001868 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
Dean Liaoe3e75f62017-11-14 10:36:43 +08001869 connect_timeout=None, alive_interval=None,
1870 alive_count_max=None, connection_attempts=None):
Fang Deng96667ca2013-08-01 17:46:18 -07001871 """Override default make_ssh_command to use options tuned for Chrome OS.
1872
1873 Tuning changes:
1874 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1875 connection failure. Consistency with remote_access.sh.
1876
Samuel Tan2ce155b2015-06-23 18:24:38 -07001877 - ServerAliveInterval=900; which causes SSH to ping connection every
1878 900 seconds. In conjunction with ServerAliveCountMax ensures
1879 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001880 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001881 the test completed successfully. Later increased from 180 seconds to
1882 900 seconds to account for tests where the DUT is suspended for
1883 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001884
1885 - ServerAliveCountMax=3; consistency with remote_access.sh.
1886
1887 - ConnectAttempts=4; reduce flakiness in connection errors;
1888 consistency with remote_access.sh.
1889
1890 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1891 Host keys change with every new installation, don't waste
1892 memory/space saving them.
1893
1894 - SSH protocol forced to 2; needed for ServerAliveInterval.
1895
1896 @param user User name to use for the ssh connection.
1897 @param port Port on the target host to use for ssh connection.
1898 @param opts Additional options to the ssh command.
1899 @param hosts_file Ignored.
1900 @param connect_timeout Ignored.
1901 @param alive_interval Ignored.
Dean Liaoe3e75f62017-11-14 10:36:43 +08001902 @param alive_count_max Ignored.
1903 @param connection_attempts Ignored.
Fang Deng96667ca2013-08-01 17:46:18 -07001904 """
Dean Liaoe3e75f62017-11-14 10:36:43 +08001905 options = ' '.join([opts, '-o Protocol=2'])
1906 return super(CrosHost, self).make_ssh_command(
1907 user=user, port=port, opts=options, hosts_file='/dev/null',
1908 connect_timeout=30, alive_interval=900, alive_count_max=3,
1909 connection_attempts=4)
1910
1911
Jason Abeleb6f924f2013-11-13 16:01:54 -08001912 def syslog(self, message, tag='autotest'):
1913 """Logs a message to syslog on host.
1914
1915 @param message String message to log into syslog
1916 @param tag String tag prefix for syslog
1917
1918 """
1919 self.run('logger -t "%s" "%s"' % (tag, message))
1920
1921
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001922 def _ping_check_status(self, status):
1923 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001924
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001925 @param status Check the ping status against this value.
1926 @return True iff `status` and the result of ping are the same
1927 (i.e. both True or both False).
1928
1929 """
Abhishek Pandit-Subedi038df162020-09-14 16:37:43 -07001930 ping_val = utils.ping(self.hostname,
1931 tries=1,
1932 deadline=1,
1933 timeout=2,
1934 ignore_timeout=True)
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001935 return not (status ^ (ping_val == 0))
1936
1937 def _ping_wait_for_status(self, status, timeout):
1938 """Wait for the host to have a given status (UP or DOWN).
1939
1940 Status is checked by polling. Polling will not last longer
1941 than the number of seconds in `timeout`. The polling
1942 interval will be long enough that only approximately
1943 _PING_WAIT_COUNT polling cycles will be executed, subject
1944 to a maximum interval of about one minute.
1945
1946 @param status Waiting will stop immediately if `ping` of the
1947 host returns this status.
1948 @param timeout Poll for at most this many seconds.
1949 @return True iff the host status from `ping` matched the
1950 requested status at the time of return.
1951
1952 """
1953 # _ping_check_status() takes about 1 second, hence the
1954 # "- 1" in the formula below.
Nathan Ciobanu38480a32016-10-25 15:26:45 -07001955 # FIXME: if the ping command errors then _ping_check_status()
1956 # returns instantly. If timeout is also smaller than twice
1957 # _PING_WAIT_COUNT then the while loop below forks many
1958 # thousands of ping commands (see /tmp/test_that_results_XXXXX/
1959 # /results-1-logging_YYY.ZZZ/debug/autoserv.DEBUG) and hogs one
1960 # CPU core for 60 seconds.
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001961 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1962 end_time = time.time() + timeout
1963 while time.time() <= end_time:
1964 if self._ping_check_status(status):
1965 return True
1966 if poll_interval > 0:
1967 time.sleep(poll_interval)
1968
1969 # The last thing we did was sleep(poll_interval), so it may
1970 # have been too long since the last `ping`. Check one more
1971 # time, just to be sure.
1972 return self._ping_check_status(status)
1973
1974 def ping_wait_up(self, timeout):
1975 """Wait for the host to respond to `ping`.
1976
1977 N.B. This method is not a reliable substitute for
1978 `wait_up()`, because a host that responds to ping will not
1979 necessarily respond to ssh. This method should only be used
1980 if the target DUT can be considered functional even if it
1981 can't be reached via ssh.
1982
1983 @param timeout Minimum time to allow before declaring the
1984 host to be non-responsive.
1985 @return True iff the host answered to ping before the timeout.
1986
1987 """
1988 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001989
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001990 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001991 """Wait until the host no longer responds to `ping`.
1992
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001993 This function can be used as a slightly faster version of
1994 `wait_down()`, by avoiding potentially long ssh timeouts.
1995
1996 @param timeout Minimum time to allow for the host to become
1997 non-responsive.
1998 @return True iff the host quit answering ping before the
1999 timeout.
2000
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002001 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002002 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002003
Anand K Mistry50f218e2020-07-31 14:50:15 +10002004 def _is_host_port_forwarded(self):
Garry Wanga2e78172020-09-09 23:49:07 -07002005 """Checks if the dut is connected over port forwarding.
Anand K Mistry50f218e2020-07-31 14:50:15 +10002006
2007 N.B. This method does not detect all situations where port forwarding is
2008 occurring. Namely, running autotest on the dut may result in a
2009 false-positive, and port forwarding using a different machine on the
2010 same network will be a false-negative.
2011
2012 @return True if the dut is connected over port forwarding
2013 False otherwise
2014 """
Garry Wanga2e78172020-09-09 23:49:07 -07002015 is_localhost = self.hostname in ['localhost', '127.0.0.1']
2016 is_forwarded = is_localhost and not self.is_default_port
2017 if is_forwarded:
2018 logging.info('Detected DUT connected by port forwarding')
2019 return is_forwarded
Anand K Mistry50f218e2020-07-31 14:50:15 +10002020
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002021 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002022 """Wait for the client to enter low-power sleep mode.
2023
2024 The test for "is asleep" can't distinguish a system that is
2025 powered off; to confirm that the unit was asleep, it is
2026 necessary to force resume, and then call
2027 `test_wait_for_resume()`.
2028
2029 This function is expected to be called from a test as part
2030 of a sequence like the following:
2031
2032 ~~~~~~~~
2033 boot_id = host.get_boot_id()
2034 # trigger sleep on the host
2035 host.test_wait_for_sleep()
2036 # trigger resume on the host
2037 host.test_wait_for_resume(boot_id)
2038 ~~~~~~~~
2039
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002040 @param sleep_timeout time limit in seconds to allow the host sleep.
2041
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002042 @exception TestFail The host did not go to sleep within
2043 the allowed time.
2044 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002045 if sleep_timeout is None:
2046 sleep_timeout = self.SLEEP_TIMEOUT
2047
Anand K Mistry50f218e2020-07-31 14:50:15 +10002048 # If the dut is accessed over SSH port-forwarding, `ping` is not useful
2049 # for detecting the dut is down since a ping to localhost will always
2050 # succeed. In this case, fall back to wait_down() which uses SSH.
2051 if self._is_host_port_forwarded():
Garry Wanga2e78172020-09-09 23:49:07 -07002052 success = self.wait_down(timeout=sleep_timeout)
Anand K Mistry50f218e2020-07-31 14:50:15 +10002053 else:
Garry Wanga2e78172020-09-09 23:49:07 -07002054 success = self.ping_wait_down(timeout=sleep_timeout)
Anand K Mistry50f218e2020-07-31 14:50:15 +10002055
2056 if not success:
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002057 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002058 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002059
2060
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002061 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002062 """Wait for the client to resume from low-power sleep mode.
2063
2064 The `old_boot_id` parameter should be the value from
2065 `get_boot_id()` obtained prior to entering sleep mode. A
2066 `TestFail` exception is raised if the boot id changes.
2067
2068 See @ref test_wait_for_sleep for more on this function's
2069 usage.
2070
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002071 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002072 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002073 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002074
2075 @exception TestFail The host did not respond within the
2076 allowed time.
2077 @exception TestFail The host responded, but the boot id test
2078 indicated a reboot rather than a sleep
2079 cycle.
2080 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002081 if resume_timeout is None:
2082 resume_timeout = self.RESUME_TIMEOUT
2083
2084 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002085 raise error.TestFail(
2086 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002087 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002088 else:
2089 new_boot_id = self.get_boot_id()
2090 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002091 logging.error('client rebooted (old boot %s, new boot %s)',
2092 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002093 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002094 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002095
2096
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002097 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002098 """Wait for the client to shut down.
2099
2100 The test for "has shut down" can't distinguish a system that
2101 is merely asleep; to confirm that the unit was down, it is
2102 necessary to force boot, and then call test_wait_for_boot().
2103
2104 This function is expected to be called from a test as part
2105 of a sequence like the following:
2106
2107 ~~~~~~~~
2108 boot_id = host.get_boot_id()
2109 # trigger shutdown on the host
2110 host.test_wait_for_shutdown()
2111 # trigger boot on the host
2112 host.test_wait_for_boot(boot_id)
2113 ~~~~~~~~
2114
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002115 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002116 @exception TestFail The host did not shut down within the
2117 allowed time.
2118 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002119 if shutdown_timeout is None:
2120 shutdown_timeout = self.SHUTDOWN_TIMEOUT
2121
Anand K Mistry50f218e2020-07-31 14:50:15 +10002122 if self._is_host_port_forwarded():
Garry Wanga2e78172020-09-09 23:49:07 -07002123 success = self.wait_down(timeout=shutdown_timeout)
Anand K Mistry50f218e2020-07-31 14:50:15 +10002124 else:
Garry Wanga2e78172020-09-09 23:49:07 -07002125 success = self.ping_wait_down(timeout=shutdown_timeout)
Anand K Mistry50f218e2020-07-31 14:50:15 +10002126
2127 if not success:
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002128 raise error.TestFail(
2129 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002130 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002131
2132
2133 def test_wait_for_boot(self, old_boot_id=None):
2134 """Wait for the client to boot from cold power.
2135
2136 The `old_boot_id` parameter should be the value from
2137 `get_boot_id()` obtained prior to shutting down. A
2138 `TestFail` exception is raised if the boot id does not
2139 change. The boot id test is omitted if `old_boot_id` is not
2140 specified.
2141
2142 See @ref test_wait_for_shutdown for more on this function's
2143 usage.
2144
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002145 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002146 shut down.
2147
2148 @exception TestFail The host did not respond within the
2149 allowed time.
2150 @exception TestFail The host responded, but the boot id test
2151 indicated that there was no reboot.
2152 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002153 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002154 raise error.TestFail(
2155 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002156 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002157 elif old_boot_id:
2158 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002159 logging.error('client not rebooted (boot %s)',
2160 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002161 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002162 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07002163
2164
2165 @staticmethod
2166 def check_for_rpm_support(hostname):
2167 """For a given hostname, return whether or not it is powered by an RPM.
2168
Simran Basi1df55112013-09-06 11:25:09 -07002169 @param hostname: hostname to check for rpm support.
2170
Simran Basid5e5e272012-09-24 15:23:59 -07002171 @return None if this host does not follows the defined naming format
2172 for RPM powered DUT's in the lab. If it does follow the format,
2173 it returns a regular expression MatchObject instead.
2174 """
Fang Dengbaff9082015-01-06 13:46:15 -08002175 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002176
2177
2178 def has_power(self):
2179 """For this host, return whether or not it is powered by an RPM.
2180
2181 @return True if this host is in the CROS lab and follows the defined
2182 naming format.
2183 """
Fang Deng0ca40e22013-08-27 17:47:44 -07002184 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002185
2186
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002187 def _set_power(self, state, power_method):
Garry Wang5e5538a2019-04-08 15:36:18 -07002188 """Sets the power to the host via RPM, CCD, Servo or manual.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002189
2190 @param state Specifies which power state to set to DUT
2191 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07002192 use. By default "RPM" or "CCD" will be used based
2193 on servo type. Valid values from
2194 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002195
2196 """
2197 ACCEPTABLE_STATES = ['ON', 'OFF']
2198
Garry Wang5e5538a2019-04-08 15:36:18 -07002199 if not power_method:
2200 power_method = self.get_default_power_method()
2201
2202 state = state.upper()
2203 if state not in ACCEPTABLE_STATES:
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002204 raise error.TestError('State must be one of: %s.'
2205 % (ACCEPTABLE_STATES,))
2206
2207 if power_method == self.POWER_CONTROL_SERVO:
2208 logging.info('Setting servo port J10 to %s', state)
2209 self.servo.set('prtctl3_pwren', state.lower())
2210 time.sleep(self._USB_POWER_TIMEOUT)
2211 elif power_method == self.POWER_CONTROL_MANUAL:
2212 logging.info('You have %d seconds to set the AC power to %s.',
2213 self._POWER_CYCLE_TIMEOUT, state)
2214 time.sleep(self._POWER_CYCLE_TIMEOUT)
Garry Wang5e5538a2019-04-08 15:36:18 -07002215 elif power_method == self.POWER_CONTROL_CCD:
2216 servo_role = 'src' if state == 'ON' else 'snk'
2217 logging.info('servo ccd power pass through detected,'
2218 ' changing servo_role to %s.', servo_role)
2219 self.servo.set_servo_v4_role(servo_role)
2220 if not self.ping_wait_up(timeout=self._CHANGE_SERVO_ROLE_TIMEOUT):
Garry Wang94bf9de2019-06-10 17:23:37 -07002221 # Make sure we don't leave DUT with no power(servo_role=snk)
2222 # when DUT is not pingable, as we raise a exception here
2223 # that may break a power cycle in the middle.
2224 self.servo.set_servo_v4_role('src')
Garry Wang5e5538a2019-04-08 15:36:18 -07002225 raise error.AutoservError(
2226 'DUT failed to regain network connection after %d seconds.'
2227 % self._CHANGE_SERVO_ROLE_TIMEOUT)
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002228 else:
2229 if not self.has_power():
2230 raise error.TestFail('DUT does not have RPM connected.')
Garry Wangad4d4fd2019-01-30 17:00:38 -08002231 self._add_rpm_changed_tag()
Garry Wang5e5538a2019-04-08 15:36:18 -07002232 rpm_client.set_power(self, state, timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07002233
2234
Garry Wang5e5538a2019-04-08 15:36:18 -07002235 def power_off(self, power_method=None):
2236 """Turn off power to this host via RPM, CCD, Servo or manual.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002237
2238 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07002239 use. By default "RPM" or "CCD" will be used based
2240 on servo type. Valid values from
2241 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002242
2243 """
Derek Beckettb66e5c82020-08-12 15:31:02 -07002244 self._sync_if_up()
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002245 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07002246
Derek Beckettb66e5c82020-08-12 15:31:02 -07002247 def _check_supported(self):
2248 """Throw an error if dts mode control is not supported."""
2249 if not self.servo_pwr_supported:
2250 raise error.TestFail('power_state controls not supported')
2251
2252 def _sync_if_up(self):
2253 """Run sync on the DUT and wait for completion if the DUT is up.
2254
2255 Additionally, try to sync and ignore status if its not up.
2256
2257 Useful prior to reboots to ensure files are written to disc.
2258
2259 """
2260 if self.is_up_fast():
2261 self.run("sync")
2262 return
2263 # If it is not up, attempt to sync in the rare event the DUT is up but
2264 # doesn't respond to a ping. Ignore any errors.
2265 try:
2266 self.run("sync", ignore_status=True, timeout=1)
2267 except Exception:
2268 pass
2269
2270 def power_off_via_servo(self):
2271 """Force the DUT to power off.
2272
2273 The DUT is guaranteed to be off at the end of this call,
2274 regardless of its previous state, provided that there is
2275 working EC and boot firmware. There is no requirement for
2276 working OS software.
2277
2278 """
2279 self._check_supported()
2280 self._sync_if_up()
2281 self.servo.set_nocheck('power_state', 'off')
2282
2283 def power_on_via_servo(self, rec_mode='on'):
2284 """Force the DUT to power on.
2285
2286 Prior to calling this function, the DUT must be powered off,
2287 e.g. with a call to `power_off()`.
2288
2289 At power on, recovery mode is set as specified by the
2290 corresponding argument. When booting with recovery mode on, it
2291 is the caller's responsibility to unplug/plug in a bootable
2292 external storage device.
2293
2294 If the DUT requires a delay after powering on but before
2295 processing inputs such as USB stick insertion, the delay is
2296 handled by this method; the caller is not responsible for such
2297 delays.
2298
2299 @param rec_mode Setting of recovery mode to be applied at
2300 power on. default: REC_OFF aka 'off'
2301
2302 """
2303 self._check_supported()
2304 self.servo.set_nocheck('power_state', rec_mode)
2305
2306 def reset_via_servo(self):
2307 """Force the DUT to reset.
2308
2309 The DUT is guaranteed to be on at the end of this call,
2310 regardless of its previous state, provided that there is
2311 working OS software. This also guarantees that the EC has
2312 been restarted.
2313
2314 """
2315 self._check_supported()
2316 self._sync_if_up()
2317 self.servo.set_nocheck('power_state', 'reset')
2318
Simran Basid5e5e272012-09-24 15:23:59 -07002319
Garry Wang5e5538a2019-04-08 15:36:18 -07002320 def power_on(self, power_method=None):
2321 """Turn on power to this host via RPM, CCD, Servo or manual.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002322
2323 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07002324 use. By default "RPM" or "CCD" will be used based
2325 on servo type. Valid values from
2326 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002327
2328 """
2329 self._set_power('ON', power_method)
2330
2331
Garry Wang5e5538a2019-04-08 15:36:18 -07002332 def power_cycle(self, power_method=None):
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002333 """Cycle power to this host by turning it OFF, then ON.
2334
2335 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07002336 use. By default "RPM" or "CCD" will be used based
2337 on servo type. Valid values from
2338 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002339
2340 """
Garry Wang5e5538a2019-04-08 15:36:18 -07002341 if not power_method:
2342 power_method = self.get_default_power_method()
2343
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002344 if power_method in (self.POWER_CONTROL_SERVO,
Garry Wang5e5538a2019-04-08 15:36:18 -07002345 self.POWER_CONTROL_MANUAL,
2346 self.POWER_CONTROL_CCD):
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002347 self.power_off(power_method=power_method)
2348 time.sleep(self._POWER_CYCLE_TIMEOUT)
2349 self.power_on(power_method=power_method)
2350 else:
Garry Wangad4d4fd2019-01-30 17:00:38 -08002351 self._add_rpm_changed_tag()
2352 rpm_client.set_power(self, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002353
2354
Mary Ruthvende14a8b2019-08-23 12:43:52 -07002355 def get_platform_from_fwid(self):
2356 """Determine the platform from the crossystem fwid.
2357
2358 @returns a string representing this host's platform.
2359 """
Greg Edelstona7b05d12020-04-01 16:00:51 -06002360 # Look at the firmware for non-unibuild cases or if cros_config fails.
Mary Ruthvende14a8b2019-08-23 12:43:52 -07002361 crossystem = utils.Crossystem(self)
2362 crossystem.init()
2363 # Extract fwid value and use the leading part as the platform id.
2364 # fwid generally follow the format of {platform}.{firmware version}
2365 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
2366 platform = crossystem.fwid().split('.')[0].lower()
2367 # Newer platforms start with 'Google_' while the older ones do not.
2368 return platform.replace('google_', '')
2369
Puthikorn Voravootivat0f7c3d82019-11-27 13:48:39 -08002370
Simran Basic6f1f7a2012-10-16 10:47:46 -07002371 def get_platform(self):
2372 """Determine the correct platform label for this host.
2373
2374 @returns a string representing this host's platform.
2375 """
C Shapiroed87c6f2018-04-19 09:13:58 -06002376 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
2377 run_method=self.run)
C Shapiroed87c6f2018-04-19 09:13:58 -06002378 platform = ''
Puthikorn Voravootivat0f7c3d82019-11-27 13:48:39 -08002379 if release_info.get('CHROMEOS_RELEASE_UNIBUILD') == '1':
Greg Edelstona7b05d12020-04-01 16:00:51 -06002380 platform = self.get_model_from_cros_config()
Mary Ruthvende14a8b2019-08-23 12:43:52 -07002381 return platform if platform else self.get_platform_from_fwid()
Simran Basic6f1f7a2012-10-16 10:47:46 -07002382
2383
Greg Edelstona7b05d12020-04-01 16:00:51 -06002384 def get_model_from_cros_config(self):
2385 """Get the host model from cros_config command.
Puthikorn Voravootivat0f7c3d82019-11-27 13:48:39 -08002386
Greg Edelstona7b05d12020-04-01 16:00:51 -06002387 @returns a string representing this host's model.
Puthikorn Voravootivat0f7c3d82019-11-27 13:48:39 -08002388 """
Greg Edelstona7b05d12020-04-01 16:00:51 -06002389 return cros_config.call_cros_config_get_output('/ name',
2390 self.run, ignore_status=True)
Puthikorn Voravootivat0f7c3d82019-11-27 13:48:39 -08002391
2392
Hung-ying Tyanb1328032014-04-01 14:18:54 +08002393 def get_architecture(self):
2394 """Determine the correct architecture label for this host.
2395
2396 @returns a string representing this host's architecture.
2397 """
2398 crossystem = utils.Crossystem(self)
2399 crossystem.init()
2400 return crossystem.arch()
2401
2402
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002403 def get_chrome_version(self):
2404 """Gets the Chrome version number and milestone as strings.
2405
2406 Invokes "chrome --version" to get the version number and milestone.
2407
2408 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
2409 current Chrome version number as a string (in the form "W.X.Y.Z")
2410 and "milestone" is the first component of the version number
2411 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
2412 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
2413 of "chrome --version" and the milestone will be the empty string.
2414
2415 """
MK Ryu35d661e2014-09-25 17:44:10 -07002416 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002417 return utils.parse_chrome_version(version_string)
2418
J. Richard Barnetted2af5852016-02-05 15:03:10 -08002419
Puthikorn Voravootivatfd132172017-11-16 18:24:38 -08002420 def get_ec_version(self):
2421 """Get the ec version as strings.
2422
2423 @returns a string representing this host's ec version.
2424 """
Puthikorn Voravootivat65ad7672018-01-30 14:00:01 -08002425 command = 'mosys ec info -s fw_version'
Puthikorn Voravootivat720640d2018-02-16 17:32:38 -08002426 result = self.run(command, ignore_status=True)
2427 if result.exit_status != 0:
2428 return ''
2429 return result.stdout.strip()
Puthikorn Voravootivatfd132172017-11-16 18:24:38 -08002430
2431
2432 def get_firmware_version(self):
2433 """Get the firmware version as strings.
2434
2435 @returns a string representing this host's firmware version.
2436 """
2437 crossystem = utils.Crossystem(self)
2438 crossystem.init()
2439 return crossystem.fwid()
2440
2441
Puthikorn Voravootivat58f739b2021-01-07 08:28:36 -08002442 def get_hardware_id(self):
2443 """Get hardware id as strings.
2444
2445 @returns a string representing this host's hardware id.
2446 """
2447 crossystem = utils.Crossystem(self)
2448 crossystem.init()
2449 return crossystem.hwid()
2450
Puthikorn Voravootivatfd132172017-11-16 18:24:38 -08002451 def get_hardware_revision(self):
2452 """Get the hardware revision as strings.
2453
2454 @returns a string representing this host's hardware revision.
2455 """
Puthikorn Voravootivat65ad7672018-01-30 14:00:01 -08002456 command = 'mosys platform version'
Puthikorn Voravootivat720640d2018-02-16 17:32:38 -08002457 result = self.run(command, ignore_status=True)
2458 if result.exit_status != 0:
2459 return ''
2460 return result.stdout.strip()
Puthikorn Voravootivatfd132172017-11-16 18:24:38 -08002461
2462
2463 def get_kernel_version(self):
2464 """Get the kernel version as strings.
2465
2466 @returns a string representing this host's kernel version.
2467 """
2468 return self.run('uname -r').stdout.strip()
2469
2470
Puthikorn Voravootivat54aa53c2018-03-01 19:00:25 -08002471 def get_cpu_name(self):
2472 """Get the cpu name as strings.
2473
2474 @returns a string representing this host's cpu name.
2475 """
2476
2477 # Try get cpu name from device tree first
2478 if self.path_exists('/proc/device-tree/compatible'):
2479 command = ' | '.join(
2480 ["sed -e 's/\\x0/\\n/g' /proc/device-tree/compatible",
2481 'tail -1'])
2482 return self.run(command).stdout.strip().replace(',', ' ')
2483
2484 # Get cpu name from uname -p
2485 command = 'uname -p'
2486 ret = self.run(command).stdout.strip()
2487
2488 # 'uname -p' return variant of unknown or amd64 or x86_64 or i686
2489 # Try get cpu name from /proc/cpuinfo instead
2490 if re.match("unknown|amd64|[ix][0-9]?86(_64)?", ret, re.IGNORECASE):
2491 command = "grep model.name /proc/cpuinfo | cut -f 2 -d: | head -1"
2492 self = self.run(command).stdout.strip()
2493
2494 # Remove bloat from CPU name, for example
2495 # Intel(R) Core(TM) i5-7Y57 CPU @ 1.20GHz -> Intel Core i5-7Y57
2496 # Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz -> Intel Xeon E5-2690 v4
2497 # AMD A10-7850K APU with Radeon(TM) R7 Graphics -> AMD A10-7850K
2498 # AMD GX-212JC SOC with Radeon(TM) R2E Graphics -> AMD GX-212JC
2499 trim_re = r' (@|processor|apu|soc|radeon).*|\(.*?\)| cpu'
2500 return re.sub(trim_re, '', ret, flags=re.IGNORECASE)
2501
2502
2503 def get_screen_resolution(self):
2504 """Get the screen(s) resolution as strings.
2505 In case of more than 1 monitor, return resolution for each monitor
2506 separate with plus sign.
2507
2508 @returns a string representing this host's screen(s) resolution.
2509 """
2510 command = 'for f in /sys/class/drm/*/*/modes; do head -1 $f; done'
2511 ret = self.run(command, ignore_status=True)
2512 # We might have Chromebox without a screen
2513 if ret.exit_status != 0:
2514 return ''
2515 return ret.stdout.strip().replace('\n', '+')
2516
2517
2518 def get_mem_total_gb(self):
2519 """Get total memory available in the system in GiB (2^20).
2520
2521 @returns an integer representing total memory
2522 """
2523 mem_total_kb = self.read_from_meminfo('MemTotal')
2524 kb_in_gb = float(2 ** 20)
2525 return int(round(mem_total_kb / kb_in_gb))
2526
2527
2528 def get_disk_size_gb(self):
2529 """Get size of disk in GB (10^9)
2530
2531 @returns an integer representing size of disk, 0 in Error Case
2532 """
2533 command = 'grep $(rootdev -s -d | cut -f3 -d/)$ /proc/partitions'
2534 result = self.run(command, ignore_status=True)
2535 if result.exit_status != 0:
2536 return 0
2537 _, _, block, _ = re.split(r' +', result.stdout.strip())
2538 byte_per_block = 1024.0
2539 disk_kb_in_gb = 1e9
2540 return int(int(block) * byte_per_block / disk_kb_in_gb + 0.5)
2541
2542
2543 def get_battery_size(self):
2544 """Get size of battery in Watt-hour via sysfs
2545
2546 This method assumes that battery support voltage_min_design and
2547 charge_full_design sysfs.
2548
2549 @returns a float representing Battery size, 0 if error.
2550 """
2551 # sysfs report data in micro scale
2552 battery_scale = 1e6
2553
2554 command = 'cat /sys/class/power_supply/*/voltage_min_design'
2555 result = self.run(command, ignore_status=True)
2556 if result.exit_status != 0:
2557 return 0
2558 voltage = float(result.stdout.strip()) / battery_scale
2559
2560 command = 'cat /sys/class/power_supply/*/charge_full_design'
2561 result = self.run(command, ignore_status=True)
2562 if result.exit_status != 0:
2563 return 0
2564 amphereHour = float(result.stdout.strip()) / battery_scale
2565
2566 return voltage * amphereHour
2567
2568
2569 def get_low_battery_shutdown_percent(self):
2570 """Get the percent-based low-battery shutdown threshold.
2571
2572 @returns a float representing low-battery shutdown percent, 0 if error.
2573 """
2574 ret = 0.0
2575 try:
2576 command = 'check_powerd_config --low_battery_shutdown_percent'
2577 ret = float(self.run(command).stdout)
2578 except error.CmdError:
2579 logging.debug("Can't run %s", command)
2580 except ValueError:
2581 logging.debug("Didn't get number from %s", command)
2582
2583 return ret
2584
2585
Puthikorn Voravootivat09c83d72018-08-10 15:58:32 -07002586 def has_hammer(self):
2587 """Check whether DUT has hammer device or not.
2588
2589 @returns boolean whether device has hammer or not
2590 """
2591 command = 'grep Hammer /sys/bus/usb/devices/*/product'
2592 return self.run(command, ignore_status=True).exit_status == 0
2593
2594
Niranjan Kumar34618872017-05-31 12:57:09 -07002595 def is_chrome_switch_present(self, switch):
David Haddock3ce538e2017-06-22 13:37:05 -07002596 """Returns True if the specified switch was provided to Chrome.
2597
2598 @param switch The chrome switch to search for.
2599 """
Niranjan Kumar34618872017-05-31 12:57:09 -07002600
Niranjan Kumar5f23fe92017-06-22 15:18:55 -07002601 command = 'pgrep -x -f -c "/opt/google/chrome/chrome.*%s.*"' % switch
2602 return self.run(command, ignore_status=True).exit_status == 0
Niranjan Kumar34618872017-05-31 12:57:09 -07002603
2604
2605 def oobe_triggers_update(self):
2606 """Returns True if this host has an OOBE flow during which
2607 it will perform an update check and perhaps an update.
2608 One example of such a flow is Hands-Off Zero-Touch Enrollment.
2609 As more such flows are developed, code handling them needs
2610 to be added here.
2611
2612 @return Boolean indicating whether this host's OOBE triggers an update.
2613 """
2614 return self.is_chrome_switch_present(
2615 '--enterprise-enable-zero-touch-enrollment=hands-off')
2616
2617
Kevin Chenga2619dc2016-03-28 11:42:08 -07002618 # TODO(kevcheng): change this to just return the board without the
2619 # 'board:' prefix and fix up all the callers. Also look into removing the
2620 # need for this method.
Simran Basic6f1f7a2012-10-16 10:47:46 -07002621 def get_board(self):
2622 """Determine the correct board label for this host.
2623
2624 @returns a string representing this host's board.
2625 """
2626 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
2627 run_method=self.run)
J. Richard Barnetted2af5852016-02-05 15:03:10 -08002628 return (ds_constants.BOARD_PREFIX +
2629 release_info['CHROMEOS_RELEASE_BOARD'])
Simran Basic6f1f7a2012-10-16 10:47:46 -07002630
Po-Hsien Wang4618adf2017-10-10 14:40:21 -07002631 def get_channel(self):
2632 """Determine the correct channel label for this host.
Simran Basic6f1f7a2012-10-16 10:47:46 -07002633
Po-Hsien Wang4618adf2017-10-10 14:40:21 -07002634 @returns: a string represeting this host's build channel.
2635 (stable, dev, beta). None on fail.
2636 """
2637 return lsbrelease_utils.get_chromeos_channel(
2638 lsb_release_content=self._get_lsb_release_content())
Kevin Chenga328da62016-03-31 10:49:04 -07002639
Kevin Chenga328da62016-03-31 10:49:04 -07002640 def get_power_supply(self):
2641 """
2642 Determine what type of power supply the host has
2643
2644 @returns a string representing this host's power supply.
2645 'power:battery' when the device has a battery intended for
2646 extended use
2647 'power:AC_primary' when the device has a battery not intended
2648 for extended use (for moving the machine, etc)
2649 'power:AC_only' when the device has no battery at all.
2650 """
2651 psu = self.run(command='mosys psu type', ignore_status=True)
2652 if psu.exit_status:
2653 # The psu command for mosys is not included for all platforms. The
2654 # assumption is that the device will have a battery if the command
2655 # is not found.
2656 return 'power:battery'
2657
2658 psu_str = psu.stdout.strip()
2659 if psu_str == 'unknown':
2660 return None
2661
2662 return 'power:%s' % psu_str
2663
2664
Puthikorn Voravootivat54aa53c2018-03-01 19:00:25 -08002665 def has_battery(self):
2666 """Determine if DUT has a battery.
2667
2668 Returns:
2669 Boolean, False if known not to have battery, True otherwise.
2670 """
2671 rv = True
2672 power_supply = self.get_power_supply()
2673 if power_supply == 'power:battery':
2674 _NO_BATTERY_BOARD_TYPE = ['CHROMEBOX', 'CHROMEBIT', 'CHROMEBASE']
2675 board_type = self.get_board_type()
2676 if board_type in _NO_BATTERY_BOARD_TYPE:
2677 logging.warn('Do NOT believe type %s has battery. '
2678 'See debug for mosys details', board_type)
Sam Hurst57fa60a2020-05-08 08:55:47 -07002679 psu = utils.system_output('mosys -vvvv psu type',
Puthikorn Voravootivat54aa53c2018-03-01 19:00:25 -08002680 ignore_status=True)
2681 logging.debug(psu)
2682 rv = False
2683 elif power_supply == 'power:AC_only':
2684 rv = False
2685
2686 return rv
2687
2688
Kevin Chenga328da62016-03-31 10:49:04 -07002689 def get_servo(self):
2690 """Determine if the host has a servo attached.
2691
2692 If the host has a working servo attached, it should have a servo label.
2693
2694 @return: string 'servo' if the host has servo attached. Otherwise,
2695 returns None.
2696 """
2697 return 'servo' if self._servo_host else None
2698
2699
Kevin Chenga328da62016-03-31 10:49:04 -07002700 def has_internal_display(self):
2701 """Determine if the device under test is equipped with an internal
2702 display.
2703
2704 @return: 'internal_display' if one is present; None otherwise.
2705 """
2706 from autotest_lib.client.cros.graphics import graphics_utils
2707 from autotest_lib.client.common_lib import utils as common_utils
2708
2709 def __system_output(cmd):
2710 return self.run(cmd).stdout
2711
2712 def __read_file(remote_path):
2713 return self.run('cat %s' % remote_path).stdout
2714
2715 # Hijack the necessary client functions so that we can take advantage
2716 # of the client lib here.
2717 # FIXME: find a less hacky way than this
2718 original_system_output = utils.system_output
2719 original_read_file = common_utils.read_file
2720 utils.system_output = __system_output
2721 common_utils.read_file = __read_file
2722 try:
2723 return ('internal_display' if graphics_utils.has_internal_display()
2724 else None)
2725 finally:
2726 utils.system_output = original_system_output
2727 common_utils.read_file = original_read_file
2728
2729
Dan Shi85276d42014-04-08 22:11:45 -07002730 def is_boot_from_usb(self):
2731 """Check if DUT is boot from USB.
2732
2733 @return: True if DUT is boot from usb.
2734 """
2735 device = self.run('rootdev -s -d').stdout.strip()
2736 removable = int(self.run('cat /sys/block/%s/removable' %
2737 os.path.basename(device)).stdout.strip())
2738 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002739
Otabek Kasimov77a40332020-10-20 15:40:03 -07002740 def is_boot_from_external_device(self):
2741 """Check if DUT is boot from external storage.
2742
2743 @return: True if DUT is boot from external storage.
2744 """
2745 boot_device = self.run('rootdev -s -d', ignore_status=True,
2746 timeout=60).stdout.strip()
2747 if not boot_device:
2748 logging.debug('Boot storage not detected on the host.')
2749 return False
2750 main_storage_cmd = ('. /usr/sbin/write_gpt.sh;'
2751 ' . /usr/share/misc/chromeos-common.sh;'
2752 ' load_base_vars; get_fixed_dst_drive')
2753 main_storage = self.run(main_storage_cmd,
2754 ignore_status=True,
2755 timeout=60).stdout.strip()
Otabek Kasimov723e8562020-12-08 13:29:34 -08002756 if not main_storage or boot_device != main_storage:
2757 logging.debug('Device booted from external storage storage.')
2758 return True
2759 logging.debug('Device booted from main storage.')
2760 return False
Helen Zhang17dae2b2014-11-11 09:25:52 -08002761
2762 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002763 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002764
2765 @param key: meminfo requested
2766
2767 @return the memory value as a string
2768
2769 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002770 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2771 logging.debug('%s', meminfo)
2772 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002773
2774
Rohit Makasana98e696f2016-06-03 18:48:10 -07002775 def get_cpu_arch(self):
2776 """Returns CPU arch of the device.
2777
2778 @return CPU architecture of the DUT.
2779 """
Allen Li2c32d6b2017-02-03 15:28:10 -08002780 # Add CPUs by following logic in client/bin/utils.py.
Rohit Makasana98e696f2016-06-03 18:48:10 -07002781 if self.run("grep '^flags.*:.* lm .*' /proc/cpuinfo",
2782 ignore_status=True).stdout:
2783 return 'x86_64'
2784 if self.run("grep -Ei 'ARM|CPU implementer' /proc/cpuinfo",
2785 ignore_status=True).stdout:
2786 return 'arm'
2787 return 'i386'
2788
2789
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002790 def get_board_type(self):
2791 """
2792 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002793 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2794
2795 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002796 """
Danny Chan471a8d12015-08-18 14:57:41 -07002797 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2798 ignore_status=True).stdout
2799 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002800 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002801 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002802
2803
Rohit Makasanadf0a3a32017-06-30 13:55:18 -07002804 def get_arc_version(self):
2805 """Return ARC version installed on the DUT.
2806
2807 @returns ARC version as string if the CrOS build has ARC, else None.
2808 """
2809 arc_version = self.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
2810 ignore_status=True).stdout
2811 if arc_version:
2812 return arc_version.split('=')[-1].strip()
2813 return None
2814
2815
Gilad Arnolda76bef02015-09-29 13:55:15 -07002816 def get_os_type(self):
2817 return 'cros'
Simran Basia5522a32015-10-06 11:01:24 -07002818
2819
Kevin Chenga2619dc2016-03-28 11:42:08 -07002820 def get_labels(self):
Kevin Chengf8660142016-08-12 10:17:41 -07002821 """Return the detected labels on the host."""
Kevin Chenga2619dc2016-03-28 11:42:08 -07002822 return self.labels.get_labels(self)
Garry Wang5e5538a2019-04-08 15:36:18 -07002823
2824
2825 def get_default_power_method(self):
2826 """
2827 Get the default power method for power_on/off/cycle() methods.
2828 @return POWER_CONTROL_RPM or POWER_CONTROL_CCD
2829 """
2830 if not self._default_power_method:
Garry Wang1a004aa2019-05-16 22:56:51 -07002831 self._default_power_method = self.POWER_CONTROL_RPM
Ruben Rodriguez Buchillon3eeeab32019-10-02 15:29:58 -07002832 if self.servo and self.servo.supports_built_in_pd_control():
2833 self._default_power_method = self.POWER_CONTROL_CCD
2834 else:
2835 logging.debug('Either servo is unitialized or the servo '
2836 'setup does not support pd controls. Falling '
2837 'back to default RPM method.')
Garry Wang5e5538a2019-04-08 15:36:18 -07002838 return self._default_power_method
Puthikorn Voravootivat4a054792019-12-13 16:44:17 -08002839
2840
2841 def find_usb_devices(self, idVendor, idProduct):
2842 """
2843 Get usb device sysfs name for specific device.
2844
2845 @param idVendor Vendor ID to search in sysfs directory.
2846 @param idProduct Product ID to search in sysfs directory.
2847
2848 @return Usb node names in /sys/bus/usb/drivers/usb/ that match.
2849 """
2850 # Look for matching file and cut at position 7 to get dir name.
2851 grep_cmd = 'grep {} /sys/bus/usb/drivers/usb/*/{} | cut -f 7 -d /'
2852
2853 vendor_cmd = grep_cmd.format(idVendor, 'idVendor')
2854 product_cmd = grep_cmd.format(idProduct, 'idProduct')
2855
2856 # Use uniq -d to print duplicate line from both command
2857 cmd = 'sort <({}) <({}) | uniq -d'.format(vendor_cmd, product_cmd)
2858
2859 return self.run(cmd, ignore_status=True).stdout.strip().split('\n')
2860
2861
2862 def bind_usb_device(self, usb_node):
2863 """
2864 Bind usb device
2865
2866 @param usb_node Node name in /sys/bus/usb/drivers/usb/
2867 """
2868 cmd = 'echo {} > /sys/bus/usb/drivers/usb/bind'.format(usb_node)
2869 self.run(cmd, ignore_status=True)
2870
2871
2872 def unbind_usb_device(self, usb_node):
2873 """
2874 Unbind usb device
2875
2876 @param usb_node Node name in /sys/bus/usb/drivers/usb/
2877 """
2878 cmd = 'echo {} > /sys/bus/usb/drivers/usb/unbind'.format(usb_node)
2879 self.run(cmd, ignore_status=True)
2880
2881
2882 def get_wlan_ip(self):
2883 """
2884 Get ip address of wlan interface.
2885
2886 @return ip address of wlan or empty string if wlan is not connected.
2887 """
2888 cmds = [
2889 'iw dev', # List wlan physical device
2890 'grep Interface', # Grep only interface name
2891 'cut -f 2 -d" "', # Cut the name part
2892 'xargs ifconfig', # Feed it to ifconfig to get ip
2893 'grep -oE "inet [0-9.]+"', # Grep only ipv4
2894 'cut -f 2 -d " "' # Cut the ip part
2895 ]
2896 return self.run(' | '.join(cmds), ignore_status=True).stdout.strip()
Puthikorn Voravootivatcd0dc9e2020-01-22 14:22:22 -08002897
2898 def connect_to_wifi(self, ssid, passphrase=None, security=None):
2899 """
2900 Connect to wifi network
2901
2902 @param ssid SSID of the wifi network.
2903 @param passphrase Passphrase of the wifi network. None if not existed.
2904 @param security Security of the wifi network. Default to "psk" if
2905 passphase is given without security. Possible values
2906 are "none", "psk", "802_1x".
2907
2908 @return True if succeed, False if not.
2909 """
2910 cmd = '/usr/local/autotest/cros/scripts/wifi connect ' + ssid
2911 if passphrase:
2912 cmd += ' ' + passphrase
2913 if security:
2914 cmd += ' ' + security
2915 return self.run(cmd, ignore_status=True).exit_status == 0
Otabek Kasimov6825b762020-06-23 23:42:44 -07002916
2917 def get_device_repair_state(self):
2918 """Get device repair state"""
2919 return self._device_repair_state
2920
Otabek Kasimov7cad9ce2020-07-28 14:58:48 -07002921 def set_device_repair_state(self, state, resultdir=None):
Otabek Kasimov6825b762020-06-23 23:42:44 -07002922 """Set device repair state.
2923
2924 The special device state will be written to the 'dut_state.repair'
Otabek Kasimov7cad9ce2020-07-28 14:58:48 -07002925 file in result directory. The file will be read by Lucifer. The
2926 file will not be created if result directory not specified.
2927
2928 @params state: The new state for the device.
2929 @params resultdir: The path to result directory. If path not provided
2930 will be attempt to get retrieve it from job
2931 if present.
Otabek Kasimov6825b762020-06-23 23:42:44 -07002932 """
Otabek Kasimov7cad9ce2020-07-28 14:58:48 -07002933 resultdir = resultdir or getattr(self.job, 'resultdir', '')
2934 if resultdir:
2935 target = os.path.join(resultdir, 'dut_state.repair')
Otabek Kasimov6825b762020-06-23 23:42:44 -07002936 common_utils.open_write_close(target, state)
Otabek Kasimov7cad9ce2020-07-28 14:58:48 -07002937 logging.info('Set device state as %s. '
2938 'Created dut_state.repair file.', state)
Otabek Kasimov6825b762020-06-23 23:42:44 -07002939 else:
2940 logging.debug('Cannot write the device state due missing info '
2941 'about result dir.')
2942 self._device_repair_state = state
2943
Otabek Kasimov7cad9ce2020-07-28 14:58:48 -07002944 def set_device_needs_replacement(self, resultdir=None):
2945 """Set device as required replacement.
2946
2947 @params resultdir: The path to result directory. If path not provided
2948 will be attempt to get retrieve it from job
2949 if present.
2950 """
2951 self.set_device_repair_state(
2952 cros_constants.DEVICE_STATE_NEEDS_REPLACEMENT,
2953 resultdir=resultdir)
2954
Otabek Kasimov86062d02020-11-17 13:30:22 -08002955 def _dut_fail_ssh_verifier(self):
2956 """Check if DUT failed SSH verifier.
2957
2958 @returns: bool, True - verifier marked as fail.
2959 False - result not reachable, verifier did not fail.
2960 """
2961 if not self._repair_strategy:
2962 return False
2963 dut_ssh_verifier = self._repair_strategy.verifier_is_good('ssh')
2964 return dut_ssh_verifier == hosts.VERIFY_FAILED
2965
Otabek Kasimovd48389b2020-12-07 02:38:34 -08002966 def _stat_if_pingable_but_not_sshable(self):
2967 """Check if DUT pingable but failed SSH verifier."""
2968 if not self._repair_strategy:
2969 return
2970 dut_ssh = self._repair_strategy.verifier_is_good('ssh')
2971 dut_ping = self._repair_strategy.verifier_is_good('ping')
2972 if (dut_ping == hosts.VERIFY_FAILED
2973 and dut_ssh == hosts.VERIFY_FAILED):
2974 metrics.Counter('chromeos/autotest/dut_pingable_no_ssh').increment(
2975 fields={'host': self.hostname})
2976
Otabek Kasimov7cad9ce2020-07-28 14:58:48 -07002977 def try_set_device_needs_manual_repair(self):
Otabek Kasimov6825b762020-06-23 23:42:44 -07002978 """Check if device require manual attention to be fixed.
2979
2980 The state 'needs_manual_repair' can be set when auto repair cannot
2981 fix the device due hardware or cable issues.
2982 """
2983 # ignore the logic if state present
2984 # state can be set by any cros repair actions
Otabek Kasimov86062d02020-11-17 13:30:22 -08002985 if self.get_device_repair_state():
Otabek Kasimov6825b762020-06-23 23:42:44 -07002986 return
Otabek Kasimov86062d02020-11-17 13:30:22 -08002987 if not self._dut_fail_ssh_verifier():
2988 # DUT is sshable and we still have many options to repair it.
Otabek Kasimovc9812582020-10-08 18:52:52 -07002989 return
Otabek Kasimov9189ede2020-11-09 14:08:58 -08002990 needs_manual_repair = False
2991 dhp = self.health_profile
Otabek Kasimov69253822020-11-24 10:52:27 -08002992 if dhp and dhp.get_repair_fail_count() > 49:
2993 # 42 = 6 times during 7 days. (every 4 hour repair)
2994 # round up to 50 in case somebody will run some attempt on it.
Otabek Kasimovc9812582020-10-08 18:52:52 -07002995 logging.info(
Otabek Kasimov9189ede2020-11-09 14:08:58 -08002996 'DUT is not sshable and fail %s times.'
Otabek Kasimov69253822020-11-24 10:52:27 -08002997 ' Limit to try repair is 50 times',
Otabek Kasimov9189ede2020-11-09 14:08:58 -08002998 dhp.get_repair_fail_count())
2999 needs_manual_repair = True
3000
3001 if not needs_manual_repair:
3002 # We cannot ssh to the DUT and we have hardware or set-up issues
3003 # with servo then we need request manual repair for the DUT.
3004 servo_state_required_manual_fix = [
3005 servo_constants.SERVO_STATE_DUT_NOT_CONNECTED,
3006 servo_constants.SERVO_STATE_NEED_REPLACEMENT,
3007 ]
3008 if self.get_servo_state() in servo_state_required_manual_fix:
3009 logging.info(
3010 'DUT required manual repair because it is not sshable'
3011 ' and possible have setup issue with Servo. Please'
3012 ' verify all connections and present of devices.')
3013 needs_manual_repair = True
3014
3015 if needs_manual_repair:
Otabek Kasimovc9812582020-10-08 18:52:52 -07003016 self.set_device_repair_state(
3017 cros_constants.DEVICE_STATE_NEEDS_MANUAL_REPAIR)
Otabek Kasimov42506d02020-07-29 14:44:57 -07003018
Otabek Kasimov86062d02020-11-17 13:30:22 -08003019 def _reboot_labstation_if_needed(self):
3020 """Place request to reboot the labstation if DUT is not sshable.
3021
3022 @returns: None
3023 """
Puthikorn Voravootivat58f739b2021-01-07 08:28:36 -08003024 message_prefix = "Don't need to request servo-host reboot"
Otabek Kasimov86062d02020-11-17 13:30:22 -08003025 if not self._dut_fail_ssh_verifier():
3026 return
3027 if not self._servo_host:
Puthikorn Voravootivat58f739b2021-01-07 08:28:36 -08003028 logging.debug('%s as it not initialized', message_prefix)
Otabek Kasimov86062d02020-11-17 13:30:22 -08003029 return
3030 if not self._servo_host.is_up_fast():
Puthikorn Voravootivat58f739b2021-01-07 08:28:36 -08003031 logging.debug('%s as servo-host is not sshable', message_prefix)
Otabek Kasimov86062d02020-11-17 13:30:22 -08003032 return
3033 if not self._servo_host.is_labstation():
3034 logging.debug('Servo_v3 is not requested to reboot for the DUT')
3035 return
3036 usb_path = self._servo_host.get_main_servo_usb_path()
3037 if usb_path:
3038 connected_port = os.path.basename(os.path.normpath(usb_path))
3039 # Directly connected servo to the labstation looks like '1-5.3'
3040 # and when connected by hub - '1-5.2.3' or '1-5.2.1.3'. Where:
3041 # - '1-5' - port on labstation
3042 # - '2' or '2.1' - port on the hub or smart-hub
3043 # - '3' - port on servo hub
3044 if len(connected_port.split('.')) > 2:
Puthikorn Voravootivat58f739b2021-01-07 08:28:36 -08003045 logging.debug('%s as servo connected by hub', message_prefix)
Otabek Kasimov86062d02020-11-17 13:30:22 -08003046 return
3047 self._servo_host.request_reboot()
3048 logging.info('Requested labstation reboot because DUT is not sshable')
3049
Otabek Kasimov42506d02020-07-29 14:44:57 -07003050 def is_file_system_writable(self, testdirs=None):
3051 """Check is the file systems are writable.
3052
3053 The standard linux response to certain unexpected file system errors
3054 (including hardware errors in block devices) is to change the file
3055 system status to read-only. This checks that that hasn't happened.
3056
3057 @param testdirs: List of directories to check. If no data provided
3058 then '/mnt/stateful_partition' and '/var/tmp'
3059 directories will be checked.
3060
3061 @returns boolean whether file-system writable.
3062 """
3063 def _check_dir(testdir):
3064 # check if we can create a file
3065 filename = os.path.join(testdir, 'writable_my_test_file')
3066 command = 'touch %s && rm %s' % (filename, filename)
3067 rv = self.run(command=command,
3068 timeout=30,
3069 ignore_status=True)
3070 is_writable = rv.exit_status == 0
3071 if not is_writable:
3072 logging.info('Cannot create a file in "%s"!'
3073 ' Probably the FS is read-only', testdir)
3074 logging.info("FileSystem is not writable!")
3075 return False
3076 return True
3077
3078 if not testdirs or len(testdirs) == 0:
3079 # N.B. Order matters here: Encrypted stateful is loop-mounted
3080 # from a file in unencrypted stateful, so we don't test for
3081 # errors in encrypted stateful if unencrypted fails.
3082 testdirs = ['/mnt/stateful_partition', '/var/tmp']
3083
3084 for dir in testdirs:
3085 # loop will be stopped if any directory fill fail the check
3086 try:
3087 if not _check_dir(dir):
3088 return False
3089 except Exception as e:
3090 # here expected only timeout error, all other will
3091 # be catch by 'ignore_status=True'
3092 logging.debug('Fail to check %s to write in it', dir)
3093 return False
3094 return True
Garry Wang1a493d82020-08-31 21:01:19 -07003095
Dana Goyettec172b172020-07-29 16:26:15 -07003096 def blocking_sync(self, freeze_for_reset=False):
3097 """Sync root device and internal device, via script.
3098
3099 The actual calls end up logged by the run() call, since they're printed
3100 to stdout/stderr in the script.
3101
3102 @param freeze_for_reset: if True, prepare for reset by blocking writes
3103 (only if enable_fs_sync_fsfreeze=True)
3104 """
3105
3106 if freeze_for_reset and self.USE_FSFREEZE:
3107 logging.info('Blocking sync and freeze')
3108 elif freeze_for_reset:
3109 logging.info('Blocking sync for reset')
3110 else:
3111 logging.info('Blocking sync')
3112
3113 # client/bin is installed on the DUT as /usr/local/autotest/bin
3114 sync_cmd = '/usr/local/autotest/bin/fs_sync.py'
3115 if freeze_for_reset and self.USE_FSFREEZE:
3116 sync_cmd += ' --freeze'
3117 return self.run(sync_cmd)
3118
Garry Wanga2e78172020-09-09 23:49:07 -07003119 def set_health_profile_dut_state(self, state):
3120 if not self.health_profile:
3121 logging.debug('Device health profile is not initialized, skip'
3122 ' set dut state.')
3123 return
3124 reset_counters = state in profile_constants.STATES_NEED_RESET_COUNTER
3125 self.health_profile.update_dut_state(state, reset_counters)
Garry Wang53fc8f32020-09-18 13:30:08 -07003126
3127 def require_snk_mode_in_recovery(self):
3128 """Check whether we need to switch servo_v4 role to snk when
3129 booting into recovery mode. (See crbug.com/1129165)
3130 """
Garry Wanga8739cc2020-10-30 00:49:23 -07003131 has_battery = True
3132 # Determine if the host has battery based on host_info first.
3133 power_info = self.host_info_store.get().get_label_value('power')
3134 if power_info:
3135 has_battery = power_info == 'battery'
3136 elif self.is_up_fast():
3137 # when running local tests host_info is not available, so we
3138 # need to determine whether the host has battery by checking
3139 # from host side.
3140 logging.debug('Label `power` is not found in host_info, checking'
3141 ' if the host has battery from host side.')
3142 has_battery = self.has_battery()
3143
3144 if not has_battery:
Garry Wang53fc8f32020-09-18 13:30:08 -07003145 logging.info(
3146 '%s does not has battery, snk mode is not needed'
3147 ' for recovery.', self.hostname)
3148 return False
Garry Wanga8739cc2020-10-30 00:49:23 -07003149
Garry Wang53fc8f32020-09-18 13:30:08 -07003150 if not self.servo.supports_built_in_pd_control():
3151 logging.info('Power delivery is not supported on this servo, snk'
3152 ' mode is not needed for recovery.')
3153 return False
3154 try:
Garry Wang53fc8f32020-09-18 13:30:08 -07003155 battery_percent = self.servo.get('battery_charge_percent')
Otabek Kasimov58e22562020-11-03 17:17:41 -08003156 if battery_percent < cros_constants.MIN_BATTERY_LEVEL:
Garry Wang53fc8f32020-09-18 13:30:08 -07003157 logging.info(
3158 'Current battery level %s%% below %s%% threshold, we'
3159 ' will attempt to boot host in recovery mode without'
3160 ' changing servo to snk mode. Please note the host may'
3161 ' not able to see usb drive in recovery mode later due'
3162 ' to servo not in snk mode.', battery_percent,
Otabek Kasimov58e22562020-11-03 17:17:41 -08003163 cros_constants.MIN_BATTERY_LEVEL)
Garry Wang53fc8f32020-09-18 13:30:08 -07003164 return False
3165 except Exception as e:
3166 logging.info(
3167 'Unexpected error occurred when getting'
3168 ' battery_charge_percent from servo; %s', str(e))
3169 return False
3170 return True
Otabek Kasimov382c3bb2020-10-28 13:22:45 -07003171
3172 def _set_servo_topology(self):
3173 """Set servo-topology info to the host-info."""
3174 logging.debug('Try to save servo topology to host-info.')
3175 if not self._servo_host:
3176 logging.info('Servo host is not initilized.')
3177 return
3178 if not self._servo_host.is_servo_topology_supported():
3179 logging.info('Servo-topology is not supported.')
3180 return
3181 servo_topology = self._servo_host.get_topology()
3182 if not servo_topology or servo_topology.is_empty():
3183 logging.info('Servo topology is empty')
3184 return
3185 servo_topology.save(self.host_info_store)