blob: 8408864daf7827ace561f2f46888e820c69bdb15 [file] [log] [blame]
J. Richard Barnette24adbf42012-04-11 15:04:53 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Dale Curtisaa5eedb2011-08-23 16:18:52 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
J. Richard Barnette1d78b012012-05-15 13:56:30 -07005import logging
Dan Shi0f466e82013-02-22 15:44:58 -08006import os
Simran Basid5e5e272012-09-24 15:23:59 -07007import re
Vincent Palatindf2372c2016-10-07 17:03:00 +02008import sys
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07009import time
10
mussa584b4462014-06-20 15:13:28 -070011import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070012from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070013from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080014from autotest_lib.client.common_lib import error
15from autotest_lib.client.common_lib import global_config
J. Richard Barnette91137f02016-03-10 16:52:26 -080016from autotest_lib.client.common_lib import hosts
Dan Shi549fb822015-03-24 18:01:11 -070017from autotest_lib.client.common_lib import lsbrelease_utils
Richard Barnette03a0c132012-11-05 12:40:35 -080018from autotest_lib.client.common_lib.cros import dev_server
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -070019from autotest_lib.client.common_lib.cros import retry
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000020from autotest_lib.client.cros import constants as client_constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080021from autotest_lib.client.cros import cros_ui
Simran Basi5ace6f22016-01-06 17:30:44 -080022from autotest_lib.server import afe_utils
Anh Le3ad780d2019-05-28 10:54:51 -070023from autotest_lib.server import crashcollect
Dan Shia1ecd5c2013-06-06 11:21:31 -070024from autotest_lib.server import utils as server_utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070025from autotest_lib.server.cros import provision
Scott Zawalski89c44dd2013-02-26 09:28:02 -050026from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070027from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
Scottfe06ed82015-11-05 17:15:01 -080028from autotest_lib.server.cros.servo import plankton
Fang Deng96667ca2013-08-01 17:46:18 -070029from autotest_lib.server.hosts import abstract_ssh
Kevin Chenga2619dc2016-03-28 11:42:08 -070030from autotest_lib.server.hosts import base_label
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +080031from autotest_lib.server.hosts import chameleon_host
Richard Barnetted31580e2018-05-14 19:58:00 +000032from autotest_lib.server.hosts import cros_label
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -080033from autotest_lib.server.hosts import cros_repair
Scottfe06ed82015-11-05 17:15:01 -080034from autotest_lib.server.hosts import plankton_host
Fang Deng5d518f42013-08-02 14:04:32 -070035from autotest_lib.server.hosts import servo_host
Simran Basidcff4252012-11-20 16:13:20 -080036from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070037
Simran Basi382506b2016-09-13 14:58:15 -070038# In case cros_host is being ran via SSP on an older Moblab version with an
39# older chromite version.
40try:
41 from chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080042except ImportError:
Congbin Guo42427612019-02-12 10:22:06 -080043 metrics = utils.metrics_mock
Dan Shi5e2efb72017-02-07 11:40:23 -080044
Simran Basid5e5e272012-09-24 15:23:59 -070045
Dan Shib8540a52015-07-16 14:18:23 -070046CONFIG = global_config.global_config
47
Dan Shid07ee2e2015-09-24 14:49:25 -070048
beepsc87ff602013-07-31 21:53:00 -070049class FactoryImageCheckerException(error.AutoservError):
50 """Exception raised when an image is a factory image."""
51 pass
52
53
Fang Deng0ca40e22013-08-27 17:47:44 -070054class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070055 """Chromium OS specific subclass of Host."""
56
Simran Basi5ace6f22016-01-06 17:30:44 -080057 VERSION_PREFIX = provision.CROS_VERSION_PREFIX
58
Scott Zawalski62bacae2013-03-05 10:40:32 -050059 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070060
Richard Barnette03a0c132012-11-05 12:40:35 -080061 # Timeout values (in seconds) associated with various Chrome OS
62 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070063 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080064 # In general, a good rule of thumb is that the timeout can be up
65 # to twice the typical measured value on the slowest platform.
66 # The times here have not necessarily been empirically tested to
67 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070068 #
69 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080070 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
71 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080072 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070073 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080074 # other things, this must account for the 30 second dev-mode
J. Richard Barnette417cc792015-10-01 09:56:36 -070075 # screen delay, time to start the network on the DUT, and the
76 # ssh timeout of 120 seconds.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070077 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080078 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080079 # network.
beepsf079cfb2013-09-18 17:49:51 -070080 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080081 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
82 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070083
84 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080085 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +080086 SHUTDOWN_TIMEOUT = 10
J. Richard Barnette417cc792015-10-01 09:56:36 -070087 BOOT_TIMEOUT = 150
J. Richard Barnette5bab5f52015-08-03 13:14:38 -070088 USB_BOOT_TIMEOUT = 300
J. Richard Barnette7817b052014-08-28 09:47:29 -070089 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -080090 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -070091
Dan Shica503482015-03-30 17:23:25 -070092 # Minimum OS version that supports server side packaging. Older builds may
93 # not have server side package built or with Autotest code change to support
94 # server-side packaging.
Dan Shib8540a52015-07-16 14:18:23 -070095 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value(
Dan Shiced09e42015-04-17 16:09:34 -070096 'AUTOSERV', 'min_version_support_ssp', type=int)
Dan Shica503482015-03-30 17:23:25 -070097
J. Richard Barnette84890bd2014-02-21 11:05:47 -080098 # REBOOT_TIMEOUT: How long to wait for a reboot.
99 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700100 # We have a long timeout to ensure we don't flakily fail due to other
101 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700102 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
103 # return from reboot' bug is solved.
104 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700105
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800106 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
107 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
Garry Wang5e5538a2019-04-08 15:36:18 -0700108 # _CHANGE_SERVO_ROLE_TIMEOUT: Time to allow DUT regain network connection
109 # since changing servo role will reset USB state
110 # and causes temporary ethernet drop.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800111 _USB_POWER_TIMEOUT = 5
112 _POWER_CYCLE_TIMEOUT = 10
Garry Wang5e5538a2019-04-08 15:36:18 -0700113 _CHANGE_SERVO_ROLE_TIMEOUT = 180
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800114
Fang Dengdeba14f2014-11-14 11:54:09 -0800115 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
116 '-host(\d+)')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700117
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800118 # Constants used in ping_wait_up() and ping_wait_down().
119 #
120 # _PING_WAIT_COUNT is the approximate number of polling
121 # cycles to use when waiting for a host state change.
122 #
123 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
124 # for arguments to the internal _ping_wait_for_status()
125 # method.
126 _PING_WAIT_COUNT = 40
127 _PING_STATUS_DOWN = False
128 _PING_STATUS_UP = True
129
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800130 # Allowed values for the power_method argument.
131
Garry Wang5e5538a2019-04-08 15:36:18 -0700132 # POWER_CONTROL_RPM: Used in power_off/on/cycle() methods, default for all
133 # DUTs except those with servo_v4 CCD.
134 # POWER_CONTROL_CCD: Used in power_off/on/cycle() methods, default for all
135 # DUTs with servo_v4 CCD.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800136 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
137 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
138 POWER_CONTROL_RPM = 'RPM'
Garry Wang5e5538a2019-04-08 15:36:18 -0700139 POWER_CONTROL_CCD = 'CCD'
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800140 POWER_CONTROL_SERVO = 'servoj10'
141 POWER_CONTROL_MANUAL = 'manual'
142
143 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
Garry Wang5e5538a2019-04-08 15:36:18 -0700144 POWER_CONTROL_CCD,
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800145 POWER_CONTROL_SERVO,
146 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800147
Garry Wang5e5538a2019-04-08 15:36:18 -0700148 # CCD_SERVO: The string of servo type to compare with the return value of
149 # self.servo.get_servo_version() to decide default power method.
150 CCD_SERVO = 'servo_v4_with_ccd_cr50'
151
Simran Basi5e6339a2013-03-21 11:34:32 -0700152 _RPM_OUTLET_CHANGED = 'outlet_changed'
153
Dan Shi9cb0eec2014-06-03 09:04:50 -0700154 # URL pattern to download firmware image.
Dan Shib8540a52015-07-16 14:18:23 -0700155 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value(
Dan Shi9cb0eec2014-06-03 09:04:50 -0700156 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700157
Anh Le3ad780d2019-05-28 10:54:51 -0700158 # DUT_LOG_LOCATION: the directory in the DUT that the log is saved
159 # after re-imaging using chromeos-install.
160 # The location is specified in chromeos-install script.
161 DUT_LOG_LOCATION = '/mnt/stateful_partition/unencrypted/prior_logs'
J. Richard Barnette91137f02016-03-10 16:52:26 -0800162
J. Richard Barnette964fba02012-10-24 17:34:29 -0700163 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800164 def check_host(host, timeout=10):
165 """
166 Check if the given host is a chrome-os host.
167
168 @param host: An ssh host representing a device.
169 @param timeout: The timeout for the run command.
170
171 @return: True if the host device is chromeos.
172
beeps46dadc92013-11-07 14:07:10 -0800173 """
174 try:
Allen Liad719c12017-06-27 23:48:04 +0000175 result = host.run(
Simran Basi933c8af2015-04-29 14:05:07 -0700176 'grep -q CHROMEOS /etc/lsb-release && '
177 '! test -f /mnt/stateful_partition/.android_tester && '
178 '! grep -q moblab /etc/lsb-release',
179 ignore_status=True, timeout=timeout)
Laurence Goodby468de252017-06-08 17:22:53 -0700180 if result.exit_status == 0:
Allen Liad719c12017-06-27 23:48:04 +0000181 lsb_release_content = host.run(
Laurence Goodby468de252017-06-08 17:22:53 -0700182 'grep CHROMEOS_RELEASE_BOARD /etc/lsb-release',
183 timeout=timeout).stdout
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -0800184 return not (
185 lsbrelease_utils.is_jetstream(
186 lsb_release_content=lsb_release_content) or
187 lsbrelease_utils.is_gce_board(
188 lsb_release_content=lsb_release_content))
189
beeps46dadc92013-11-07 14:07:10 -0800190 except (error.AutoservRunError, error.AutoservSSHTimeout):
191 return False
Laurence Goodby468de252017-06-08 17:22:53 -0700192
193 return False
beeps46dadc92013-11-07 14:07:10 -0800194
195
196 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800197 def get_chameleon_arguments(args_dict):
198 """Extract chameleon options from `args_dict` and return the result.
199
200 Recommended usage:
201 ~~~~~~~~
202 args_dict = utils.args_to_dict(args)
203 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
204 host = hosts.create_host(machine, chameleon_args=chameleon_args)
205 ~~~~~~~~
206
207 @param args_dict Dictionary from which to extract the chameleon
208 arguments.
209 """
Allen Li083866b2016-08-18 10:07:10 -0700210 return {key: args_dict[key]
211 for key in ('chameleon_host', 'chameleon_port')
212 if key in args_dict}
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800213
214
215 @staticmethod
Scottfe06ed82015-11-05 17:15:01 -0800216 def get_plankton_arguments(args_dict):
217 """Extract chameleon options from `args_dict` and return the result.
218
219 Recommended usage:
220 ~~~~~~~~
221 args_dict = utils.args_to_dict(args)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700222 plankton_args = hosts.CrosHost.get_plankton_arguments(args_dict)
223 host = hosts.create_host(machine, plankton_args=plankton_args)
Scottfe06ed82015-11-05 17:15:01 -0800224 ~~~~~~~~
225
226 @param args_dict Dictionary from which to extract the plankton
227 arguments.
228 """
Allen Li083866b2016-08-18 10:07:10 -0700229 return {key: args_dict[key]
230 for key in ('plankton_host', 'plankton_port')
231 if key in args_dict}
Scottfe06ed82015-11-05 17:15:01 -0800232
233
234 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800235 def get_servo_arguments(args_dict):
236 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800237
238 Recommended usage:
239 ~~~~~~~~
240 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700241 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800242 host = hosts.create_host(machine, servo_args=servo_args)
243 ~~~~~~~~
244
245 @param args_dict Dictionary from which to extract the servo
246 arguments.
247 """
Richard Barnettee519dcd2016-08-15 17:37:17 -0700248 servo_attrs = (servo_host.SERVO_HOST_ATTR,
249 servo_host.SERVO_PORT_ATTR,
Nick Sanders2f3c9852018-10-24 12:10:24 -0700250 servo_host.SERVO_BOARD_ATTR,
251 servo_host.SERVO_MODEL_ATTR)
Armando Miraglia2ac802e2017-08-15 14:54:47 +0200252 servo_args = {key: args_dict[key]
253 for key in servo_attrs
254 if key in args_dict}
255 return (
256 None
257 if servo_host.SERVO_HOST_ATTR in servo_args
258 and not servo_args[servo_host.SERVO_HOST_ATTR]
259 else servo_args)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700260
J. Richard Barnette964fba02012-10-24 17:34:29 -0700261
J. Richard Barnette91137f02016-03-10 16:52:26 -0800262 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
263 plankton_args=None, try_lab_servo=False,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700264 try_servo_repair=False,
J. Richard Barnette91137f02016-03-10 16:52:26 -0800265 ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700266 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800267 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700268
Fang Denge545abb2014-12-30 18:43:47 -0800269 This method will attempt to create the test-assistant object
270 (chameleon/servo) when it is needed by the test. Check
271 the docstring of chameleon_host.create_chameleon_host and
272 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700273
Fang Denge545abb2014-12-30 18:43:47 -0800274 @param hostname: Hostname of the dut.
275 @param chameleon_args: A dictionary that contains args for creating
276 a ChameleonHost. See chameleon_host for details.
277 @param servo_args: A dictionary that contains args for creating
278 a ServoHost object. See servo_host for details.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700279 @param try_lab_servo: When true, indicates that an attempt should
280 be made to create a ServoHost for a DUT in
281 the test lab, even if not required by
282 `servo_args`. See servo_host for details.
283 @param try_servo_repair: If a servo host is created, check it
284 with `repair()` rather than `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800285 See servo_host for details.
286 @param ssh_verbosity_flag: String, to pass to the ssh command to control
287 verbosity.
288 @param ssh_options: String, other ssh options to pass to the ssh
289 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700290 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700291 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700292 *args, **dargs)
J. Richard Barnette91137f02016-03-10 16:52:26 -0800293 self._repair_strategy = cros_repair.create_cros_repair_strategy()
Kevin Chenga2619dc2016-03-28 11:42:08 -0700294 self.labels = base_label.LabelRetriever(cros_label.CROS_LABELS)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700295 # self.env is a dictionary of environment variable settings
296 # to be exported for commands run on the host.
297 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
298 # errors that might happen.
299 self.env['LIBC_FATAL_STDERR_'] = '1'
Fang Dengd1c2b732013-08-20 12:59:46 -0700300 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700301 self._ssh_options = ssh_options
Richard Barnette4aeb01c2018-09-20 09:36:12 -0700302 self.set_servo_host(
303 servo_host.create_servo_host(
Richard Barnetteea3e4602016-06-10 12:36:41 -0700304 dut=self, servo_args=servo_args,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700305 try_lab_servo=try_lab_servo,
Richard Barnette4aeb01c2018-09-20 09:36:12 -0700306 try_servo_repair=try_servo_repair))
Garry Wang5e5538a2019-04-08 15:36:18 -0700307 self._default_power_method = None
Richard Barnettee519dcd2016-08-15 17:37:17 -0700308
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800309 # TODO(waihong): Do the simplication on Chameleon too.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800310 self._chameleon_host = chameleon_host.create_chameleon_host(
311 dut=self.hostname, chameleon_args=chameleon_args)
Scottfe06ed82015-11-05 17:15:01 -0800312 # Add plankton host if plankton args were added on command line
313 self._plankton_host = plankton_host.create_plankton_host(plankton_args)
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800314
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800315 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800316 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800317 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800318 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700319
Scottfe06ed82015-11-05 17:15:01 -0800320 if self._plankton_host:
321 self.plankton_servo = self._plankton_host.get_servo()
322 logging.info('plankton_servo: %r', self.plankton_servo)
323 # Create the plankton object used to access the ec uart
Scott07a848f2016-01-12 15:04:52 -0800324 self.plankton = plankton.Plankton(self.plankton_servo,
325 self._plankton_host.get_servod_server_proxy())
Scottfe06ed82015-11-05 17:15:01 -0800326 else:
Scott07a848f2016-01-12 15:04:52 -0800327 self.plankton = None
Scottfe06ed82015-11-05 17:15:01 -0800328
Fang Deng5d518f42013-08-02 14:04:32 -0700329
Richard Barnetteb4b4d6f2018-05-05 01:47:41 +0000330 def get_cros_repair_image_name(self):
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800331 info = self.host_info_store.get()
Allen Li87325182018-09-14 12:43:21 -0700332 if not info.board:
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800333 raise error.AutoservError('Cannot obtain repair image name. '
334 'No board label value found')
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800335 return afe_utils.get_stable_cros_image_name(info.board)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500336
337
Rohit Makasanadf0a3a32017-06-30 13:55:18 -0700338 def host_version_prefix(self, image):
339 """Return version label prefix.
340
341 In case the CrOS provisioning version is something other than the
342 standard CrOS version e.g. CrOS TH version, this function will
343 find the prefix from provision.py.
344
345 @param image: The image name to find its version prefix.
346 @returns: A prefix string for the image type.
347 """
348 return provision.get_version_label_prefix(image)
349
350
beepsdae65fd2013-07-26 16:24:41 -0700351 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700352 """
353 Make sure job_repo_url of this host is valid.
354
joychen03eaad92013-06-26 09:55:21 -0700355 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700356 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
357 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
358 download and extract it. If the devserver embedded in the url is
359 unresponsive, update the job_repo_url of the host after staging it on
360 another devserver.
361
362 @param job_repo_url: A url pointing to the devserver where the autotest
363 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700364 @param tag: The tag from the server job, in the format
365 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700366
367 @raises DevServerException: If we could not resolve a devserver.
368 @raises AutoservError: If we're unable to save the new job_repo_url as
369 a result of choosing a new devserver because the old one failed to
370 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700371 @raises urllib2.URLError: If the devserver embedded in job_repo_url
372 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700373 """
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800374 info = self.host_info_store.get()
375 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
beepscb6f1e22013-06-28 19:14:10 -0700376 if not job_repo_url:
377 logging.warning('No job repo url set on host %s', self.hostname)
378 return
379
380 logging.info('Verifying job repo url %s', job_repo_url)
381 devserver_url, image_name = tools.get_devserver_build_from_package_url(
382 job_repo_url)
383
beeps0c865032013-07-30 11:37:06 -0700384 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700385
386 logging.info('Staging autotest artifacts for %s on devserver %s',
387 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700388
389 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700390 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700391 stage_time = time.time() - start_time
392
393 # Record how much of the verification time comes from a devserver
394 # restage. If we're doing things right we should not see multiple
395 # devservers for a given board/build/branch path.
396 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800397 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700398 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800399 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700400 pass
401 else:
beeps0c865032013-07-30 11:37:06 -0700402 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700403 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700404 stats_key = {
405 'board': board,
406 'build_type': build_type,
407 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700408 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700409 }
Dan Shi5e2efb72017-02-07 11:40:23 -0800410
411 monarch_fields = {
412 'board': board,
413 'build_type': build_type,
414 # TODO(akeshet): To be consistent with most other metrics,
415 # consider changing the following field to be named
416 # 'milestone'.
417 'branch': branch,
418 'dev_server': devserver,
419 }
420 metrics.Counter(
421 'chromeos/autotest/provision/verify_url'
422 ).increment(fields=monarch_fields)
423 metrics.SecondsDistribution(
424 'chromeos/autotest/provision/verify_url_duration'
425 ).add(stage_time, fields=monarch_fields)
426
427
Dan Shicf4d2032015-03-12 15:04:21 -0700428 def stage_server_side_package(self, image=None):
429 """Stage autotest server-side package on devserver.
430
431 @param image: Full path of an OS image to install or a build name.
432
433 @return: A url to the autotest server-side package.
Dan Shi14de7622016-08-22 11:09:06 -0700434
435 @raise: error.AutoservError if fail to locate the build to test with, or
436 fail to stage server-side package.
Dan Shicf4d2032015-03-12 15:04:21 -0700437 """
Dan Shid37736b2016-07-06 15:10:29 -0700438 # If enable_drone_in_restricted_subnet is False, do not set hostname
439 # in devserver.resolve call, so a devserver in non-restricted subnet
440 # is picked to stage autotest server package for drone to download.
441 hostname = self.hostname
442 if not server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
443 hostname = None
Dan Shicf4d2032015-03-12 15:04:21 -0700444 if image:
445 image_name = tools.get_build_from_image(image)
446 if not image_name:
447 raise error.AutoservError(
448 'Failed to parse build name from %s' % image)
Dan Shid37736b2016-07-06 15:10:29 -0700449 ds = dev_server.ImageServer.resolve(image_name, hostname)
Dan Shicf4d2032015-03-12 15:04:21 -0700450 else:
Prathmesh Prabhu9235e4c2017-03-28 13:16:06 -0700451 info = self.host_info_store.get()
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800452 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
Dan Shicf4d2032015-03-12 15:04:21 -0700453 if job_repo_url:
454 devserver_url, image_name = (
455 tools.get_devserver_build_from_package_url(job_repo_url))
Dan Shid37736b2016-07-06 15:10:29 -0700456 # If enable_drone_in_restricted_subnet is True, use the
457 # existing devserver. Otherwise, resolve a new one in
458 # non-restricted subnet.
459 if server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
460 ds = dev_server.ImageServer(devserver_url)
461 else:
462 ds = dev_server.ImageServer.resolve(image_name)
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800463 elif info.build is not None:
464 ds = dev_server.ImageServer.resolve(info.build, hostname)
Prathmesh Prabhu0c1dd4d2017-06-07 13:01:53 -0700465 image_name = info.build
Dan Shicf4d2032015-03-12 15:04:21 -0700466 else:
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800467 raise error.AutoservError(
468 'Failed to stage server-side package. The host has '
469 'no job_report_url attribute or version label.')
Dan Shica503482015-03-30 17:23:25 -0700470
471 # Get the OS version of the build, for any build older than
472 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
473 match = re.match('.*/R\d+-(\d+)\.', image_name)
474 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
Dan Shi14de7622016-08-22 11:09:06 -0700475 raise error.AutoservError(
476 'Build %s is older than %s. Server side packaging is '
477 'disabled.' % (image_name, self.MIN_VERSION_SUPPORT_SSP))
Dan Shica503482015-03-30 17:23:25 -0700478
Dan Shicf4d2032015-03-12 15:04:21 -0700479 ds.stage_artifacts(image_name, ['autotest_server_package'])
480 return '%s/static/%s/%s' % (ds.url(), image_name,
481 'autotest_server_package.tar.bz2')
482
483
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700484 def stage_image_for_servo(self, image_name=None, artifact='test_image'):
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700485 """Stage a build on a devserver and return the update_url.
486
487 @param image_name: a name like lumpy-release/R27-3837.0.0
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700488 @param artifact: a string like 'test_image'. Requests
489 appropriate image to be staged.
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700490 @returns an update URL like:
491 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
492 """
493 if not image_name:
Richard Barnetteb4b4d6f2018-05-05 01:47:41 +0000494 image_name = self.get_cros_repair_image_name()
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700495 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800496 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
Kalin Stoyanovb3c11f32018-05-11 09:02:00 -0700497 devserver.stage_artifacts(image_name, [artifact])
498 if artifact == 'test_image':
499 return devserver.get_test_image_url(image_name)
500 elif artifact == 'recovery_image':
501 return devserver.get_recovery_image_url(image_name)
502 else:
503 raise error.AutoservError("Bad artifact!")
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700504
505
beepse539be02013-07-31 21:57:39 -0700506 def stage_factory_image_for_servo(self, image_name):
507 """Stage a build on a devserver and return the update_url.
508
509 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700510
beepse539be02013-07-31 21:57:39 -0700511 @return: An update URL, eg:
512 http://<devserver>/static/canary-channel/\
513 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700514
515 @raises: ValueError if the factory artifact name is missing from
516 the config.
517
beepse539be02013-07-31 21:57:39 -0700518 """
519 if not image_name:
520 logging.error('Need an image_name to stage a factory image.')
521 return
522
Dan Shib8540a52015-07-16 14:18:23 -0700523 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700524 'CROS', 'factory_artifact', type=str, default='')
525 if not factory_artifact:
526 raise ValueError('Cannot retrieve the factory artifact name from '
527 'autotest config, and hence cannot stage factory '
528 'artifacts.')
529
beepse539be02013-07-31 21:57:39 -0700530 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800531 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
beepse539be02013-07-31 21:57:39 -0700532 devserver.stage_artifacts(
533 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700534 [factory_artifact],
535 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700536
537 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
538
539
Laurence Goodby778c9a42017-05-24 19:24:07 -0700540 def prepare_for_update(self):
541 """Prepares the DUT for an update.
542
543 Subclasses may override this to perform any special actions
544 required before updating.
545 """
Laurence Goodby468de252017-06-08 17:22:53 -0700546 pass
Laurence Goodby778c9a42017-05-24 19:24:07 -0700547
548
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800549 def _clear_fw_version_labels(self, rw_only):
550 """Clear firmware version labels from the machine.
551
552 @param rw_only: True to only clear fwrw_version; otherewise, clear
553 both fwro_version and fwrw_version.
554 """
Dan Shi9cb0eec2014-06-03 09:04:50 -0700555 labels = self._AFE.get_labels(
Dan Shi0723bf52015-06-24 10:52:38 -0700556 name__startswith=provision.FW_RW_VERSION_PREFIX,
Dan Shi9cb0eec2014-06-03 09:04:50 -0700557 host__hostname=self.hostname)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800558 if not rw_only:
559 labels = labels + self._AFE.get_labels(
560 name__startswith=provision.FW_RO_VERSION_PREFIX,
561 host__hostname=self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700562 for label in labels:
563 label.remove_hosts(hosts=[self.hostname])
564
565
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800566 def _add_fw_version_label(self, build, rw_only):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700567 """Add firmware version label to the machine.
568
569 @param build: Build of firmware.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800570 @param rw_only: True to only add fwrw_version; otherwise, add both
571 fwro_version and fwrw_version.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700572
573 """
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000574 fw_label = provision.fwrw_version_to_label(build)
MK Ryu73be9862015-07-06 12:25:00 -0700575 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800576 if not rw_only:
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000577 fw_label = provision.fwro_version_to_label(build)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800578 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Dan Shi9cb0eec2014-06-03 09:04:50 -0700579
580
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800581 def firmware_install(self, build=None, rw_only=False):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700582 """Install firmware to the DUT.
583
584 Use stateful update if the DUT is already running the same build.
585 Stateful update does not update kernel and tends to run much faster
586 than a full reimage. If the DUT is running a different build, or it
587 failed to do a stateful update, full update, including kernel update,
588 will be applied to the DUT.
589
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800590 Once a host enters firmware_install its fw[ro|rw]_version label will
591 be removed. After the firmware is updated successfully, a new
592 fw[ro|rw]_version label will be added to the host.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700593
594 @param build: The build version to which we want to provision the
595 firmware of the machine,
596 e.g. 'link-firmware/R22-2695.1.144'.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800597 @param rw_only: True to only install firmware to its RW portions. Keep
598 the RO portions unchanged.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700599
600 TODO(dshi): After bug 381718 is fixed, update here with corresponding
601 exceptions that could be raised.
602
603 """
604 if not self.servo:
605 raise error.TestError('Host %s does not have servo.' %
606 self.hostname)
607
Wai-Hong Tam3fa455a2018-07-18 14:40:43 -0700608 # Get the DUT board name from AFE.
609 info = self.host_info_store.get()
610 board = info.board
Namyoon Woo8dbfcf92019-01-15 18:37:12 -0800611
612 if board is None or board == '':
613 board = self.servo.get_board()
Dan Shi9cb0eec2014-06-03 09:04:50 -0700614
Chris Sosae92399e2015-04-24 11:32:59 -0700615 # If build is not set, try to install firmware from stable CrOS.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700616 if not build:
Richard Barnette260cbd02016-10-06 12:23:28 -0700617 build = afe_utils.get_stable_faft_version(board)
Dan Shi3d7a0e12015-10-12 11:55:45 -0700618 if not build:
619 raise error.TestError(
620 'Failed to find stable firmware build for %s.',
621 self.hostname)
622 logging.info('Will install firmware from build %s.', build)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700623
Dan Shi216389c2015-12-22 11:03:06 -0800624 ds = dev_server.ImageServer.resolve(build, self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700625 ds.stage_artifacts(build, ['firmware'])
626
627 tmpd = autotemp.tempdir(unique_id='fwimage')
628 try:
629 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
630 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
xixuan4e116822016-11-17 15:32:10 -0800631 ds.download_file(fwurl, local_tarball)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700632
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800633 self._clear_fw_version_labels(rw_only)
Congbin Guo42427612019-02-12 10:22:06 -0800634 self.servo.program_firmware(board, local_tarball, rw_only)
Wai-Hong Tamb5f66ce2016-11-10 15:45:30 -0800635 if utils.host_is_in_lab_zone(self.hostname):
636 self._add_fw_version_label(build, rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700637 finally:
638 tmpd.clean()
639
640
beepsf079cfb2013-09-18 17:49:51 -0700641 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
642 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500643 """
644 Re-install the OS on the DUT by:
645 1) installing a test image on a USB storage device attached to the Servo
646 board,
Richard Barnette03a0c132012-11-05 12:40:35 -0800647 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700648 3) installing the image with chromeos-install.
649
Scott Zawalski62bacae2013-03-05 10:40:32 -0500650 @param image_url: If specified use as the url to install on the DUT.
651 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -0700652 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
653 Factory images need a longer usb_boot_timeout than regular
654 cros images.
655 @param install_timeout: The timeout to use when installing the chromeos
656 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -0800657
Scott Zawalski62bacae2013-03-05 10:40:32 -0500658 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -0700659
J. Richard Barnette0199cc82014-12-05 17:08:40 -0800660 """
beepsf079cfb2013-09-18 17:49:51 -0700661 logging.info('Downloading image to USB, then booting from it. Usb boot '
662 'timeout = %s', usb_boot_timeout)
Allen Li48a13fe2016-11-22 14:10:40 -0800663 with metrics.SecondsTimer(
664 'chromeos/autotest/provision/servo_install/boot_duration'):
665 self.servo.install_recovery_image(image_url)
666 if not self.wait_up(timeout=usb_boot_timeout):
667 raise hosts.AutoservRepairError(
668 'DUT failed to boot from USB after %d seconds' %
Garry Wang954f8382019-01-23 13:49:29 -0800669 usb_boot_timeout, 'failed_to_reboot')
Scott Zawalski62bacae2013-03-05 10:40:32 -0500670
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +0800671 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
672 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +0800673 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +0800674 try:
675 self.run('chromeos-tpm-recovery')
676 except error.AutoservRunError:
677 logging.warn('chromeos-tpm-recovery is too old.')
678
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +0800679
Allen Li48a13fe2016-11-22 14:10:40 -0800680 with metrics.SecondsTimer(
681 'chromeos/autotest/provision/servo_install/install_duration'):
682 logging.info('Installing image through chromeos-install.')
Anh Le3ad780d2019-05-28 10:54:51 -0700683 self.run(
684 'chromeos-install --yes '
685 '--lab_preserve_logs='
686 '"/usr/local/autotest/common_lib/logs_to_collect"',
687 timeout=install_timeout)
Allen Li48a13fe2016-11-22 14:10:40 -0800688 self.halt()
beepsf079cfb2013-09-18 17:49:51 -0700689
690 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -0800691 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -0700692 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -0800693 # N.B. The Servo API requires that we use power_on() here
694 # for two reasons:
695 # 1) After turning on a DUT in recovery mode, you must turn
696 # it off and then on with power_on() once more to
697 # disable recovery mode (this is a Parrot specific
698 # requirement).
699 # 2) After power_off(), the only way to turn on is with
700 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -0700701 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -0700702
703 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -0800704 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
705 raise error.AutoservError('DUT failed to reboot installed '
706 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -0500707 self.BOOT_TIMEOUT)
708
Anh Le3ad780d2019-05-28 10:54:51 -0700709 # The log saved after re-imaging process is transferred to shard
710 # result directory when the job instance exists.
711 # When we run repair manually, the result directory is created
712 # within local host.
713 try:
714 local_dir = crashcollect.get_crashinfo_dir(
715 self,
716 self.hostname + '_log'
717 )
718
719 self.collect_logs(self.DUT_LOG_LOCATION, local_dir)
720 except OSError:
721 logging.exception('Fail to collect log. '
722 'The destination log directory does not exist')
723
Scott Zawalski62bacae2013-03-05 10:40:32 -0500724
Richard Barnette4aeb01c2018-09-20 09:36:12 -0700725 def set_servo_host(self, host):
726 """Set our servo host member, and associated servo.
727
728 @param host Our new `ServoHost`.
729 """
730 self._servo_host = host
731 if self._servo_host is not None:
732 self.servo = self._servo_host.get_servo()
733 else:
734 self.servo = None
735
736
Richard Barnette9a26ad62016-06-10 12:03:08 -0700737 def repair_servo(self):
Dan Shi90466352015-09-22 15:01:05 -0700738 """
Richard Barnette9a26ad62016-06-10 12:03:08 -0700739 Confirm that servo is initialized and verified.
Dan Shi90466352015-09-22 15:01:05 -0700740
Richard Barnette9a26ad62016-06-10 12:03:08 -0700741 If the servo object is missing, attempt to repair the servo
742 host. Repair failures are passed back to the caller.
743
744 @raise AutoservError: If there is no servo host for this CrOS
745 host.
746 """
747 if self.servo:
748 return
749 if not self._servo_host:
750 raise error.AutoservError('No servo host for %s.' %
751 self.hostname)
752 self._servo_host.repair()
753 self.servo = self._servo_host.get_servo()
Dan Shi90466352015-09-22 15:01:05 -0700754
755
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800756 def repair(self):
757 """Attempt to get the DUT to pass `self.verify()`.
Richard Barnette82c35912012-11-20 10:09:10 -0800758
759 This overrides the base class function for repair; it does
J. Richard Barnette91137f02016-03-10 16:52:26 -0800760 not call back to the parent class, but instead relies on
761 `self._repair_strategy` to coordinate the verification and
762 repair steps needed to get the DUT working.
Richard Barnette82c35912012-11-20 10:09:10 -0800763 """
Richard Barnetteabbdc252018-07-26 16:57:42 -0700764 message = 'Beginning repair for host %s board %s model %s'
765 info = self.host_info_store.get()
766 message %= (self.hostname, info.board, info.model)
767 self.record('INFO', None, None, message)
J. Richard Barnette91137f02016-03-10 16:52:26 -0800768 self._repair_strategy.repair(self)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500769
Richard Barnette82c35912012-11-20 10:09:10 -0800770
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700771 def close(self):
David Rileye2c6be12017-12-11 10:20:57 -0800772 """Close connection."""
Fang Deng0ca40e22013-08-27 17:47:44 -0700773 super(CrosHost, self).close()
xixuand6011f12016-12-08 15:01:58 -0800774 if self._chameleon_host:
775 self._chameleon_host.close()
776
777 if self._servo_host:
778 self._servo_host.close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700779
780
Dan Shi49ca0932014-11-14 11:22:27 -0800781 def get_power_supply_info(self):
782 """Get the output of power_supply_info.
783
784 power_supply_info outputs the info of each power supply, e.g.,
785 Device: Line Power
786 online: no
787 type: Mains
788 voltage (V): 0
789 current (A): 0
790 Device: Battery
791 state: Discharging
792 percentage: 95.9276
793 technology: Li-ion
794
795 Above output shows two devices, Line Power and Battery, with details of
796 each device listed. This function parses the output into a dictionary,
797 with key being the device name, and value being a dictionary of details
798 of the device info.
799
800 @return: The dictionary of power_supply_info, e.g.,
801 {'Line Power': {'online': 'yes', 'type': 'main'},
802 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -0800803 @raise error.AutoservRunError if power_supply_info tool is not found in
804 the DUT. Caller should handle this error to avoid false failure
805 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -0800806 """
807 result = self.run('power_supply_info').stdout.strip()
808 info = {}
809 device_name = None
810 device_info = {}
811 for line in result.split('\n'):
812 pair = [v.strip() for v in line.split(':')]
813 if len(pair) != 2:
814 continue
815 if pair[0] == 'Device':
816 if device_name:
817 info[device_name] = device_info
818 device_name = pair[1]
819 device_info = {}
820 else:
821 device_info[pair[0]] = pair[1]
822 if device_name and not device_name in info:
823 info[device_name] = device_info
824 return info
825
826
827 def get_battery_percentage(self):
828 """Get the battery percentage.
829
830 @return: The percentage of battery level, value range from 0-100. Return
831 None if the battery info cannot be retrieved.
832 """
833 try:
834 info = self.get_power_supply_info()
835 logging.info(info)
836 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -0800837 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -0800838 return None
839
840
841 def is_ac_connected(self):
842 """Check if the dut has power adapter connected and charging.
843
844 @return: True if power adapter is connected and charging.
845 """
846 try:
847 info = self.get_power_supply_info()
848 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -0800849 except (KeyError, error.AutoservRunError):
850 return None
Dan Shi49ca0932014-11-14 11:22:27 -0800851
852
Simran Basi5e6339a2013-03-21 11:34:32 -0700853 def _cleanup_poweron(self):
854 """Special cleanup method to make sure hosts always get power back."""
Garry Wangad4d4fd2019-01-30 17:00:38 -0800855 info = self.host_info_store.get()
856 if self._RPM_OUTLET_CHANGED not in info.attributes:
Simran Basi5e6339a2013-03-21 11:34:32 -0700857 return
858 logging.debug('This host has recently interacted with the RPM'
859 ' Infrastructure. Ensuring power is on.')
860 try:
861 self.power_on()
Garry Wangad4d4fd2019-01-30 17:00:38 -0800862 self._remove_rpm_changed_tag()
Simran Basi5e6339a2013-03-21 11:34:32 -0700863 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -0700864 logging.error('Failed to turn Power On for this host after '
865 'cleanup through the RPM Infrastructure.')
Dan Shi49ca0932014-11-14 11:22:27 -0800866
867 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -0800868 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -0800869 raise
870 elif self.is_ac_connected():
871 logging.info('The device has power adapter connected and '
872 'charging. No need to try to turn RPM on '
873 'again.')
Garry Wangad4d4fd2019-01-30 17:00:38 -0800874 self._remove_rpm_changed_tag()
Dan Shi49ca0932014-11-14 11:22:27 -0800875 logging.info('Battery level is now at %s%%. The device may '
876 'still have enough power to run test, so no '
877 'exception will be raised.', battery_percentage)
878
Simran Basi5e6339a2013-03-21 11:34:32 -0700879
Garry Wangad4d4fd2019-01-30 17:00:38 -0800880 def _remove_rpm_changed_tag(self):
881 info = self.host_info_store.get()
882 del info.attributes[self._RPM_OUTLET_CHANGED]
883 self.host_info_store.commit(info)
884
885
886 def _add_rpm_changed_tag(self):
887 info = self.host_info_store.get()
Garry Wang518831d2019-02-21 15:15:36 -0800888 info.attributes[self._RPM_OUTLET_CHANGED] = 'true'
Garry Wangad4d4fd2019-01-30 17:00:38 -0800889 self.host_info_store.commit(info)
890
891
892
beepsc87ff602013-07-31 21:53:00 -0700893 def _is_factory_image(self):
894 """Checks if the image on the DUT is a factory image.
895
896 @return: True if the image on the DUT is a factory image.
897 False otherwise.
898 """
899 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
900 return result.exit_status == 0
901
902
903 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800904 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -0700905
906 @raises: FactoryImageCheckerException for factory images, since
907 we cannot attempt to restart ui on them.
908 error.AutoservRunError for any other type of error that
909 occurs while restarting ui.
910 """
911 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -0700912 raise FactoryImageCheckerException('Cannot restart ui on factory '
913 'images')
beepsc87ff602013-07-31 21:53:00 -0700914
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800915 # TODO(jrbarnette): The command to stop/start the ui job
916 # should live inside cros_ui, too. However that would seem
917 # to imply interface changes to the existing start()/restart()
918 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -0700919 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800920 self.run('stop ui; start ui')
921 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -0700922
923
Daniel Erat4b5f7e02018-07-04 22:05:30 -0700924 def _start_powerd_if_needed(self):
925 """Start powerd if it isn't already running."""
926 self.run('start powerd', ignore_status=True)
927
928
xixuana3bbc422017-05-04 15:57:21 -0700929 def _get_lsb_release_content(self):
930 """Return the content of lsb-release file of host."""
931 return self.run(
932 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
933
934
Dan Shi549fb822015-03-24 18:01:11 -0700935 def get_release_version(self):
936 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
937
938 @returns The version string in lsb-release, under attribute
939 CHROMEOS_RELEASE_VERSION.
940 """
Dan Shi549fb822015-03-24 18:01:11 -0700941 return lsbrelease_utils.get_chromeos_release_version(
xixuana3bbc422017-05-04 15:57:21 -0700942 lsb_release_content=self._get_lsb_release_content())
943
944
Don Garrettb9f35802018-01-22 18:25:40 -0800945 def get_release_builder_path(self):
946 """Get the value of CHROMEOS_RELEASE_BUILDER_PATH from lsb-release.
947
948 @returns The version string in lsb-release, under attribute
949 CHROMEOS_RELEASE_BUILDER_PATH.
950 """
951 return lsbrelease_utils.get_chromeos_release_builder_path(
952 lsb_release_content=self._get_lsb_release_content())
953
954
xixuana3bbc422017-05-04 15:57:21 -0700955 def get_chromeos_release_milestone(self):
956 """Get the value of attribute CHROMEOS_RELEASE_BUILD_TYPE
957 from lsb-release.
958
959 @returns The version string in lsb-release, under attribute
960 CHROMEOS_RELEASE_BUILD_TYPE.
961 """
962 return lsbrelease_utils.get_chromeos_release_milestone(
963 lsb_release_content=self._get_lsb_release_content())
Dan Shi549fb822015-03-24 18:01:11 -0700964
965
966 def verify_cros_version_label(self):
967 """ Make sure host's cros-version label match the actual image in dut.
968
969 Remove any cros-version: label that doesn't match that installed in
970 the dut.
971
972 @param raise_error: Set to True to raise exception if any mismatch found
973
974 @raise error.AutoservError: If any mismatch between cros-version label
975 and the build installed in dut is found.
976 """
977 labels = self._AFE.get_labels(
978 name__startswith=ds_constants.VERSION_PREFIX,
979 host__hostname=self.hostname)
980 mismatch_found = False
981 if labels:
Richard Barnette147dda42018-02-15 10:54:02 -0800982 # Ask the DUT for its canonical image name. This will be in
983 # a form like this: kevin-release/R66-10405.0.0
Don Garrettb9f35802018-01-22 18:25:40 -0800984 release_builder_path = self.get_release_builder_path()
Dan Shi549fb822015-03-24 18:01:11 -0700985 host_list = [self.hostname]
986 for label in labels:
987 # Remove any cros-version label that does not match
Richard Barnette147dda42018-02-15 10:54:02 -0800988 # the DUT's installed image.
989 #
Richard Barnettec92805b2018-06-26 14:07:07 -0700990 # TODO(jrbarnette): We make exceptions for certain
991 # known cases where the version label will not match the
992 # original CHROMEOS_RELEASE_BUILDER_PATH setting:
993 # * Tests for the `arc-presubmit` pool append
994 # "-cheetsth" to the label.
995 # * Moblab use cases based on `cros stage` store images
996 # under a name with the string "-custom" embedded.
997 # It's not reliable to match such an image name to the
998 # label.
999 label_version = label.name[len(ds_constants.VERSION_PREFIX):]
1000 if '-custom' in label_version:
1001 continue
1002 if label_version.endswith('-cheetsth'):
1003 label_version = label_version[:-len('-cheetsth')]
1004 if label_version != release_builder_path:
Don Garrettb9f35802018-01-22 18:25:40 -08001005 logging.warn(
Aviv Keshet3ccfc912019-04-10 16:50:49 -07001006 'version according to cros-version label "%s" does not '
1007 'match DUT-determined version %s. Removing the label.',
1008 label_version, release_builder_path)
Dan Shi549fb822015-03-24 18:01:11 -07001009 label.remove_hosts(hosts=host_list)
1010 mismatch_found = True
1011 if mismatch_found:
1012 raise error.AutoservError('The host has wrong cros-version label.')
1013
1014
Laurence Goodby778c9a42017-05-24 19:24:07 -07001015 def cleanup_services(self):
1016 """Reinitializes the device for cleanup.
1017
1018 Subclasses may override this to customize the cleanup method.
1019
1020 To indicate failure of the reset, the implementation may raise
1021 any of:
1022 error.AutoservRunError
1023 error.AutotestRunError
1024 FactoryImageCheckerException
1025
1026 @raises error.AutoservRunError
1027 @raises error.AutotestRunError
1028 @raises error.FactoryImageCheckerException
1029 """
1030 self._restart_ui()
Daniel Erat4b5f7e02018-07-04 22:05:30 -07001031 self._start_powerd_if_needed()
Laurence Goodby778c9a42017-05-24 19:24:07 -07001032
1033
beepsc87ff602013-07-31 21:53:00 -07001034 def cleanup(self):
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -08001035 """Cleanup state on device."""
MK Ryu35d661e2014-09-25 17:44:10 -07001036 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001037 try:
Laurence Goodby778c9a42017-05-24 19:24:07 -07001038 self.cleanup_services()
beepsc87ff602013-07-31 21:53:00 -07001039 except (error.AutotestRunError, error.AutoservRunError,
1040 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001041 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001042 # Since restarting the UI fails fall back to normal Autotest
1043 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001044 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001045 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001046 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001047 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001048 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001049
1050
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001051 def reboot(self, **dargs):
1052 """
1053 This function reboots the site host. The more generic
1054 RemoteHost.reboot() performs sync and sleeps for 5
1055 seconds. This is not necessary for Chrome OS devices as the
1056 sync should be finished in a short time during the reboot
1057 command.
1058 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001059 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001060 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001061 dargs['reboot_cmd'] = ('sleep 1; '
1062 'reboot & sleep %d; '
1063 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001064 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001065 if 'fastsync' not in dargs:
1066 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001067
Prathmesh Prabhu53a4c1c2018-09-14 16:36:27 -07001068 dargs['board'] = self.host_info_store.get().board
Vincent Palatindf2372c2016-10-07 17:03:00 +02001069 # Record who called us
1070 orig = sys._getframe(1).f_code
Vincent Palatin80780b22016-07-27 16:02:37 +02001071 metric_fields = {'board' : dargs['board'],
Vincent Palatindf2372c2016-10-07 17:03:00 +02001072 'dut_host_name' : self.hostname,
1073 'success' : True}
1074 metric_debug_fields = {'board' : dargs['board'],
Prathmesh Prabhu53a4c1c2018-09-14 16:36:27 -07001075 'caller' : "%s:%s" % (orig.co_filename,
1076 orig.co_name),
Vincent Palatindf2372c2016-10-07 17:03:00 +02001077 'success' : True,
1078 'error' : ''}
1079
Vincent Palatin80780b22016-07-27 16:02:37 +02001080 t0 = time.time()
1081 try:
1082 super(CrosHost, self).reboot(**dargs)
1083 except Exception as e:
1084 metric_fields['success'] = False
Vincent Palatindf2372c2016-10-07 17:03:00 +02001085 metric_debug_fields['success'] = False
1086 metric_debug_fields['error'] = type(e).__name__
Vincent Palatin80780b22016-07-27 16:02:37 +02001087 raise
1088 finally:
1089 duration = int(time.time() - t0)
Dan Shi5e2efb72017-02-07 11:40:23 -08001090 metrics.Counter(
1091 'chromeos/autotest/autoserv/reboot_count').increment(
1092 fields=metric_fields)
1093 metrics.Counter(
1094 'chromeos/autotest/autoserv/reboot_debug').increment(
1095 fields=metric_debug_fields)
1096 metrics.SecondsDistribution(
1097 'chromeos/autotest/autoserv/reboot_duration').add(
1098 duration, fields=metric_fields)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001099
1100
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001101 def suspend(self, suspend_time=60,
1102 suspend_cmd=None, allow_early_resume=False):
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001103 """
1104 This function suspends the site host.
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001105
1106 @param suspend_time: How long to suspend as integer seconds.
1107 @param suspend_cmd: Suspend command to execute.
1108 @param allow_early_resume: If False and if device resumes before
1109 |suspend_time|, throw an error.
1110
1111 @exception AutoservSuspendError Host resumed earlier than
1112 |suspend_time|.
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001113 """
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001114
1115 if suspend_cmd is None:
1116 suspend_cmd = ' && '.join([
J. Richard Barnette9af19632015-09-25 12:18:03 -07001117 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001118 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
J. Richard Barnette9af19632015-09-25 12:18:03 -07001119 'powerd_dbus_suspend --delay=0'])
Ravi Chandra Sadineni812e61b2018-07-09 11:16:50 -07001120 super(CrosHost, self).suspend(suspend_time, suspend_cmd,
1121 allow_early_resume);
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001122
1123
Simran Basiec564392014-08-25 16:48:09 -07001124 def upstart_status(self, service_name):
1125 """Check the status of an upstart init script.
1126
1127 @param service_name: Service to look up.
1128
1129 @returns True if the service is running, False otherwise.
1130 """
Richard Barnettee204dc52017-09-26 11:02:25 -07001131 return 'start/running' in self.run('status %s' % service_name,
1132 ignore_status=True).stdout
Simran Basiec564392014-08-25 16:48:09 -07001133
Tom Hughese9552342018-12-18 14:29:25 -08001134 def upstart_stop(self, service_name):
1135 """Stops an upstart job if it's running.
1136
1137 @param service_name: Service to stop
1138
1139 @returns True if service has been stopped or was already stopped
1140 False otherwise.
1141 """
1142 if not self.upstart_status(service_name):
1143 return True
1144
1145 result = self.run('stop %s' % service_name, ignore_status=True)
1146 if result.exit_status != 0:
1147 return False
1148 return True
1149
1150 def upstart_restart(self, service_name):
1151 """Restarts (or starts) an upstart job.
1152
1153 @param service_name: Service to start/restart
1154
1155 @returns True if service has been started/restarted, False otherwise.
1156 """
1157 cmd = 'start'
1158 if self.upstart_status(service_name):
1159 cmd = 'restart'
1160 cmd = cmd + ' %s' % service_name
1161 result = self.run(cmd)
1162 if result.exit_status != 0:
1163 return False
1164 return True
Simran Basiec564392014-08-25 16:48:09 -07001165
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001166 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001167 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001168
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001169 Tests for the following conditions:
1170 1. All conditions tested by the parent version of this
1171 function.
1172 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001173 3. Sufficient space in /mnt/stateful_partition/encrypted.
1174 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001175
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001176 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001177 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001178 default_kilo_inodes_required = CONFIG.get_config_value(
1179 'SERVER', 'kilo_inodes_required', type=int, default=100)
1180 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1181 kilo_inodes_required = CONFIG.get_config_value(
1182 'SERVER', 'kilo_inodes_required_%s' % board,
1183 type=int, default=default_kilo_inodes_required)
1184 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001185 self.check_diskspace(
1186 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001187 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001188 'SERVER', 'gb_diskspace_required', type=float,
1189 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001190 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1191 # Not all targets build with encrypted stateful support.
1192 if self.path_exists(encrypted_stateful_path):
1193 self.check_diskspace(
1194 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001195 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001196 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1197 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001198
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -07001199 self.wait_for_system_services()
Prashanth B5d0a0512014-04-25 12:26:08 -07001200
beepsc87ff602013-07-31 21:53:00 -07001201 # Factory images don't run update engine,
1202 # goofy controls dbus on these DUTs.
1203 if not self._is_factory_image():
1204 self.run('update_engine_client --status')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001205
Dan Shi549fb822015-03-24 18:01:11 -07001206 self.verify_cros_version_label()
1207
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001208
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -07001209 @retry.retry(error.AutoservError, timeout_min=5, delay_sec=10)
1210 def wait_for_system_services(self):
1211 """Waits for system-services to be running.
1212
1213 Sometimes, update_engine will take a while to update firmware, so we
1214 should give this some time to finish. See crbug.com/765686#c38 for
1215 details.
1216 """
1217 if not self.upstart_status('system-services'):
1218 raise error.AutoservError('Chrome failed to reach login. '
1219 'System services not running.')
1220
1221
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001222 def verify(self):
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -08001223 """Verify Chrome OS system is in good state."""
Richard Barnetteabbdc252018-07-26 16:57:42 -07001224 message = 'Beginning verify for host %s board %s model %s'
1225 info = self.host_info_store.get()
1226 message %= (self.hostname, info.board, info.model)
1227 self.record('INFO', None, None, message)
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001228 self._repair_strategy.verify(self)
1229
1230
Fang Deng96667ca2013-08-01 17:46:18 -07001231 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
Dean Liaoe3e75f62017-11-14 10:36:43 +08001232 connect_timeout=None, alive_interval=None,
1233 alive_count_max=None, connection_attempts=None):
Fang Deng96667ca2013-08-01 17:46:18 -07001234 """Override default make_ssh_command to use options tuned for Chrome OS.
1235
1236 Tuning changes:
1237 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1238 connection failure. Consistency with remote_access.sh.
1239
Samuel Tan2ce155b2015-06-23 18:24:38 -07001240 - ServerAliveInterval=900; which causes SSH to ping connection every
1241 900 seconds. In conjunction with ServerAliveCountMax ensures
1242 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001243 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001244 the test completed successfully. Later increased from 180 seconds to
1245 900 seconds to account for tests where the DUT is suspended for
1246 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001247
1248 - ServerAliveCountMax=3; consistency with remote_access.sh.
1249
1250 - ConnectAttempts=4; reduce flakiness in connection errors;
1251 consistency with remote_access.sh.
1252
1253 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1254 Host keys change with every new installation, don't waste
1255 memory/space saving them.
1256
1257 - SSH protocol forced to 2; needed for ServerAliveInterval.
1258
1259 @param user User name to use for the ssh connection.
1260 @param port Port on the target host to use for ssh connection.
1261 @param opts Additional options to the ssh command.
1262 @param hosts_file Ignored.
1263 @param connect_timeout Ignored.
1264 @param alive_interval Ignored.
Dean Liaoe3e75f62017-11-14 10:36:43 +08001265 @param alive_count_max Ignored.
1266 @param connection_attempts Ignored.
Fang Deng96667ca2013-08-01 17:46:18 -07001267 """
Dean Liaoe3e75f62017-11-14 10:36:43 +08001268 options = ' '.join([opts, '-o Protocol=2'])
1269 return super(CrosHost, self).make_ssh_command(
1270 user=user, port=port, opts=options, hosts_file='/dev/null',
1271 connect_timeout=30, alive_interval=900, alive_count_max=3,
1272 connection_attempts=4)
1273
1274
Jason Abeleb6f924f2013-11-13 16:01:54 -08001275 def syslog(self, message, tag='autotest'):
1276 """Logs a message to syslog on host.
1277
1278 @param message String message to log into syslog
1279 @param tag String tag prefix for syslog
1280
1281 """
1282 self.run('logger -t "%s" "%s"' % (tag, message))
1283
1284
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001285 def _ping_check_status(self, status):
1286 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001287
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001288 @param status Check the ping status against this value.
1289 @return True iff `status` and the result of ping are the same
1290 (i.e. both True or both False).
1291
1292 """
1293 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1294 return not (status ^ (ping_val == 0))
1295
1296 def _ping_wait_for_status(self, status, timeout):
1297 """Wait for the host to have a given status (UP or DOWN).
1298
1299 Status is checked by polling. Polling will not last longer
1300 than the number of seconds in `timeout`. The polling
1301 interval will be long enough that only approximately
1302 _PING_WAIT_COUNT polling cycles will be executed, subject
1303 to a maximum interval of about one minute.
1304
1305 @param status Waiting will stop immediately if `ping` of the
1306 host returns this status.
1307 @param timeout Poll for at most this many seconds.
1308 @return True iff the host status from `ping` matched the
1309 requested status at the time of return.
1310
1311 """
1312 # _ping_check_status() takes about 1 second, hence the
1313 # "- 1" in the formula below.
Nathan Ciobanu38480a32016-10-25 15:26:45 -07001314 # FIXME: if the ping command errors then _ping_check_status()
1315 # returns instantly. If timeout is also smaller than twice
1316 # _PING_WAIT_COUNT then the while loop below forks many
1317 # thousands of ping commands (see /tmp/test_that_results_XXXXX/
1318 # /results-1-logging_YYY.ZZZ/debug/autoserv.DEBUG) and hogs one
1319 # CPU core for 60 seconds.
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001320 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1321 end_time = time.time() + timeout
1322 while time.time() <= end_time:
1323 if self._ping_check_status(status):
1324 return True
1325 if poll_interval > 0:
1326 time.sleep(poll_interval)
1327
1328 # The last thing we did was sleep(poll_interval), so it may
1329 # have been too long since the last `ping`. Check one more
1330 # time, just to be sure.
1331 return self._ping_check_status(status)
1332
1333 def ping_wait_up(self, timeout):
1334 """Wait for the host to respond to `ping`.
1335
1336 N.B. This method is not a reliable substitute for
1337 `wait_up()`, because a host that responds to ping will not
1338 necessarily respond to ssh. This method should only be used
1339 if the target DUT can be considered functional even if it
1340 can't be reached via ssh.
1341
1342 @param timeout Minimum time to allow before declaring the
1343 host to be non-responsive.
1344 @return True iff the host answered to ping before the timeout.
1345
1346 """
1347 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001348
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001349 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001350 """Wait until the host no longer responds to `ping`.
1351
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001352 This function can be used as a slightly faster version of
1353 `wait_down()`, by avoiding potentially long ssh timeouts.
1354
1355 @param timeout Minimum time to allow for the host to become
1356 non-responsive.
1357 @return True iff the host quit answering ping before the
1358 timeout.
1359
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001360 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001361 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001362
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001363 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001364 """Wait for the client to enter low-power sleep mode.
1365
1366 The test for "is asleep" can't distinguish a system that is
1367 powered off; to confirm that the unit was asleep, it is
1368 necessary to force resume, and then call
1369 `test_wait_for_resume()`.
1370
1371 This function is expected to be called from a test as part
1372 of a sequence like the following:
1373
1374 ~~~~~~~~
1375 boot_id = host.get_boot_id()
1376 # trigger sleep on the host
1377 host.test_wait_for_sleep()
1378 # trigger resume on the host
1379 host.test_wait_for_resume(boot_id)
1380 ~~~~~~~~
1381
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001382 @param sleep_timeout time limit in seconds to allow the host sleep.
1383
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001384 @exception TestFail The host did not go to sleep within
1385 the allowed time.
1386 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001387 if sleep_timeout is None:
1388 sleep_timeout = self.SLEEP_TIMEOUT
1389
1390 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001391 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001392 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001393
1394
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001395 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001396 """Wait for the client to resume from low-power sleep mode.
1397
1398 The `old_boot_id` parameter should be the value from
1399 `get_boot_id()` obtained prior to entering sleep mode. A
1400 `TestFail` exception is raised if the boot id changes.
1401
1402 See @ref test_wait_for_sleep for more on this function's
1403 usage.
1404
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001405 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001406 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001407 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001408
1409 @exception TestFail The host did not respond within the
1410 allowed time.
1411 @exception TestFail The host responded, but the boot id test
1412 indicated a reboot rather than a sleep
1413 cycle.
1414 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001415 if resume_timeout is None:
1416 resume_timeout = self.RESUME_TIMEOUT
1417
1418 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001419 raise error.TestFail(
1420 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001421 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001422 else:
1423 new_boot_id = self.get_boot_id()
1424 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001425 logging.error('client rebooted (old boot %s, new boot %s)',
1426 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001427 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001428 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001429
1430
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001431 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001432 """Wait for the client to shut down.
1433
1434 The test for "has shut down" can't distinguish a system that
1435 is merely asleep; to confirm that the unit was down, it is
1436 necessary to force boot, and then call test_wait_for_boot().
1437
1438 This function is expected to be called from a test as part
1439 of a sequence like the following:
1440
1441 ~~~~~~~~
1442 boot_id = host.get_boot_id()
1443 # trigger shutdown on the host
1444 host.test_wait_for_shutdown()
1445 # trigger boot on the host
1446 host.test_wait_for_boot(boot_id)
1447 ~~~~~~~~
1448
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001449 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001450 @exception TestFail The host did not shut down within the
1451 allowed time.
1452 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001453 if shutdown_timeout is None:
1454 shutdown_timeout = self.SHUTDOWN_TIMEOUT
1455
1456 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001457 raise error.TestFail(
1458 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001459 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001460
1461
1462 def test_wait_for_boot(self, old_boot_id=None):
1463 """Wait for the client to boot from cold power.
1464
1465 The `old_boot_id` parameter should be the value from
1466 `get_boot_id()` obtained prior to shutting down. A
1467 `TestFail` exception is raised if the boot id does not
1468 change. The boot id test is omitted if `old_boot_id` is not
1469 specified.
1470
1471 See @ref test_wait_for_shutdown for more on this function's
1472 usage.
1473
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001474 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001475 shut down.
1476
1477 @exception TestFail The host did not respond within the
1478 allowed time.
1479 @exception TestFail The host responded, but the boot id test
1480 indicated that there was no reboot.
1481 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001482 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001483 raise error.TestFail(
1484 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001485 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001486 elif old_boot_id:
1487 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001488 logging.error('client not rebooted (boot %s)',
1489 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001490 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001491 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07001492
1493
1494 @staticmethod
1495 def check_for_rpm_support(hostname):
1496 """For a given hostname, return whether or not it is powered by an RPM.
1497
Simran Basi1df55112013-09-06 11:25:09 -07001498 @param hostname: hostname to check for rpm support.
1499
Simran Basid5e5e272012-09-24 15:23:59 -07001500 @return None if this host does not follows the defined naming format
1501 for RPM powered DUT's in the lab. If it does follow the format,
1502 it returns a regular expression MatchObject instead.
1503 """
Fang Dengbaff9082015-01-06 13:46:15 -08001504 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001505
1506
1507 def has_power(self):
1508 """For this host, return whether or not it is powered by an RPM.
1509
1510 @return True if this host is in the CROS lab and follows the defined
1511 naming format.
1512 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001513 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001514
1515
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001516 def _set_power(self, state, power_method):
Garry Wang5e5538a2019-04-08 15:36:18 -07001517 """Sets the power to the host via RPM, CCD, Servo or manual.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001518
1519 @param state Specifies which power state to set to DUT
1520 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07001521 use. By default "RPM" or "CCD" will be used based
1522 on servo type. Valid values from
1523 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001524
1525 """
1526 ACCEPTABLE_STATES = ['ON', 'OFF']
1527
Garry Wang5e5538a2019-04-08 15:36:18 -07001528 if not power_method:
1529 power_method = self.get_default_power_method()
1530
1531 state = state.upper()
1532 if state not in ACCEPTABLE_STATES:
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001533 raise error.TestError('State must be one of: %s.'
1534 % (ACCEPTABLE_STATES,))
1535
1536 if power_method == self.POWER_CONTROL_SERVO:
1537 logging.info('Setting servo port J10 to %s', state)
1538 self.servo.set('prtctl3_pwren', state.lower())
1539 time.sleep(self._USB_POWER_TIMEOUT)
1540 elif power_method == self.POWER_CONTROL_MANUAL:
1541 logging.info('You have %d seconds to set the AC power to %s.',
1542 self._POWER_CYCLE_TIMEOUT, state)
1543 time.sleep(self._POWER_CYCLE_TIMEOUT)
Garry Wang5e5538a2019-04-08 15:36:18 -07001544 elif power_method == self.POWER_CONTROL_CCD:
1545 servo_role = 'src' if state == 'ON' else 'snk'
1546 logging.info('servo ccd power pass through detected,'
1547 ' changing servo_role to %s.', servo_role)
1548 self.servo.set_servo_v4_role(servo_role)
1549 if not self.ping_wait_up(timeout=self._CHANGE_SERVO_ROLE_TIMEOUT):
Garry Wang94bf9de2019-06-10 17:23:37 -07001550 # Make sure we don't leave DUT with no power(servo_role=snk)
1551 # when DUT is not pingable, as we raise a exception here
1552 # that may break a power cycle in the middle.
1553 self.servo.set_servo_v4_role('src')
Garry Wang5e5538a2019-04-08 15:36:18 -07001554 raise error.AutoservError(
1555 'DUT failed to regain network connection after %d seconds.'
1556 % self._CHANGE_SERVO_ROLE_TIMEOUT)
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001557 else:
1558 if not self.has_power():
1559 raise error.TestFail('DUT does not have RPM connected.')
Garry Wangad4d4fd2019-01-30 17:00:38 -08001560 self._add_rpm_changed_tag()
Garry Wang5e5538a2019-04-08 15:36:18 -07001561 rpm_client.set_power(self, state, timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07001562
1563
Garry Wang5e5538a2019-04-08 15:36:18 -07001564 def power_off(self, power_method=None):
1565 """Turn off power to this host via RPM, CCD, Servo or manual.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001566
1567 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07001568 use. By default "RPM" or "CCD" will be used based
1569 on servo type. Valid values from
1570 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001571
1572 """
1573 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001574
1575
Garry Wang5e5538a2019-04-08 15:36:18 -07001576 def power_on(self, power_method=None):
1577 """Turn on power to this host via RPM, CCD, Servo or manual.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001578
1579 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07001580 use. By default "RPM" or "CCD" will be used based
1581 on servo type. Valid values from
1582 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001583
1584 """
1585 self._set_power('ON', power_method)
1586
1587
Garry Wang5e5538a2019-04-08 15:36:18 -07001588 def power_cycle(self, power_method=None):
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001589 """Cycle power to this host by turning it OFF, then ON.
1590
1591 @param power_method Specifies which method of power control to
Garry Wang5e5538a2019-04-08 15:36:18 -07001592 use. By default "RPM" or "CCD" will be used based
1593 on servo type. Valid values from
1594 POWER_CONTROL_VALID_ARGS, or None to use default.
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001595
1596 """
Garry Wang5e5538a2019-04-08 15:36:18 -07001597 if not power_method:
1598 power_method = self.get_default_power_method()
1599
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001600 if power_method in (self.POWER_CONTROL_SERVO,
Garry Wang5e5538a2019-04-08 15:36:18 -07001601 self.POWER_CONTROL_MANUAL,
1602 self.POWER_CONTROL_CCD):
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001603 self.power_off(power_method=power_method)
1604 time.sleep(self._POWER_CYCLE_TIMEOUT)
1605 self.power_on(power_method=power_method)
1606 else:
Garry Wangad4d4fd2019-01-30 17:00:38 -08001607 self._add_rpm_changed_tag()
1608 rpm_client.set_power(self, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001609
1610
1611 def get_platform(self):
1612 """Determine the correct platform label for this host.
1613
1614 @returns a string representing this host's platform.
1615 """
C Shapiroed87c6f2018-04-19 09:13:58 -06001616 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1617 run_method=self.run)
1618 unibuild = release_info.get('CHROMEOS_RELEASE_UNIBUILD') == '1'
1619 platform = ''
1620 if unibuild:
1621 cmd = 'mosys platform model'
1622 result = self.run(command=cmd, ignore_status=True)
1623 if result.exit_status == 0:
1624 platform = result.stdout.strip()
1625
1626 if not platform:
1627 # Look at the firmware for non-unibuild cases or if mosys fails.
C Shapiro218e8752017-09-22 11:10:57 -06001628 crossystem = utils.Crossystem(self)
1629 crossystem.init()
1630 # Extract fwid value and use the leading part as the platform id.
1631 # fwid generally follow the format of {platform}.{firmware version}
1632 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1633 platform = crossystem.fwid().split('.')[0].lower()
1634 # Newer platforms start with 'Google_' while the older ones do not.
C Shapiroed87c6f2018-04-19 09:13:58 -06001635 platform = platform.replace('google_', '')
1636 return platform
Simran Basic6f1f7a2012-10-16 10:47:46 -07001637
1638
Hung-ying Tyanb1328032014-04-01 14:18:54 +08001639 def get_architecture(self):
1640 """Determine the correct architecture label for this host.
1641
1642 @returns a string representing this host's architecture.
1643 """
1644 crossystem = utils.Crossystem(self)
1645 crossystem.init()
1646 return crossystem.arch()
1647
1648
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001649 def get_chrome_version(self):
1650 """Gets the Chrome version number and milestone as strings.
1651
1652 Invokes "chrome --version" to get the version number and milestone.
1653
1654 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
1655 current Chrome version number as a string (in the form "W.X.Y.Z")
1656 and "milestone" is the first component of the version number
1657 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
1658 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
1659 of "chrome --version" and the milestone will be the empty string.
1660
1661 """
MK Ryu35d661e2014-09-25 17:44:10 -07001662 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001663 return utils.parse_chrome_version(version_string)
1664
J. Richard Barnetted2af5852016-02-05 15:03:10 -08001665
Puthikorn Voravootivatfd132172017-11-16 18:24:38 -08001666 def get_ec_version(self):
1667 """Get the ec version as strings.
1668
1669 @returns a string representing this host's ec version.
1670 """
Puthikorn Voravootivat65ad7672018-01-30 14:00:01 -08001671 command = 'mosys ec info -s fw_version'
Puthikorn Voravootivat720640d2018-02-16 17:32:38 -08001672 result = self.run(command, ignore_status=True)
1673 if result.exit_status != 0:
1674 return ''
1675 return result.stdout.strip()
Puthikorn Voravootivatfd132172017-11-16 18:24:38 -08001676
1677
1678 def get_firmware_version(self):
1679 """Get the firmware version as strings.
1680
1681 @returns a string representing this host's firmware version.
1682 """
1683 crossystem = utils.Crossystem(self)
1684 crossystem.init()
1685 return crossystem.fwid()
1686
1687
1688 def get_hardware_revision(self):
1689 """Get the hardware revision as strings.
1690
1691 @returns a string representing this host's hardware revision.
1692 """
Puthikorn Voravootivat65ad7672018-01-30 14:00:01 -08001693 command = 'mosys platform version'
Puthikorn Voravootivat720640d2018-02-16 17:32:38 -08001694 result = self.run(command, ignore_status=True)
1695 if result.exit_status != 0:
1696 return ''
1697 return result.stdout.strip()
Puthikorn Voravootivatfd132172017-11-16 18:24:38 -08001698
1699
1700 def get_kernel_version(self):
1701 """Get the kernel version as strings.
1702
1703 @returns a string representing this host's kernel version.
1704 """
1705 return self.run('uname -r').stdout.strip()
1706
1707
Puthikorn Voravootivat54aa53c2018-03-01 19:00:25 -08001708 def get_cpu_name(self):
1709 """Get the cpu name as strings.
1710
1711 @returns a string representing this host's cpu name.
1712 """
1713
1714 # Try get cpu name from device tree first
1715 if self.path_exists('/proc/device-tree/compatible'):
1716 command = ' | '.join(
1717 ["sed -e 's/\\x0/\\n/g' /proc/device-tree/compatible",
1718 'tail -1'])
1719 return self.run(command).stdout.strip().replace(',', ' ')
1720
1721 # Get cpu name from uname -p
1722 command = 'uname -p'
1723 ret = self.run(command).stdout.strip()
1724
1725 # 'uname -p' return variant of unknown or amd64 or x86_64 or i686
1726 # Try get cpu name from /proc/cpuinfo instead
1727 if re.match("unknown|amd64|[ix][0-9]?86(_64)?", ret, re.IGNORECASE):
1728 command = "grep model.name /proc/cpuinfo | cut -f 2 -d: | head -1"
1729 self = self.run(command).stdout.strip()
1730
1731 # Remove bloat from CPU name, for example
1732 # Intel(R) Core(TM) i5-7Y57 CPU @ 1.20GHz -> Intel Core i5-7Y57
1733 # Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz -> Intel Xeon E5-2690 v4
1734 # AMD A10-7850K APU with Radeon(TM) R7 Graphics -> AMD A10-7850K
1735 # AMD GX-212JC SOC with Radeon(TM) R2E Graphics -> AMD GX-212JC
1736 trim_re = r' (@|processor|apu|soc|radeon).*|\(.*?\)| cpu'
1737 return re.sub(trim_re, '', ret, flags=re.IGNORECASE)
1738
1739
1740 def get_screen_resolution(self):
1741 """Get the screen(s) resolution as strings.
1742 In case of more than 1 monitor, return resolution for each monitor
1743 separate with plus sign.
1744
1745 @returns a string representing this host's screen(s) resolution.
1746 """
1747 command = 'for f in /sys/class/drm/*/*/modes; do head -1 $f; done'
1748 ret = self.run(command, ignore_status=True)
1749 # We might have Chromebox without a screen
1750 if ret.exit_status != 0:
1751 return ''
1752 return ret.stdout.strip().replace('\n', '+')
1753
1754
1755 def get_mem_total_gb(self):
1756 """Get total memory available in the system in GiB (2^20).
1757
1758 @returns an integer representing total memory
1759 """
1760 mem_total_kb = self.read_from_meminfo('MemTotal')
1761 kb_in_gb = float(2 ** 20)
1762 return int(round(mem_total_kb / kb_in_gb))
1763
1764
1765 def get_disk_size_gb(self):
1766 """Get size of disk in GB (10^9)
1767
1768 @returns an integer representing size of disk, 0 in Error Case
1769 """
1770 command = 'grep $(rootdev -s -d | cut -f3 -d/)$ /proc/partitions'
1771 result = self.run(command, ignore_status=True)
1772 if result.exit_status != 0:
1773 return 0
1774 _, _, block, _ = re.split(r' +', result.stdout.strip())
1775 byte_per_block = 1024.0
1776 disk_kb_in_gb = 1e9
1777 return int(int(block) * byte_per_block / disk_kb_in_gb + 0.5)
1778
1779
1780 def get_battery_size(self):
1781 """Get size of battery in Watt-hour via sysfs
1782
1783 This method assumes that battery support voltage_min_design and
1784 charge_full_design sysfs.
1785
1786 @returns a float representing Battery size, 0 if error.
1787 """
1788 # sysfs report data in micro scale
1789 battery_scale = 1e6
1790
1791 command = 'cat /sys/class/power_supply/*/voltage_min_design'
1792 result = self.run(command, ignore_status=True)
1793 if result.exit_status != 0:
1794 return 0
1795 voltage = float(result.stdout.strip()) / battery_scale
1796
1797 command = 'cat /sys/class/power_supply/*/charge_full_design'
1798 result = self.run(command, ignore_status=True)
1799 if result.exit_status != 0:
1800 return 0
1801 amphereHour = float(result.stdout.strip()) / battery_scale
1802
1803 return voltage * amphereHour
1804
1805
1806 def get_low_battery_shutdown_percent(self):
1807 """Get the percent-based low-battery shutdown threshold.
1808
1809 @returns a float representing low-battery shutdown percent, 0 if error.
1810 """
1811 ret = 0.0
1812 try:
1813 command = 'check_powerd_config --low_battery_shutdown_percent'
1814 ret = float(self.run(command).stdout)
1815 except error.CmdError:
1816 logging.debug("Can't run %s", command)
1817 except ValueError:
1818 logging.debug("Didn't get number from %s", command)
1819
1820 return ret
1821
1822
Puthikorn Voravootivat09c83d72018-08-10 15:58:32 -07001823 def has_hammer(self):
1824 """Check whether DUT has hammer device or not.
1825
1826 @returns boolean whether device has hammer or not
1827 """
1828 command = 'grep Hammer /sys/bus/usb/devices/*/product'
1829 return self.run(command, ignore_status=True).exit_status == 0
1830
1831
Niranjan Kumar34618872017-05-31 12:57:09 -07001832 def is_chrome_switch_present(self, switch):
David Haddock3ce538e2017-06-22 13:37:05 -07001833 """Returns True if the specified switch was provided to Chrome.
1834
1835 @param switch The chrome switch to search for.
1836 """
Niranjan Kumar34618872017-05-31 12:57:09 -07001837
Niranjan Kumar5f23fe92017-06-22 15:18:55 -07001838 command = 'pgrep -x -f -c "/opt/google/chrome/chrome.*%s.*"' % switch
1839 return self.run(command, ignore_status=True).exit_status == 0
Niranjan Kumar34618872017-05-31 12:57:09 -07001840
1841
1842 def oobe_triggers_update(self):
1843 """Returns True if this host has an OOBE flow during which
1844 it will perform an update check and perhaps an update.
1845 One example of such a flow is Hands-Off Zero-Touch Enrollment.
1846 As more such flows are developed, code handling them needs
1847 to be added here.
1848
1849 @return Boolean indicating whether this host's OOBE triggers an update.
1850 """
1851 return self.is_chrome_switch_present(
1852 '--enterprise-enable-zero-touch-enrollment=hands-off')
1853
1854
Kevin Chenga2619dc2016-03-28 11:42:08 -07001855 # TODO(kevcheng): change this to just return the board without the
1856 # 'board:' prefix and fix up all the callers. Also look into removing the
1857 # need for this method.
Simran Basic6f1f7a2012-10-16 10:47:46 -07001858 def get_board(self):
1859 """Determine the correct board label for this host.
1860
1861 @returns a string representing this host's board.
1862 """
1863 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1864 run_method=self.run)
J. Richard Barnetted2af5852016-02-05 15:03:10 -08001865 return (ds_constants.BOARD_PREFIX +
1866 release_info['CHROMEOS_RELEASE_BOARD'])
Simran Basic6f1f7a2012-10-16 10:47:46 -07001867
Po-Hsien Wang4618adf2017-10-10 14:40:21 -07001868 def get_channel(self):
1869 """Determine the correct channel label for this host.
Simran Basic6f1f7a2012-10-16 10:47:46 -07001870
Po-Hsien Wang4618adf2017-10-10 14:40:21 -07001871 @returns: a string represeting this host's build channel.
1872 (stable, dev, beta). None on fail.
1873 """
1874 return lsbrelease_utils.get_chromeos_channel(
1875 lsb_release_content=self._get_lsb_release_content())
Kevin Chenga328da62016-03-31 10:49:04 -07001876
Kevin Chenga328da62016-03-31 10:49:04 -07001877 def get_power_supply(self):
1878 """
1879 Determine what type of power supply the host has
1880
1881 @returns a string representing this host's power supply.
1882 'power:battery' when the device has a battery intended for
1883 extended use
1884 'power:AC_primary' when the device has a battery not intended
1885 for extended use (for moving the machine, etc)
1886 'power:AC_only' when the device has no battery at all.
1887 """
1888 psu = self.run(command='mosys psu type', ignore_status=True)
1889 if psu.exit_status:
1890 # The psu command for mosys is not included for all platforms. The
1891 # assumption is that the device will have a battery if the command
1892 # is not found.
1893 return 'power:battery'
1894
1895 psu_str = psu.stdout.strip()
1896 if psu_str == 'unknown':
1897 return None
1898
1899 return 'power:%s' % psu_str
1900
1901
Puthikorn Voravootivat54aa53c2018-03-01 19:00:25 -08001902 def has_battery(self):
1903 """Determine if DUT has a battery.
1904
1905 Returns:
1906 Boolean, False if known not to have battery, True otherwise.
1907 """
1908 rv = True
1909 power_supply = self.get_power_supply()
1910 if power_supply == 'power:battery':
1911 _NO_BATTERY_BOARD_TYPE = ['CHROMEBOX', 'CHROMEBIT', 'CHROMEBASE']
1912 board_type = self.get_board_type()
1913 if board_type in _NO_BATTERY_BOARD_TYPE:
1914 logging.warn('Do NOT believe type %s has battery. '
1915 'See debug for mosys details', board_type)
1916 psu = self.system_output('mosys -vvvv psu type',
1917 ignore_status=True)
1918 logging.debug(psu)
1919 rv = False
1920 elif power_supply == 'power:AC_only':
1921 rv = False
1922
1923 return rv
1924
1925
Kevin Chenga328da62016-03-31 10:49:04 -07001926 def get_servo(self):
1927 """Determine if the host has a servo attached.
1928
1929 If the host has a working servo attached, it should have a servo label.
1930
1931 @return: string 'servo' if the host has servo attached. Otherwise,
1932 returns None.
1933 """
1934 return 'servo' if self._servo_host else None
1935
1936
Kevin Chenga328da62016-03-31 10:49:04 -07001937 def has_internal_display(self):
1938 """Determine if the device under test is equipped with an internal
1939 display.
1940
1941 @return: 'internal_display' if one is present; None otherwise.
1942 """
1943 from autotest_lib.client.cros.graphics import graphics_utils
1944 from autotest_lib.client.common_lib import utils as common_utils
1945
1946 def __system_output(cmd):
1947 return self.run(cmd).stdout
1948
1949 def __read_file(remote_path):
1950 return self.run('cat %s' % remote_path).stdout
1951
1952 # Hijack the necessary client functions so that we can take advantage
1953 # of the client lib here.
1954 # FIXME: find a less hacky way than this
1955 original_system_output = utils.system_output
1956 original_read_file = common_utils.read_file
1957 utils.system_output = __system_output
1958 common_utils.read_file = __read_file
1959 try:
1960 return ('internal_display' if graphics_utils.has_internal_display()
1961 else None)
1962 finally:
1963 utils.system_output = original_system_output
1964 common_utils.read_file = original_read_file
1965
1966
Dan Shi85276d42014-04-08 22:11:45 -07001967 def is_boot_from_usb(self):
1968 """Check if DUT is boot from USB.
1969
1970 @return: True if DUT is boot from usb.
1971 """
1972 device = self.run('rootdev -s -d').stdout.strip()
1973 removable = int(self.run('cat /sys/block/%s/removable' %
1974 os.path.basename(device)).stdout.strip())
1975 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08001976
1977
1978 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08001979 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08001980
1981 @param key: meminfo requested
1982
1983 @return the memory value as a string
1984
1985 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08001986 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
1987 logging.debug('%s', meminfo)
1988 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07001989
1990
Rohit Makasana98e696f2016-06-03 18:48:10 -07001991 def get_cpu_arch(self):
1992 """Returns CPU arch of the device.
1993
1994 @return CPU architecture of the DUT.
1995 """
Allen Li2c32d6b2017-02-03 15:28:10 -08001996 # Add CPUs by following logic in client/bin/utils.py.
Rohit Makasana98e696f2016-06-03 18:48:10 -07001997 if self.run("grep '^flags.*:.* lm .*' /proc/cpuinfo",
1998 ignore_status=True).stdout:
1999 return 'x86_64'
2000 if self.run("grep -Ei 'ARM|CPU implementer' /proc/cpuinfo",
2001 ignore_status=True).stdout:
2002 return 'arm'
2003 return 'i386'
2004
2005
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002006 def get_board_type(self):
2007 """
2008 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002009 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2010
2011 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002012 """
Danny Chan471a8d12015-08-18 14:57:41 -07002013 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2014 ignore_status=True).stdout
2015 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002016 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002017 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002018
2019
Rohit Makasanadf0a3a32017-06-30 13:55:18 -07002020 def get_arc_version(self):
2021 """Return ARC version installed on the DUT.
2022
2023 @returns ARC version as string if the CrOS build has ARC, else None.
2024 """
2025 arc_version = self.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
2026 ignore_status=True).stdout
2027 if arc_version:
2028 return arc_version.split('=')[-1].strip()
2029 return None
2030
2031
Gilad Arnolda76bef02015-09-29 13:55:15 -07002032 def get_os_type(self):
2033 return 'cros'
Simran Basia5522a32015-10-06 11:01:24 -07002034
2035
Kevin Chenga2619dc2016-03-28 11:42:08 -07002036 def get_labels(self):
Kevin Chengf8660142016-08-12 10:17:41 -07002037 """Return the detected labels on the host."""
Kevin Chenga2619dc2016-03-28 11:42:08 -07002038 return self.labels.get_labels(self)
Garry Wang5e5538a2019-04-08 15:36:18 -07002039
2040
2041 def get_default_power_method(self):
2042 """
2043 Get the default power method for power_on/off/cycle() methods.
2044 @return POWER_CONTROL_RPM or POWER_CONTROL_CCD
2045 """
2046 if not self._default_power_method:
Garry Wang1a004aa2019-05-16 22:56:51 -07002047 self._default_power_method = self.POWER_CONTROL_RPM
Garry Wang5e5538a2019-04-08 15:36:18 -07002048 info = self.host_info_store.get()
2049 if info.attributes.get('servo_type') == self.CCD_SERVO:
Garry Wang1a004aa2019-05-16 22:56:51 -07002050 if self.servo:
2051 self._default_power_method = self.POWER_CONTROL_CCD
2052 else:
2053 logging.warn('CCD servo detected but failed to apply CCD'
2054 ' servo RPM method due to servo instance'
2055 ' was not initialized, fall back to normal'
2056 ' RPM method.')
Garry Wang5e5538a2019-04-08 15:36:18 -07002057 return self._default_power_method