blob: 7ad540299cc51c94452ad4ca735cbfebae29e069 [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
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07008import time
9
mussa584b4462014-06-20 15:13:28 -070010import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070011from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070012from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080013from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib import global_config
J. Richard Barnette91137f02016-03-10 16:52:26 -080015from autotest_lib.client.common_lib import hosts
Dan Shi549fb822015-03-24 18:01:11 -070016from autotest_lib.client.common_lib import lsbrelease_utils
J. Richard Barnette45e93de2012-04-11 17:24:15 -070017from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette03a0c132012-11-05 12:40:35 -080018from autotest_lib.client.common_lib.cros import dev_server
Gabe Blackb72f4fb2015-01-20 16:47:13 -080019from autotest_lib.client.common_lib.cros.graphite import autotest_es
Gabe Black1e1c41b2015-02-04 23:55:15 -080020from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000021from autotest_lib.client.cros import constants as client_constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080022from autotest_lib.client.cros import cros_ui
Kevin Chenga328da62016-03-31 10:49:04 -070023from autotest_lib.client.cros.audio import cras_utils
24from autotest_lib.client.cros.input_playback import input_playback
25from autotest_lib.client.cros.video import constants as video_test_constants
Simran Basi5ace6f22016-01-06 17:30:44 -080026from autotest_lib.server import afe_utils
MK Ryu35d661e2014-09-25 17:44:10 -070027from autotest_lib.server import autoserv_parser
28from autotest_lib.server import autotest
29from autotest_lib.server import constants
Dan Shia1ecd5c2013-06-06 11:21:31 -070030from autotest_lib.server import utils as server_utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070031from autotest_lib.server.cros import provision
Scott Zawalski89c44dd2013-02-26 09:28:02 -050032from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070033from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
Dan Shi9cb0eec2014-06-03 09:04:50 -070034from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
Scottfe06ed82015-11-05 17:15:01 -080035from autotest_lib.server.cros.servo import plankton
Fang Deng96667ca2013-08-01 17:46:18 -070036from autotest_lib.server.hosts import abstract_ssh
Kevin Chenga2619dc2016-03-28 11:42:08 -070037from autotest_lib.server.hosts import base_label
38from autotest_lib.server.hosts import cros_label
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +080039from autotest_lib.server.hosts import chameleon_host
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -080040from autotest_lib.server.hosts import cros_repair
Scottfe06ed82015-11-05 17:15:01 -080041from autotest_lib.server.hosts import plankton_host
Fang Deng5d518f42013-08-02 14:04:32 -070042from autotest_lib.server.hosts import servo_host
Simran Basidcff4252012-11-20 16:13:20 -080043from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070044
45
Dan Shib8540a52015-07-16 14:18:23 -070046CONFIG = global_config.global_config
xixuanec801e32016-08-25 10:20:22 -070047ENABLE_DEVSERVER_TRIGGER_AUTO_UPDATE = CONFIG.get_config_value(
48 'CROS', 'enable_devserver_trigger_auto_update', type=bool,
49 default=False)
Dan Shib8540a52015-07-16 14:18:23 -070050
Kevin Chenga328da62016-03-31 10:49:04 -070051LUCID_SLEEP_BOARDS = ['samus', 'lulu']
52
Dan Shid07ee2e2015-09-24 14:49:25 -070053
beepsc87ff602013-07-31 21:53:00 -070054class FactoryImageCheckerException(error.AutoservError):
55 """Exception raised when an image is a factory image."""
56 pass
57
58
Fang Deng0ca40e22013-08-27 17:47:44 -070059class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070060 """Chromium OS specific subclass of Host."""
61
Simran Basi5ace6f22016-01-06 17:30:44 -080062 VERSION_PREFIX = provision.CROS_VERSION_PREFIX
63
J. Richard Barnette45e93de2012-04-11 17:24:15 -070064 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -050065 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
xixuanec801e32016-08-25 10:20:22 -070066 support_devserver_provision = ENABLE_DEVSERVER_TRIGGER_AUTO_UPDATE
J. Richard Barnette45e93de2012-04-11 17:24:15 -070067
Richard Barnette03a0c132012-11-05 12:40:35 -080068 # Timeout values (in seconds) associated with various Chrome OS
69 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070070 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080071 # In general, a good rule of thumb is that the timeout can be up
72 # to twice the typical measured value on the slowest platform.
73 # The times here have not necessarily been empirically tested to
74 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070075 #
76 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080077 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
78 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080079 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070080 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080081 # other things, this must account for the 30 second dev-mode
J. Richard Barnette417cc792015-10-01 09:56:36 -070082 # screen delay, time to start the network on the DUT, and the
83 # ssh timeout of 120 seconds.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070084 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080085 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080086 # network.
beepsf079cfb2013-09-18 17:49:51 -070087 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080088 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
89 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070090
91 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080092 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +080093 SHUTDOWN_TIMEOUT = 10
J. Richard Barnette417cc792015-10-01 09:56:36 -070094 BOOT_TIMEOUT = 150
J. Richard Barnette5bab5f52015-08-03 13:14:38 -070095 USB_BOOT_TIMEOUT = 300
J. Richard Barnette7817b052014-08-28 09:47:29 -070096 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -080097 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -070098
Dan Shica503482015-03-30 17:23:25 -070099 # Minimum OS version that supports server side packaging. Older builds may
100 # not have server side package built or with Autotest code change to support
101 # server-side packaging.
Dan Shib8540a52015-07-16 14:18:23 -0700102 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value(
Dan Shiced09e42015-04-17 16:09:34 -0700103 'AUTOSERV', 'min_version_support_ssp', type=int)
Dan Shica503482015-03-30 17:23:25 -0700104
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800105 # REBOOT_TIMEOUT: How long to wait for a reboot.
106 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700107 # We have a long timeout to ensure we don't flakily fail due to other
108 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700109 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
110 # return from reboot' bug is solved.
111 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700112
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800113 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
114 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
115 _USB_POWER_TIMEOUT = 5
116 _POWER_CYCLE_TIMEOUT = 10
117
Dan Shib8540a52015-07-16 14:18:23 -0700118 _RPM_RECOVERY_BOARDS = CONFIG.get_config_value('CROS',
Richard Barnette82c35912012-11-20 10:09:10 -0800119 'rpm_recovery_boards', type=str).split(',')
120
Richard Barnette82c35912012-11-20 10:09:10 -0800121 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Fang Dengdeba14f2014-11-14 11:54:09 -0800122 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
123 '-host(\d+)')
Kevin Chenga328da62016-03-31 10:49:04 -0700124 _LIGHTSENSOR_FILES = [ "in_illuminance0_input",
125 "in_illuminance_input",
126 "in_illuminance0_raw",
127 "in_illuminance_raw",
128 "illuminance0_input"]
129 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700130
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800131 # Constants used in ping_wait_up() and ping_wait_down().
132 #
133 # _PING_WAIT_COUNT is the approximate number of polling
134 # cycles to use when waiting for a host state change.
135 #
136 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
137 # for arguments to the internal _ping_wait_for_status()
138 # method.
139 _PING_WAIT_COUNT = 40
140 _PING_STATUS_DOWN = False
141 _PING_STATUS_UP = True
142
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800143 # Allowed values for the power_method argument.
144
145 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
146 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
147 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
148 POWER_CONTROL_RPM = 'RPM'
149 POWER_CONTROL_SERVO = 'servoj10'
150 POWER_CONTROL_MANUAL = 'manual'
151
152 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
153 POWER_CONTROL_SERVO,
154 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800155
Simran Basi5e6339a2013-03-21 11:34:32 -0700156 _RPM_OUTLET_CHANGED = 'outlet_changed'
157
Dan Shi9cb0eec2014-06-03 09:04:50 -0700158 # URL pattern to download firmware image.
Dan Shib8540a52015-07-16 14:18:23 -0700159 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value(
Dan Shi9cb0eec2014-06-03 09:04:50 -0700160 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700161
J. Richard Barnette91137f02016-03-10 16:52:26 -0800162
163 # A flag file to indicate provision failures. The file is created
164 # at the start of any AU procedure (see `machine_install()`). The
165 # file's location in stateful means that on successul update it will
166 # be removed. Thus, if this file exists, it indicates that we've
167 # tried and failed in a previous attempt to update.
168 PROVISION_FAILED = '/var/tmp/provision_failed'
MK Ryu35d661e2014-09-25 17:44:10 -0700169
MK Ryu35d661e2014-09-25 17:44:10 -0700170
J. Richard Barnette964fba02012-10-24 17:34:29 -0700171 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800172 def check_host(host, timeout=10):
173 """
174 Check if the given host is a chrome-os host.
175
176 @param host: An ssh host representing a device.
177 @param timeout: The timeout for the run command.
178
179 @return: True if the host device is chromeos.
180
beeps46dadc92013-11-07 14:07:10 -0800181 """
182 try:
Simran Basi933c8af2015-04-29 14:05:07 -0700183 result = host.run(
184 'grep -q CHROMEOS /etc/lsb-release && '
185 '! test -f /mnt/stateful_partition/.android_tester && '
186 '! grep -q moblab /etc/lsb-release',
187 ignore_status=True, timeout=timeout)
beeps46dadc92013-11-07 14:07:10 -0800188 except (error.AutoservRunError, error.AutoservSSHTimeout):
189 return False
190 return result.exit_status == 0
191
192
193 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800194 def get_chameleon_arguments(args_dict):
195 """Extract chameleon options from `args_dict` and return the result.
196
197 Recommended usage:
198 ~~~~~~~~
199 args_dict = utils.args_to_dict(args)
200 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
201 host = hosts.create_host(machine, chameleon_args=chameleon_args)
202 ~~~~~~~~
203
204 @param args_dict Dictionary from which to extract the chameleon
205 arguments.
206 """
Allen Li083866b2016-08-18 10:07:10 -0700207 return {key: args_dict[key]
208 for key in ('chameleon_host', 'chameleon_port')
209 if key in args_dict}
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800210
211
212 @staticmethod
Scottfe06ed82015-11-05 17:15:01 -0800213 def get_plankton_arguments(args_dict):
214 """Extract chameleon options from `args_dict` and return the result.
215
216 Recommended usage:
217 ~~~~~~~~
218 args_dict = utils.args_to_dict(args)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700219 plankton_args = hosts.CrosHost.get_plankton_arguments(args_dict)
220 host = hosts.create_host(machine, plankton_args=plankton_args)
Scottfe06ed82015-11-05 17:15:01 -0800221 ~~~~~~~~
222
223 @param args_dict Dictionary from which to extract the plankton
224 arguments.
225 """
Allen Li083866b2016-08-18 10:07:10 -0700226 return {key: args_dict[key]
227 for key in ('plankton_host', 'plankton_port')
228 if key in args_dict}
Scottfe06ed82015-11-05 17:15:01 -0800229
230
231 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800232 def get_servo_arguments(args_dict):
233 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800234
235 Recommended usage:
236 ~~~~~~~~
237 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700238 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800239 host = hosts.create_host(machine, servo_args=servo_args)
240 ~~~~~~~~
241
242 @param args_dict Dictionary from which to extract the servo
243 arguments.
244 """
Richard Barnettee519dcd2016-08-15 17:37:17 -0700245 servo_attrs = (servo_host.SERVO_HOST_ATTR,
246 servo_host.SERVO_PORT_ATTR,
247 servo_host.SERVO_BOARD_ATTR)
Allen Li083866b2016-08-18 10:07:10 -0700248 return {key: args_dict[key]
249 for key in servo_attrs
250 if key in args_dict}
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700251
J. Richard Barnette964fba02012-10-24 17:34:29 -0700252
J. Richard Barnette91137f02016-03-10 16:52:26 -0800253 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
254 plankton_args=None, try_lab_servo=False,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700255 try_servo_repair=False,
J. Richard Barnette91137f02016-03-10 16:52:26 -0800256 ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700257 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800258 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700259
Fang Denge545abb2014-12-30 18:43:47 -0800260 This method will attempt to create the test-assistant object
261 (chameleon/servo) when it is needed by the test. Check
262 the docstring of chameleon_host.create_chameleon_host and
263 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700264
Fang Denge545abb2014-12-30 18:43:47 -0800265 @param hostname: Hostname of the dut.
266 @param chameleon_args: A dictionary that contains args for creating
267 a ChameleonHost. See chameleon_host for details.
268 @param servo_args: A dictionary that contains args for creating
269 a ServoHost object. See servo_host for details.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700270 @param try_lab_servo: When true, indicates that an attempt should
271 be made to create a ServoHost for a DUT in
272 the test lab, even if not required by
273 `servo_args`. See servo_host for details.
274 @param try_servo_repair: If a servo host is created, check it
275 with `repair()` rather than `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800276 See servo_host for details.
277 @param ssh_verbosity_flag: String, to pass to the ssh command to control
278 verbosity.
279 @param ssh_options: String, other ssh options to pass to the ssh
280 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700281 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700282 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700283 *args, **dargs)
J. Richard Barnette91137f02016-03-10 16:52:26 -0800284 self._repair_strategy = cros_repair.create_cros_repair_strategy()
Kevin Chenga2619dc2016-03-28 11:42:08 -0700285 self.labels = base_label.LabelRetriever(cros_label.CROS_LABELS)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700286 # self.env is a dictionary of environment variable settings
287 # to be exported for commands run on the host.
288 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
289 # errors that might happen.
290 self.env['LIBC_FATAL_STDERR_'] = '1'
Fang Dengd1c2b732013-08-20 12:59:46 -0700291 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700292 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700293 # TODO(fdeng): We need to simplify the
294 # process of servo and servo_host initialization.
295 # crbug.com/298432
Fang Denge545abb2014-12-30 18:43:47 -0800296 self._servo_host = servo_host.create_servo_host(
Richard Barnetteea3e4602016-06-10 12:36:41 -0700297 dut=self, servo_args=servo_args,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700298 try_lab_servo=try_lab_servo,
299 try_servo_repair=try_servo_repair)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700300 if self._servo_host is not None:
301 self.servo = self._servo_host.get_servo()
302 else:
303 self.servo = None
304
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800305 # TODO(waihong): Do the simplication on Chameleon too.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800306 self._chameleon_host = chameleon_host.create_chameleon_host(
307 dut=self.hostname, chameleon_args=chameleon_args)
Scottfe06ed82015-11-05 17:15:01 -0800308 # Add plankton host if plankton args were added on command line
309 self._plankton_host = plankton_host.create_plankton_host(plankton_args)
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800310
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800311 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800312 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800313 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800314 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700315
Scottfe06ed82015-11-05 17:15:01 -0800316 if self._plankton_host:
317 self.plankton_servo = self._plankton_host.get_servo()
318 logging.info('plankton_servo: %r', self.plankton_servo)
319 # Create the plankton object used to access the ec uart
Scott07a848f2016-01-12 15:04:52 -0800320 self.plankton = plankton.Plankton(self.plankton_servo,
321 self._plankton_host.get_servod_server_proxy())
Scottfe06ed82015-11-05 17:15:01 -0800322 else:
Scott07a848f2016-01-12 15:04:52 -0800323 self.plankton = None
Scottfe06ed82015-11-05 17:15:01 -0800324
Fang Deng5d518f42013-08-02 14:04:32 -0700325
Dan Shi3d7a0e12015-10-12 11:55:45 -0700326 def get_repair_image_name(self, image_type='cros'):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500327 """Generate a image_name from variables in the global config.
328
Dan Shi3d7a0e12015-10-12 11:55:45 -0700329 image_type is used to differentiate different images. Default is CrOS,
330 in which case, repair image's name follows the naming convention defined
331 in global setting CROS/stable_build_pattern.
332 If the image_type is not `cros`, the repair image will be looked up
333 using key `board_name/image_type`, e.g., daisy_spring/firmware.
334
335 @param image_type: Type of the image. Default is `cros`.
336
Dan Shi08173202015-11-12 13:08:45 -0800337 @returns a str of $board-version/$BUILD. Returns None if stable version
338 for the board and the default are both not set, e.g., stable
339 firmware version for a new board.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500340
341 """
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500342 board = self._get_board_from_afe()
343 if board is None:
344 raise error.AutoservError('DUT has no board attribute, '
345 'cannot be repaired.')
Dan Shi3d7a0e12015-10-12 11:55:45 -0700346 if image_type != 'cros':
347 board = '%s/%s' % (board, image_type)
Simran Basibeb2bb22016-02-03 15:25:48 -0800348 stable_version = afe_utils.get_stable_version(board=board)
Dan Shi3d7a0e12015-10-12 11:55:45 -0700349 if image_type == 'cros':
350 build_pattern = CONFIG.get_config_value(
351 'CROS', 'stable_build_pattern')
352 stable_version = build_pattern % (board, stable_version)
Dan Shi08173202015-11-12 13:08:45 -0800353 elif image_type == 'firmware':
354 # If firmware stable version is not specified, `stable_version`
355 # from the RPC is the default stable version for CrOS image.
356 # firmware stable version must be from firmware branch, thus its
357 # value must be like board-firmware/R31-1234.0.0. Check if
358 # firmware exists in the stable version, if not, return None.
359 if 'firmware' not in stable_version:
360 return None
Dan Shi3d7a0e12015-10-12 11:55:45 -0700361 return stable_version
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500362
363
Chris Sosab76e0ee2013-05-22 16:55:41 -0700364 def lookup_job_repo_url(self):
365 """Looks up the job_repo_url for the host.
366
Dan Shi8190eb82016-02-11 17:15:58 -0800367 This is kept for backwards compatibility as AU test code in older
368 branch does not use server-side packaging and calls this method through
369 the host object.
370
371 TODO(dshi): Once R50 falls off the stable branch, we should remove this
372 method.
373
Chris Sosab76e0ee2013-05-22 16:55:41 -0700374 @returns job_repo_url from AFE or None if not found.
375
376 @raises KeyError if the host does not have a job_repo_url
377 """
Dan Shibe3636a2016-02-14 22:48:01 -0800378 return afe_utils.get_host_attribute(self, ds_constants.JOB_REPO_URL)
beepscb6f1e22013-06-28 19:14:10 -0700379
380
beepsdae65fd2013-07-26 16:24:41 -0700381 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700382 """
383 Make sure job_repo_url of this host is valid.
384
joychen03eaad92013-06-26 09:55:21 -0700385 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700386 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
387 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
388 download and extract it. If the devserver embedded in the url is
389 unresponsive, update the job_repo_url of the host after staging it on
390 another devserver.
391
392 @param job_repo_url: A url pointing to the devserver where the autotest
393 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700394 @param tag: The tag from the server job, in the format
395 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700396
397 @raises DevServerException: If we could not resolve a devserver.
398 @raises AutoservError: If we're unable to save the new job_repo_url as
399 a result of choosing a new devserver because the old one failed to
400 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700401 @raises urllib2.URLError: If the devserver embedded in job_repo_url
402 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700403 """
Dan Shibe3636a2016-02-14 22:48:01 -0800404 job_repo_url = afe_utils.get_host_attribute(self,
405 ds_constants.JOB_REPO_URL)
beepscb6f1e22013-06-28 19:14:10 -0700406 if not job_repo_url:
407 logging.warning('No job repo url set on host %s', self.hostname)
408 return
409
410 logging.info('Verifying job repo url %s', job_repo_url)
411 devserver_url, image_name = tools.get_devserver_build_from_package_url(
412 job_repo_url)
413
beeps0c865032013-07-30 11:37:06 -0700414 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700415
416 logging.info('Staging autotest artifacts for %s on devserver %s',
417 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700418
419 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700420 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700421 stage_time = time.time() - start_time
422
423 # Record how much of the verification time comes from a devserver
424 # restage. If we're doing things right we should not see multiple
425 # devservers for a given board/build/branch path.
426 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800427 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700428 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800429 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700430 pass
431 else:
beeps0c865032013-07-30 11:37:06 -0700432 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700433 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700434 stats_key = {
435 'board': board,
436 'build_type': build_type,
437 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700438 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700439 }
Gabe Black1e1c41b2015-02-04 23:55:15 -0800440 autotest_stats.Gauge('verify_job_repo_url').send(
beeps687243d2013-07-18 15:29:27 -0700441 '%(board)s.%(build_type)s.%(branch)s.%(devserver)s' % stats_key,
442 stage_time)
beepscb6f1e22013-06-28 19:14:10 -0700443
Scott Zawalskieadbf702013-03-14 09:23:06 -0400444
Dan Shicf4d2032015-03-12 15:04:21 -0700445 def stage_server_side_package(self, image=None):
446 """Stage autotest server-side package on devserver.
447
448 @param image: Full path of an OS image to install or a build name.
449
450 @return: A url to the autotest server-side package.
Dan Shi14de7622016-08-22 11:09:06 -0700451
452 @raise: error.AutoservError if fail to locate the build to test with, or
453 fail to stage server-side package.
Dan Shicf4d2032015-03-12 15:04:21 -0700454 """
Dan Shid37736b2016-07-06 15:10:29 -0700455 # If enable_drone_in_restricted_subnet is False, do not set hostname
456 # in devserver.resolve call, so a devserver in non-restricted subnet
457 # is picked to stage autotest server package for drone to download.
458 hostname = self.hostname
459 if not server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
460 hostname = None
Dan Shicf4d2032015-03-12 15:04:21 -0700461 if image:
462 image_name = tools.get_build_from_image(image)
463 if not image_name:
464 raise error.AutoservError(
465 'Failed to parse build name from %s' % image)
Dan Shid37736b2016-07-06 15:10:29 -0700466 ds = dev_server.ImageServer.resolve(image_name, hostname)
Dan Shicf4d2032015-03-12 15:04:21 -0700467 else:
Dan Shibe3636a2016-02-14 22:48:01 -0800468 job_repo_url = afe_utils.get_host_attribute(
469 self, ds_constants.JOB_REPO_URL)
Dan Shicf4d2032015-03-12 15:04:21 -0700470 if job_repo_url:
471 devserver_url, image_name = (
472 tools.get_devserver_build_from_package_url(job_repo_url))
Dan Shid37736b2016-07-06 15:10:29 -0700473 # If enable_drone_in_restricted_subnet is True, use the
474 # existing devserver. Otherwise, resolve a new one in
475 # non-restricted subnet.
476 if server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
477 ds = dev_server.ImageServer(devserver_url)
478 else:
479 ds = dev_server.ImageServer.resolve(image_name)
Dan Shicf4d2032015-03-12 15:04:21 -0700480 else:
Kevin Cheng84a71ba2016-07-14 11:03:57 -0700481 labels = afe_utils.get_labels(self, self.VERSION_PREFIX)
Dan Shicf4d2032015-03-12 15:04:21 -0700482 if not labels:
483 raise error.AutoservError(
484 'Failed to stage server-side package. The host has '
485 'no job_report_url attribute or version label.')
Kevin Cheng84a71ba2016-07-14 11:03:57 -0700486 image_name = labels[0][len(self.VERSION_PREFIX + ':'):]
Dan Shid37736b2016-07-06 15:10:29 -0700487 ds = dev_server.ImageServer.resolve(image_name, hostname)
Dan Shica503482015-03-30 17:23:25 -0700488
489 # Get the OS version of the build, for any build older than
490 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
491 match = re.match('.*/R\d+-(\d+)\.', image_name)
492 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
Dan Shi14de7622016-08-22 11:09:06 -0700493 raise error.AutoservError(
494 'Build %s is older than %s. Server side packaging is '
495 'disabled.' % (image_name, self.MIN_VERSION_SUPPORT_SSP))
Dan Shica503482015-03-30 17:23:25 -0700496
Dan Shicf4d2032015-03-12 15:04:21 -0700497 ds.stage_artifacts(image_name, ['autotest_server_package'])
498 return '%s/static/%s/%s' % (ds.url(), image_name,
499 'autotest_server_package.tar.bz2')
500
501
Dan Shi0f466e82013-02-22 15:44:58 -0800502 def _try_stateful_update(self, update_url, force_update, updater):
503 """Try to use stateful update to initialize DUT.
504
505 When DUT is already running the same version that machine_install
506 tries to install, stateful update is a much faster way to clean up
507 the DUT for testing, compared to a full reimage. It is implemeted
508 by calling autoupdater.run_update, but skipping updating root, as
509 updating the kernel is time consuming and not necessary.
510
511 @param update_url: url of the image.
512 @param force_update: Set to True to update the image even if the DUT
513 is running the same version.
514 @param updater: ChromiumOSUpdater instance used to update the DUT.
515 @returns: True if the DUT was updated with stateful update.
516
517 """
Dan Shi10b98482016-02-02 14:38:50 -0800518 # Stop service ap-update-manager to prevent rebooting during autoupdate.
519 # The service is used in jetstream boards, but not other CrOS devices.
520 self.run('sudo stop ap-update-manager', ignore_status=True)
521
J. Richard Barnette3f731032014-04-07 17:42:59 -0700522 # TODO(jrbarnette): Yes, I hate this re.match() test case.
523 # It's better than the alternative: see crbug.com/360944.
524 image_name = autoupdater.url_to_image_name(update_url)
525 release_pattern = r'^.*-release/R[0-9]+-[0-9]+\.[0-9]+\.0$'
526 if not re.match(release_pattern, image_name):
527 return False
Dan Shi0f466e82013-02-22 15:44:58 -0800528 if not updater.check_version():
529 return False
530 if not force_update:
531 logging.info('Canceling stateful update because the new and '
532 'old versions are the same.')
533 return False
534 # Following folders should be rebuilt after stateful update.
535 # A test file is used to confirm each folder gets rebuilt after
536 # the stateful update.
537 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
538 test_file = '.test_file_to_be_deleted'
539 for folder in folders_to_check:
540 touch_path = os.path.join(folder, test_file)
541 self.run('touch %s' % touch_path)
542
Chris Sosae92399e2015-04-24 11:32:59 -0700543 updater.run_update(update_root=False)
Dan Shi0f466e82013-02-22 15:44:58 -0800544
545 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700546 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800547 check_file_cmd = 'test -f %s; echo $?'
548 for folder in folders_to_check:
549 test_file_path = os.path.join(folder, test_file)
550 result = self.run(check_file_cmd % test_file_path,
551 ignore_status=True)
552 if result.exit_status == 1:
553 return False
554 return True
555
556
J. Richard Barnette7275b612013-06-04 18:13:11 -0700557 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800558 """After the DUT is updated, confirm machine_install succeeded.
559
560 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700561 @param expected_kernel: kernel expected to be active after reboot,
562 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800563
564 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700565 # Touch the lab machine file to leave a marker that
566 # distinguishes this image from other test images.
567 # Afterwards, we must re-run the autoreboot script because
568 # it depends on the _LAB_MACHINE_FILE.
J. Richard Barnette71cc1862015-12-02 10:32:38 -0800569 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
570 '( touch "$FILE" ; start autoreboot )')
571 self.run(autoreboot_cmd % self._LAB_MACHINE_FILE)
Chris Sosa65425082013-10-16 13:26:22 -0700572 updater.verify_boot_expectations(
573 expected_kernel, rollback_message=
Gilad Arnoldc26ae1f2015-10-22 16:09:41 -0700574 'Build %s failed to boot on %s; system rolled back to previous '
Chris Sosa65425082013-10-16 13:26:22 -0700575 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700576 # Check that we've got the build we meant to install.
577 if not updater.check_version_to_confirm_install():
578 raise autoupdater.ChromiumOSError(
579 'Failed to update %s to build %s; found build '
580 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700581 updater.update_version,
Dan Shi0942b1d2015-03-31 11:07:00 -0700582 self.get_release_version()))
Dan Shi0f466e82013-02-22 15:44:58 -0800583
Chris Sosae92399e2015-04-24 11:32:59 -0700584 logging.debug('Cleaning up old autotest directories.')
585 try:
586 installed_autodir = autotest.Autotest.get_installed_autodir(self)
587 self.run('rm -rf ' + installed_autodir)
588 except autotest.AutodirNotFoundError:
589 logging.debug('No autotest installed directory found.')
590
Dan Shi0f466e82013-02-22 15:44:58 -0800591
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700592 def _stage_image_for_update(self, image_name=None):
Chris Sosae92399e2015-04-24 11:32:59 -0700593 """Stage a build on a devserver and return the update_url and devserver.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400594
595 @param image_name: a name like lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700596 @returns a tuple with an update URL like:
Scott Zawalskieadbf702013-03-14 09:23:06 -0400597 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700598 and the devserver instance.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400599 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700600 if not image_name:
601 image_name = self.get_repair_image_name()
Chris Sosae92399e2015-04-24 11:32:59 -0700602
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700603 logging.info('Staging build for AU: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800604 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400605 devserver.trigger_download(image_name, synchronous=False)
Chris Sosae92399e2015-04-24 11:32:59 -0700606 return (tools.image_url_pattern() % (devserver.url(), image_name),
607 devserver)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400608
609
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700610 def stage_image_for_servo(self, image_name=None):
611 """Stage a build on a devserver and return the update_url.
612
613 @param image_name: a name like lumpy-release/R27-3837.0.0
614 @returns an update URL like:
615 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
616 """
617 if not image_name:
618 image_name = self.get_repair_image_name()
619 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800620 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700621 devserver.stage_artifacts(image_name, ['test_image'])
622 return devserver.get_test_image_url(image_name)
623
624
beepse539be02013-07-31 21:57:39 -0700625 def stage_factory_image_for_servo(self, image_name):
626 """Stage a build on a devserver and return the update_url.
627
628 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700629
beepse539be02013-07-31 21:57:39 -0700630 @return: An update URL, eg:
631 http://<devserver>/static/canary-channel/\
632 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700633
634 @raises: ValueError if the factory artifact name is missing from
635 the config.
636
beepse539be02013-07-31 21:57:39 -0700637 """
638 if not image_name:
639 logging.error('Need an image_name to stage a factory image.')
640 return
641
Dan Shib8540a52015-07-16 14:18:23 -0700642 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700643 'CROS', 'factory_artifact', type=str, default='')
644 if not factory_artifact:
645 raise ValueError('Cannot retrieve the factory artifact name from '
646 'autotest config, and hence cannot stage factory '
647 'artifacts.')
648
beepse539be02013-07-31 21:57:39 -0700649 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800650 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
beepse539be02013-07-31 21:57:39 -0700651 devserver.stage_artifacts(
652 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700653 [factory_artifact],
654 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700655
656 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
657
658
xixuanec801e32016-08-25 10:20:22 -0700659 def machine_install_by_devserver(self, update_url=None, force_update=False,
xixuan5dc64ea2016-05-20 17:27:51 -0700660 local_devserver=False, repair=False,
661 force_full_update=False):
662 """Ultiize devserver to install the DUT.
663
664 (TODO) crbugs.com/627269: The logic in this function has some overlap
665 with those in function machine_install. The merge will be done later,
666 not in the same CL.
667
xixuanec801e32016-08-25 10:20:22 -0700668 @param update_url: The update_url or build for the host to update.
xixuan5dc64ea2016-05-20 17:27:51 -0700669 @param force_update: Force an update even if the version installed
670 is the same. Default:False
671 @param local_devserver: Used by test_that to allow people to
672 use their local devserver. Default: False
673 @param repair: Forces update to repair image. Implies force_update.
674 @param force_full_update: If True, do not attempt to run stateful
675 update, force a full reimage. If False, try stateful update
676 first when the dut is already installed with the same version.
677 @raises autoupdater.ChromiumOSError
678
679 @returns A tuple of (image_name, host_attributes).
680 image_name is the name of image installed, e.g.,
681 veyron_jerry-release/R50-7871.0.0
682 host_attributes is a dictionary of (attribute, value), which
683 can be saved to afe_host_attributes table in database. This
684 method returns a dictionary with a single entry of
685 `job_repo_url`: repo_url, where repo_url is a devserver url to
686 autotest packages.
687 """
688 devserver = None
689 logging.debug('Resolving a devserver for auto-update')
690 if repair:
xixuanec801e32016-08-25 10:20:22 -0700691 update_url = self.get_repair_image_name()
xixuan5dc64ea2016-05-20 17:27:51 -0700692 force_update = True
693
xixuanec801e32016-08-25 10:20:22 -0700694 if not update_url and not self._parser.options.image:
xixuan5dc64ea2016-05-20 17:27:51 -0700695 raise error.AutoservError(
696 'There is no update URL, nor a method to get one.')
697
xixuanec801e32016-08-25 10:20:22 -0700698 if not update_url and self._parser.options.image:
699 update_url = self._parser.options.image
xixuan5dc64ea2016-05-20 17:27:51 -0700700
xixuanec801e32016-08-25 10:20:22 -0700701 logging.info('Staging build for AU: %s', update_url)
xixuan5dc64ea2016-05-20 17:27:51 -0700702
703 # Get build from parameter or AFE.
704 # If the build is not a URL, let devserver to stage it first.
705 # Otherwise, choose a devserver to trigger auto-update.
xixuanec801e32016-08-25 10:20:22 -0700706 build = None
707 if update_url.startswith('http://'):
708 build = autoupdater.url_to_image_name(update_url)
xixuan5dc64ea2016-05-20 17:27:51 -0700709 devserver = dev_server.ImageServer.resolve(build, self.hostname)
710 else:
xixuanec801e32016-08-25 10:20:22 -0700711 build = update_url
xixuan5dc64ea2016-05-20 17:27:51 -0700712 devserver = dev_server.ImageServer.resolve(build, self.hostname)
713 devserver.trigger_download(build, synchronous=False)
714
715 # Report provision stats.
716 server_name = dev_server.ImageServer.get_server_name(devserver.url())
717 server_name = server_name.replace('.', '_')
718 autotest_stats.Counter('cros_host_provision.' + server_name).increment()
719 autotest_stats.Counter('cros_host_provision.total').increment()
xixuanec801e32016-08-25 10:20:22 -0700720 logging.debug('Resolved devserver for auto-update: %s', devserver.url())
xixuan5dc64ea2016-05-20 17:27:51 -0700721
722 devserver.auto_update(self.hostname, build,
723 log_dir=self.job.sysinfo.sysinfodir,
724 force_update=force_update,
725 full_update=force_full_update)
726
727 # The reason to resolve a new devserver in function machine_install
728 # is mostly because that the update_url there may has a strange format,
729 # and it's hard to parse the devserver url from it.
730 # Since we already resolve a devserver to trigger auto-update, the same
731 # devserver is used to form JOB_REPO_URL here. Verified in local test.
732 repo_url = tools.get_package_url(devserver.url(), build)
733 return build, {ds_constants.JOB_REPO_URL: repo_url}
734
735
Chris Sosaa3ac2152012-05-23 22:23:13 -0700736 def machine_install(self, update_url=None, force_update=False,
Richard Barnette0b023a72015-04-24 16:07:30 +0000737 local_devserver=False, repair=False,
738 force_full_update=False):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500739 """Install the DUT.
740
Dan Shi0f466e82013-02-22 15:44:58 -0800741 Use stateful update if the DUT is already running the same build.
742 Stateful update does not update kernel and tends to run much faster
743 than a full reimage. If the DUT is running a different build, or it
744 failed to do a stateful update, full update, including kernel update,
745 will be applied to the DUT.
746
Simran Basi5ace6f22016-01-06 17:30:44 -0800747 Once a host enters machine_install its host attribute job_repo_url
748 (used for package install) will be removed and then updated.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400749
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500750 @param update_url: The url to use for the update
751 pattern: http://$devserver:###/update/$build
752 If update_url is None and repair is True we will install the
Dan Shi6964fa52014-12-18 11:04:27 -0800753 stable image listed in afe_stable_versions table. If the table
754 is not setup, global_config value under CROS.stable_cros_version
755 will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500756 @param force_update: Force an update even if the version installed
757 is the same. Default:False
Christopher Wiley6a4ff932015-05-15 14:00:47 -0700758 @param local_devserver: Used by test_that to allow people to
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500759 use their local devserver. Default: False
Chris Sosae92399e2015-04-24 11:32:59 -0700760 @param repair: Forces update to repair image. Implies force_update.
Fang Deng3d3b9272014-12-22 12:20:28 -0800761 @param force_full_update: If True, do not attempt to run stateful
762 update, force a full reimage. If False, try stateful update
763 first when the dut is already installed with the same version.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500764 @raises autoupdater.ChromiumOSError
765
Dan Shibe3636a2016-02-14 22:48:01 -0800766 @returns A tuple of (image_name, host_attributes).
767 image_name is the name of image installed, e.g.,
768 veyron_jerry-release/R50-7871.0.0
769 host_attributes is a dictionary of (attribute, value), which
770 can be saved to afe_host_attributes table in database. This
771 method returns a dictionary with a single entry of
772 `job_repo_url`: repo_url, where repo_url is a devserver url to
773 autotest packages.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500774 """
Chris Sosae92399e2015-04-24 11:32:59 -0700775 devserver = None
Richard Barnette0b023a72015-04-24 16:07:30 +0000776 if repair:
Chris Sosae92399e2015-04-24 11:32:59 -0700777 update_url, devserver = self._stage_image_for_update()
Richard Barnette0b023a72015-04-24 16:07:30 +0000778 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800779
Chris Sosae92399e2015-04-24 11:32:59 -0700780 if not update_url and not self._parser.options.image:
781 raise error.AutoservError(
Dan Shid07ee2e2015-09-24 14:49:25 -0700782 'There is no update URL, nor a method to get one.')
Chris Sosae92399e2015-04-24 11:32:59 -0700783
784 if not update_url and self._parser.options.image:
785 # This is the base case where we have no given update URL i.e.
786 # dynamic suites logic etc. This is the most flexible case where we
787 # can serve an update from any of our fleet of devservers.
788 requested_build = self._parser.options.image
789 if not requested_build.startswith('http://'):
790 logging.debug('Update will be staged for this installation')
791 update_url, devserver = self._stage_image_for_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700792 requested_build)
Chris Sosae92399e2015-04-24 11:32:59 -0700793 else:
794 update_url = requested_build
795
796 logging.debug('Update URL is %s', update_url)
797
Dan Shif48f8132016-02-18 10:34:30 -0800798 # Report provision stats.
xixuan9e2c98d2016-02-26 19:04:53 -0800799 server_name = dev_server.ImageServer.get_server_name(update_url)
Dan Shif48f8132016-02-18 10:34:30 -0800800 server_name = server_name.replace('.', '_')
801 autotest_stats.Counter('cros_host_provision.' + server_name).increment()
802 autotest_stats.Counter('cros_host_provision.total').increment()
803
Dan Shid07ee2e2015-09-24 14:49:25 -0700804 # Create a file to indicate if provision fails. The file will be removed
805 # by stateful update or full install.
J. Richard Barnette91137f02016-03-10 16:52:26 -0800806 self.run('touch %s' % self.PROVISION_FAILED)
Dan Shid07ee2e2015-09-24 14:49:25 -0700807
Chris Sosae92399e2015-04-24 11:32:59 -0700808 update_complete = False
809 updater = autoupdater.ChromiumOSUpdater(
810 update_url, host=self, local_devserver=local_devserver)
Fang Deng3d3b9272014-12-22 12:20:28 -0800811 if not force_full_update:
812 try:
Chris Sosae92399e2015-04-24 11:32:59 -0700813 # If the DUT is already running the same build, try stateful
814 # update first as it's much quicker than a full re-image.
815 update_complete = self._try_stateful_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700816 update_url, force_update, updater)
Fang Deng3d3b9272014-12-22 12:20:28 -0800817 except Exception as e:
818 logging.exception(e)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700819
Dan Shi0f466e82013-02-22 15:44:58 -0800820 inactive_kernel = None
Chris Sosae92399e2015-04-24 11:32:59 -0700821 if update_complete or (not force_update and updater.check_version()):
822 logging.info('Install complete without full update')
823 else:
824 logging.info('DUT requires full update.')
825 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi10b98482016-02-02 14:38:50 -0800826 # Stop service ap-update-manager to prevent rebooting during
827 # autoupdate. The service is used in jetstream boards, but not other
828 # CrOS devices.
829 self.run('sudo stop ap-update-manager', ignore_status=True)
830
Chris Sosae92399e2015-04-24 11:32:59 -0700831 num_of_attempts = provision.FLAKY_DEVSERVER_ATTEMPTS
Chris Sosab7612bc2013-03-21 10:32:37 -0700832
Chris Sosae92399e2015-04-24 11:32:59 -0700833 while num_of_attempts > 0:
834 num_of_attempts -= 1
835 try:
836 updater.run_update()
837 except Exception:
838 logging.warn('Autoupdate did not complete.')
839 # Do additional check for the devserver health. Ideally,
840 # the autoupdater.py could raise an exception when it
841 # detected network flake but that would require
842 # instrumenting the update engine and parsing it log.
843 if (num_of_attempts <= 0 or
844 devserver is None or
xixuan9e2c98d2016-02-26 19:04:53 -0800845 dev_server.ImageServer.devserver_healthy(
Chris Sosae92399e2015-04-24 11:32:59 -0700846 devserver.url())):
Dan Shid07ee2e2015-09-24 14:49:25 -0700847 raise
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700848
Chris Sosae92399e2015-04-24 11:32:59 -0700849 logging.warn('Devserver looks unhealthy. Trying another')
850 update_url, devserver = self._stage_image_for_update(
851 requested_build)
852 logging.debug('New Update URL is %s', update_url)
853 updater = autoupdater.ChromiumOSUpdater(
854 update_url, host=self,
855 local_devserver=local_devserver)
856 else:
857 break
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700858
Chris Sosae92399e2015-04-24 11:32:59 -0700859 # Give it some time in case of IO issues.
860 time.sleep(10)
Dan Shi5699ac22014-12-19 10:55:49 -0800861
Chris Sosae92399e2015-04-24 11:32:59 -0700862 # Figure out active and inactive kernel.
863 active_kernel, inactive_kernel = updater.get_kernel_state()
Simran Basi13fa1ba2013-03-04 10:56:47 -0800864
Chris Sosae92399e2015-04-24 11:32:59 -0700865 # Ensure inactive kernel has higher priority than active.
866 if (updater.get_kernel_priority(inactive_kernel)
867 < updater.get_kernel_priority(active_kernel)):
868 raise autoupdater.ChromiumOSError(
869 'Update failed. The priority of the inactive kernel'
870 ' partition is less than that of the active kernel'
871 ' partition.')
872
873 # Updater has returned successfully; reboot the host.
Richard Barnettefc3d7432016-07-07 10:21:31 -0700874 #
875 # Regarding the 'crossystem' command: In some cases, the
876 # TPM gets into a state such that it fails verification.
877 # We don't know why. However, this call papers over the
878 # problem by clearing the TPM during the reboot.
879 #
880 # We ignore failures from 'crossystem'. Although failure
881 # here is unexpected, and could signal a bug, the point
882 # of the exercise is to paper over problems; allowing
883 # this to fail would defeat the purpose.
884 self.run('crossystem clear_tpm_owner_request=1',
885 ignore_status=True)
Chris Sosae92399e2015-04-24 11:32:59 -0700886 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
887
888 self._post_update_processing(updater, inactive_kernel)
Simran Basi5ace6f22016-01-06 17:30:44 -0800889 image_name = autoupdater.url_to_image_name(update_url)
Dan Shibe3636a2016-02-14 22:48:01 -0800890 # update_url is different from devserver url needed to stage autotest
891 # packages, therefore, resolve a new devserver url here.
892 devserver_url = dev_server.ImageServer.resolve(image_name,
893 self.hostname).url()
894 repo_url = tools.get_package_url(devserver_url, image_name)
895 return image_name, {ds_constants.JOB_REPO_URL: repo_url}
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700896
897
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800898 def _clear_fw_version_labels(self, rw_only):
899 """Clear firmware version labels from the machine.
900
901 @param rw_only: True to only clear fwrw_version; otherewise, clear
902 both fwro_version and fwrw_version.
903 """
Dan Shi9cb0eec2014-06-03 09:04:50 -0700904 labels = self._AFE.get_labels(
Dan Shi0723bf52015-06-24 10:52:38 -0700905 name__startswith=provision.FW_RW_VERSION_PREFIX,
Dan Shi9cb0eec2014-06-03 09:04:50 -0700906 host__hostname=self.hostname)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800907 if not rw_only:
908 labels = labels + self._AFE.get_labels(
909 name__startswith=provision.FW_RO_VERSION_PREFIX,
910 host__hostname=self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700911 for label in labels:
912 label.remove_hosts(hosts=[self.hostname])
913
914
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800915 def _add_fw_version_label(self, build, rw_only):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700916 """Add firmware version label to the machine.
917
918 @param build: Build of firmware.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800919 @param rw_only: True to only add fwrw_version; otherwise, add both
920 fwro_version and fwrw_version.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700921
922 """
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800923 fw_label = provision.fwrw_version_to_label(build)
MK Ryu73be9862015-07-06 12:25:00 -0700924 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800925 if not rw_only:
926 fw_label = provision.fwro_version_to_label(build)
927 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Dan Shi9cb0eec2014-06-03 09:04:50 -0700928
929
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800930 def firmware_install(self, build=None, rw_only=False):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700931 """Install firmware to the DUT.
932
933 Use stateful update if the DUT is already running the same build.
934 Stateful update does not update kernel and tends to run much faster
935 than a full reimage. If the DUT is running a different build, or it
936 failed to do a stateful update, full update, including kernel update,
937 will be applied to the DUT.
938
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800939 Once a host enters firmware_install its fw[ro|rw]_version label will
940 be removed. After the firmware is updated successfully, a new
941 fw[ro|rw]_version label will be added to the host.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700942
943 @param build: The build version to which we want to provision the
944 firmware of the machine,
945 e.g. 'link-firmware/R22-2695.1.144'.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800946 @param rw_only: True to only install firmware to its RW portions. Keep
947 the RO portions unchanged.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700948
949 TODO(dshi): After bug 381718 is fixed, update here with corresponding
950 exceptions that could be raised.
951
952 """
953 if not self.servo:
954 raise error.TestError('Host %s does not have servo.' %
955 self.hostname)
956
957 # TODO(fdeng): use host.get_board() after
958 # crbug.com/271834 is fixed.
959 board = self._get_board_from_afe()
960
Chris Sosae92399e2015-04-24 11:32:59 -0700961 # If build is not set, try to install firmware from stable CrOS.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700962 if not build:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700963 build = self.get_repair_image_name(image_type='firmware')
964 if not build:
965 raise error.TestError(
966 'Failed to find stable firmware build for %s.',
967 self.hostname)
968 logging.info('Will install firmware from build %s.', build)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700969
970 config = FAFTConfig(board)
971 if config.use_u_boot:
972 ap_image = 'image-%s.bin' % board
973 else: # Depthcharge platform
974 ap_image = 'image.bin'
975 ec_image = 'ec.bin'
Dan Shi216389c2015-12-22 11:03:06 -0800976 ds = dev_server.ImageServer.resolve(build, self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700977 ds.stage_artifacts(build, ['firmware'])
978
979 tmpd = autotemp.tempdir(unique_id='fwimage')
980 try:
981 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
982 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
983 server_utils.system('wget -O %s %s' % (local_tarball, fwurl),
984 timeout=60)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700985
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800986 self._clear_fw_version_labels(rw_only)
Tom Wai-Hong Tam552fee12016-05-11 06:27:58 +0800987 if config.chrome_ec:
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +0800988 logging.info('Will re-program EC %snow', 'RW ' if rw_only else '')
989 server_utils.system('tar xf %s -C %s %s' %
990 (local_tarball, tmpd.name, ec_image),
991 timeout=60)
992 self.servo.program_ec(os.path.join(tmpd.name, ec_image), rw_only)
993 else:
994 logging.info('Not a Chrome EC, ignore re-programing it')
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800995 logging.info('Will re-program BIOS %snow', 'RW ' if rw_only else '')
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +0800996 server_utils.system('tar xf %s -C %s %s' %
997 (local_tarball, tmpd.name, ap_image),
998 timeout=60)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800999 self.servo.program_bios(os.path.join(tmpd.name, ap_image), rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001000 self.servo.get_power_state_controller().reset()
1001 time.sleep(self.servo.BOOT_DELAY)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001002 self._add_fw_version_label(build, rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001003 finally:
1004 tmpd.clean()
1005
1006
Dan Shi10e992b2013-08-30 11:02:59 -07001007 def show_update_engine_log(self):
1008 """Output update engine log."""
MK Ryu35d661e2014-09-25 17:44:10 -07001009 logging.debug('Dumping %s', client_constants.UPDATE_ENGINE_LOG)
1010 self.run('cat %s' % client_constants.UPDATE_ENGINE_LOG)
Dan Shi10e992b2013-08-30 11:02:59 -07001011
1012
Richard Barnette82c35912012-11-20 10:09:10 -08001013 def _get_board_from_afe(self):
Kevin Cheng84a71ba2016-07-14 11:03:57 -07001014 """Retrieve this host's board from its labels stored locally.
Richard Barnette82c35912012-11-20 10:09:10 -08001015
1016 Looks for a host label of the form "board:<board>", and
1017 returns the "<board>" part of the label. `None` is returned
1018 if there is not a single, unique label matching the pattern.
1019
1020 @returns board from label, or `None`.
1021 """
Kevin Cheng84a71ba2016-07-14 11:03:57 -07001022 return afe_utils.get_board(self)
Simran Basi833814b2013-01-29 13:13:43 -08001023
1024
Dan Shib3b6db32016-02-03 14:54:05 -08001025 def get_build(self):
1026 """Retrieve the current build for this Host from the AFE.
1027
1028 Looks through this host's labels in the AFE to determine its build.
1029 This method is replaced by afe_utils.get_build. It's kept here to
1030 maintain backwards compatibility for test control files in older CrOS
1031 builds (R48, R49 etc.) still call host.get_build, e.g.,
1032 `provision_AutoUpdate.double`.
1033 TODO(sbasi): Once R50 falls into release branch, this method can be
1034 removed.
1035
1036 @returns The current build or None if it could not find it or if there
1037 were multiple build labels assigned to this host.
1038 """
1039 return afe_utils.get_build(self)
1040
1041
beepsf079cfb2013-09-18 17:49:51 -07001042 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
1043 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001044 """
1045 Re-install the OS on the DUT by:
1046 1) installing a test image on a USB storage device attached to the Servo
1047 board,
Richard Barnette03a0c132012-11-05 12:40:35 -08001048 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001049 3) installing the image with chromeos-install.
1050
Scott Zawalski62bacae2013-03-05 10:40:32 -05001051 @param image_url: If specified use as the url to install on the DUT.
1052 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -07001053 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
1054 Factory images need a longer usb_boot_timeout than regular
1055 cros images.
1056 @param install_timeout: The timeout to use when installing the chromeos
1057 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -08001058
Scott Zawalski62bacae2013-03-05 10:40:32 -05001059 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -07001060
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001061 """
beepsf079cfb2013-09-18 17:49:51 -07001062 usb_boot_timer_key = ('servo_install.usb_boot_timeout_%s'
1063 % usb_boot_timeout)
1064 logging.info('Downloading image to USB, then booting from it. Usb boot '
1065 'timeout = %s', usb_boot_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -08001066 timer = autotest_stats.Timer(usb_boot_timer_key)
beepsf079cfb2013-09-18 17:49:51 -07001067 timer.start()
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001068 self.servo.install_recovery_image(image_url)
beepsf079cfb2013-09-18 17:49:51 -07001069 if not self.wait_up(timeout=usb_boot_timeout):
J. Richard Barnette91137f02016-03-10 16:52:26 -08001070 raise hosts.AutoservRepairError(
Scott Zawalski62bacae2013-03-05 10:40:32 -05001071 'DUT failed to boot from USB after %d seconds' %
beepsf079cfb2013-09-18 17:49:51 -07001072 usb_boot_timeout)
1073 timer.stop()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001074
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001075 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
1076 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001077 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001078 try:
1079 self.run('chromeos-tpm-recovery')
1080 except error.AutoservRunError:
1081 logging.warn('chromeos-tpm-recovery is too old.')
1082
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001083
beepsf079cfb2013-09-18 17:49:51 -07001084 install_timer_key = ('servo_install.install_timeout_%s'
1085 % install_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -08001086 timer = autotest_stats.Timer(install_timer_key)
beepsf079cfb2013-09-18 17:49:51 -07001087 timer.start()
1088 logging.info('Installing image through chromeos-install.')
J. Richard Barnette9af19632015-09-25 12:18:03 -07001089 self.run('chromeos-install --yes', timeout=install_timeout)
1090 self.halt()
beepsf079cfb2013-09-18 17:49:51 -07001091 timer.stop()
1092
1093 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001094 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -07001095 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001096 # N.B. The Servo API requires that we use power_on() here
1097 # for two reasons:
1098 # 1) After turning on a DUT in recovery mode, you must turn
1099 # it off and then on with power_on() once more to
1100 # disable recovery mode (this is a Parrot specific
1101 # requirement).
1102 # 2) After power_off(), the only way to turn on is with
1103 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -07001104 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001105
1106 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001107 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
1108 raise error.AutoservError('DUT failed to reboot installed '
1109 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -05001110 self.BOOT_TIMEOUT)
1111
1112
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001113 def _is_firmware_repair_supported(self):
1114 """Check if the firmware repair is supported.
Dan Shi3d7a0e12015-10-12 11:55:45 -07001115
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001116 The firmware repair is only applicable to DUTs in pools listed in
1117 global config CROS/pools_support_firmware_repair.
Dan Shi3d7a0e12015-10-12 11:55:45 -07001118
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001119 @return: True if it is supported; otherwise False.
Dan Shi3d7a0e12015-10-12 11:55:45 -07001120 """
1121 logging.info('Checking if host %s can be repaired with firmware '
1122 'repair.', self.hostname)
1123 pools = server_utils.get_labels_from_afe(self.hostname, 'pool:',
1124 self._AFE)
1125 pools_support_firmware_repair = CONFIG.get_config_value('CROS',
1126 'pools_support_firmware_repair', type=str).split(',')
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001127
1128 return (pools and pools_support_firmware_repair and
1129 set(pools).intersection(set(pools_support_firmware_repair)))
1130
1131
Richard Barnette9a26ad62016-06-10 12:03:08 -07001132 def repair_servo(self):
Dan Shi90466352015-09-22 15:01:05 -07001133 """
Richard Barnette9a26ad62016-06-10 12:03:08 -07001134 Confirm that servo is initialized and verified.
Dan Shi90466352015-09-22 15:01:05 -07001135
Richard Barnette9a26ad62016-06-10 12:03:08 -07001136 If the servo object is missing, attempt to repair the servo
1137 host. Repair failures are passed back to the caller.
1138
1139 @raise AutoservError: If there is no servo host for this CrOS
1140 host.
1141 """
1142 if self.servo:
1143 return
1144 if not self._servo_host:
1145 raise error.AutoservError('No servo host for %s.' %
1146 self.hostname)
1147 self._servo_host.repair()
1148 self.servo = self._servo_host.get_servo()
Dan Shi90466352015-09-22 15:01:05 -07001149
1150
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -08001151 def repair(self):
1152 """Attempt to get the DUT to pass `self.verify()`.
Richard Barnette82c35912012-11-20 10:09:10 -08001153
1154 This overrides the base class function for repair; it does
J. Richard Barnette91137f02016-03-10 16:52:26 -08001155 not call back to the parent class, but instead relies on
1156 `self._repair_strategy` to coordinate the verification and
1157 repair steps needed to get the DUT working.
Richard Barnette82c35912012-11-20 10:09:10 -08001158 """
J. Richard Barnette91137f02016-03-10 16:52:26 -08001159 self._repair_strategy.repair(self)
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001160
Richard Barnette82c35912012-11-20 10:09:10 -08001161
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001162 def close(self):
Fang Deng0ca40e22013-08-27 17:47:44 -07001163 super(CrosHost, self).close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001164
1165
Dan Shi49ca0932014-11-14 11:22:27 -08001166 def get_power_supply_info(self):
1167 """Get the output of power_supply_info.
1168
1169 power_supply_info outputs the info of each power supply, e.g.,
1170 Device: Line Power
1171 online: no
1172 type: Mains
1173 voltage (V): 0
1174 current (A): 0
1175 Device: Battery
1176 state: Discharging
1177 percentage: 95.9276
1178 technology: Li-ion
1179
1180 Above output shows two devices, Line Power and Battery, with details of
1181 each device listed. This function parses the output into a dictionary,
1182 with key being the device name, and value being a dictionary of details
1183 of the device info.
1184
1185 @return: The dictionary of power_supply_info, e.g.,
1186 {'Line Power': {'online': 'yes', 'type': 'main'},
1187 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001188 @raise error.AutoservRunError if power_supply_info tool is not found in
1189 the DUT. Caller should handle this error to avoid false failure
1190 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001191 """
1192 result = self.run('power_supply_info').stdout.strip()
1193 info = {}
1194 device_name = None
1195 device_info = {}
1196 for line in result.split('\n'):
1197 pair = [v.strip() for v in line.split(':')]
1198 if len(pair) != 2:
1199 continue
1200 if pair[0] == 'Device':
1201 if device_name:
1202 info[device_name] = device_info
1203 device_name = pair[1]
1204 device_info = {}
1205 else:
1206 device_info[pair[0]] = pair[1]
1207 if device_name and not device_name in info:
1208 info[device_name] = device_info
1209 return info
1210
1211
1212 def get_battery_percentage(self):
1213 """Get the battery percentage.
1214
1215 @return: The percentage of battery level, value range from 0-100. Return
1216 None if the battery info cannot be retrieved.
1217 """
1218 try:
1219 info = self.get_power_supply_info()
1220 logging.info(info)
1221 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001222 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001223 return None
1224
1225
1226 def is_ac_connected(self):
1227 """Check if the dut has power adapter connected and charging.
1228
1229 @return: True if power adapter is connected and charging.
1230 """
1231 try:
1232 info = self.get_power_supply_info()
1233 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001234 except (KeyError, error.AutoservRunError):
1235 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001236
1237
Simran Basi5e6339a2013-03-21 11:34:32 -07001238 def _cleanup_poweron(self):
1239 """Special cleanup method to make sure hosts always get power back."""
1240 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1241 hosts = afe.get_hosts(hostname=self.hostname)
1242 if not hosts or not (self._RPM_OUTLET_CHANGED in
1243 hosts[0].attributes):
1244 return
1245 logging.debug('This host has recently interacted with the RPM'
1246 ' Infrastructure. Ensuring power is on.')
1247 try:
1248 self.power_on()
Dan Shi7dca56e2014-11-11 17:07:56 -08001249 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1250 hostname=self.hostname)
Simran Basi5e6339a2013-03-21 11:34:32 -07001251 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001252 logging.error('Failed to turn Power On for this host after '
1253 'cleanup through the RPM Infrastructure.')
Gabe Blackb72f4fb2015-01-20 16:47:13 -08001254 autotest_es.post(
Dan Shi7dca56e2014-11-11 17:07:56 -08001255 type_str='RPM_poweron_failure',
1256 metadata={'hostname': self.hostname})
Dan Shi49ca0932014-11-14 11:22:27 -08001257
1258 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -08001259 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -08001260 raise
1261 elif self.is_ac_connected():
1262 logging.info('The device has power adapter connected and '
1263 'charging. No need to try to turn RPM on '
1264 'again.')
1265 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1266 hostname=self.hostname)
1267 logging.info('Battery level is now at %s%%. The device may '
1268 'still have enough power to run test, so no '
1269 'exception will be raised.', battery_percentage)
1270
Simran Basi5e6339a2013-03-21 11:34:32 -07001271
beepsc87ff602013-07-31 21:53:00 -07001272 def _is_factory_image(self):
1273 """Checks if the image on the DUT is a factory image.
1274
1275 @return: True if the image on the DUT is a factory image.
1276 False otherwise.
1277 """
1278 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1279 return result.exit_status == 0
1280
1281
1282 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001283 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001284
1285 @raises: FactoryImageCheckerException for factory images, since
1286 we cannot attempt to restart ui on them.
1287 error.AutoservRunError for any other type of error that
1288 occurs while restarting ui.
1289 """
1290 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001291 raise FactoryImageCheckerException('Cannot restart ui on factory '
1292 'images')
beepsc87ff602013-07-31 21:53:00 -07001293
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001294 # TODO(jrbarnette): The command to stop/start the ui job
1295 # should live inside cros_ui, too. However that would seem
1296 # to imply interface changes to the existing start()/restart()
1297 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -07001298 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001299 self.run('stop ui; start ui')
1300 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001301
1302
Dan Shi549fb822015-03-24 18:01:11 -07001303 def get_release_version(self):
1304 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1305
1306 @returns The version string in lsb-release, under attribute
1307 CHROMEOS_RELEASE_VERSION.
1308 """
1309 lsb_release_content = self.run(
1310 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1311 return lsbrelease_utils.get_chromeos_release_version(
1312 lsb_release_content=lsb_release_content)
1313
1314
1315 def verify_cros_version_label(self):
1316 """ Make sure host's cros-version label match the actual image in dut.
1317
1318 Remove any cros-version: label that doesn't match that installed in
1319 the dut.
1320
1321 @param raise_error: Set to True to raise exception if any mismatch found
1322
1323 @raise error.AutoservError: If any mismatch between cros-version label
1324 and the build installed in dut is found.
1325 """
1326 labels = self._AFE.get_labels(
1327 name__startswith=ds_constants.VERSION_PREFIX,
1328 host__hostname=self.hostname)
1329 mismatch_found = False
1330 if labels:
1331 # Get CHROMEOS_RELEASE_VERSION from lsb-release, e.g., 6908.0.0.
1332 # Note that it's different from cros-version label, which has
1333 # builder and branch info, e.g.,
1334 # cros-version:peppy-release/R43-6908.0.0
1335 release_version = self.get_release_version()
1336 host_list = [self.hostname]
1337 for label in labels:
1338 # Remove any cros-version label that does not match
1339 # release_version.
1340 build_version = label.name[len(ds_constants.VERSION_PREFIX):]
1341 if not utils.version_match(build_version, release_version):
1342 logging.warn('cros-version label "%s" does not match '
1343 'release version %s. Removing the label.',
1344 label.name, release_version)
1345 label.remove_hosts(hosts=host_list)
1346 mismatch_found = True
1347 if mismatch_found:
Dan Shi1057bae2015-03-30 11:35:09 -07001348 autotest_es.post(use_http=True,
1349 type_str='cros_version_label_mismatch',
1350 metadata={'hostname': self.hostname})
Dan Shi549fb822015-03-24 18:01:11 -07001351 raise error.AutoservError('The host has wrong cros-version label.')
1352
1353
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001354 def verify_firmware_status(self):
1355 """Verify the host's firmware is in a good state.
1356
1357 @raise error.AutoservError: If state is not good.
1358 """
1359 if self._is_firmware_repair_supported():
Tom Wai-Hong Tam93a29562016-01-23 04:16:53 +08001360 try:
1361 # Read the AP firmware and dump the sections we are interested.
1362 cmd = ('mkdir /tmp/verify_firmware; '
1363 'cd /tmp/verify_firmware; '
1364 'for section in VBLOCK_A VBLOCK_B FW_MAIN_A FW_MAIN_B; '
1365 'do flashrom -r image.bin -i $section:$section; '
1366 'done')
1367 self.run(cmd)
1368
1369 # Verify the firmware blocks A and B.
1370 cmd = ('vbutil_firmware --verify /tmp/verify_firmware/VBLOCK_%c'
1371 ' --signpubkey /usr/share/vboot/devkeys/root_key.vbpubk'
1372 ' --fv /tmp/verify_firmware/FW_MAIN_%c')
1373 for c in ('A', 'B'):
1374 rv = self.run(cmd % (c, c), ignore_status=True)
1375 if rv.exit_status:
Dan Shic67f1332016-04-06 12:38:06 -07001376 raise error.AutoservError(
1377 'Firmware %c is in a bad state.' % c)
Tom Wai-Hong Tam93a29562016-01-23 04:16:53 +08001378 finally:
1379 # Remove the tempoary files.
1380 self.run('rm -rf /tmp/verify_firmware')
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001381 else:
1382 logging.info('Do not care about firmware status when the host '
1383 'is not in pools that support firmware repair.')
1384
1385
beepsc87ff602013-07-31 21:53:00 -07001386 def cleanup(self):
MK Ryu35d661e2014-09-25 17:44:10 -07001387 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001388 try:
beepsc87ff602013-07-31 21:53:00 -07001389 self._restart_ui()
1390 except (error.AutotestRunError, error.AutoservRunError,
1391 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001392 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001393 # Since restarting the UI fails fall back to normal Autotest
1394 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001395 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001396 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001397 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001398 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001399 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001400
1401
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001402 def reboot(self, **dargs):
1403 """
1404 This function reboots the site host. The more generic
1405 RemoteHost.reboot() performs sync and sleeps for 5
1406 seconds. This is not necessary for Chrome OS devices as the
1407 sync should be finished in a short time during the reboot
1408 command.
1409 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001410 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001411 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001412 dargs['reboot_cmd'] = ('sleep 1; '
1413 'reboot & sleep %d; '
1414 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001415 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001416 if 'fastsync' not in dargs:
1417 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001418
Charlie Mooneya8e6dab2014-05-29 14:37:55 -07001419 # For purposes of logging reboot times:
1420 # Get the board name i.e. 'daisy_spring'
Michael Liangca4f5a62014-07-10 15:45:13 -07001421 board_fullname = self.get_board()
1422
1423 # Strip the prefix and add it to dargs.
1424 dargs['board'] = board_fullname[board_fullname.find(':')+1:]
Fang Deng0ca40e22013-08-27 17:47:44 -07001425 super(CrosHost, self).reboot(**dargs)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001426
1427
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001428 def suspend(self, **dargs):
1429 """
1430 This function suspends the site host.
1431 """
1432 suspend_time = dargs.get('suspend_time', 60)
1433 dargs['timeout'] = suspend_time
1434 if 'suspend_cmd' not in dargs:
J. Richard Barnette9af19632015-09-25 12:18:03 -07001435 dargs['suspend_cmd'] = ' && '.join([
1436 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001437 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
J. Richard Barnette9af19632015-09-25 12:18:03 -07001438 'powerd_dbus_suspend --delay=0'])
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001439 super(CrosHost, self).suspend(**dargs)
1440
1441
Simran Basiec564392014-08-25 16:48:09 -07001442 def upstart_status(self, service_name):
1443 """Check the status of an upstart init script.
1444
1445 @param service_name: Service to look up.
1446
1447 @returns True if the service is running, False otherwise.
1448 """
1449 return self.run('status %s | grep start/running' %
1450 service_name).stdout.strip() != ''
1451
1452
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001453 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001454 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001455
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001456 Tests for the following conditions:
1457 1. All conditions tested by the parent version of this
1458 function.
1459 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001460 3. Sufficient space in /mnt/stateful_partition/encrypted.
1461 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001462
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001463 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001464 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001465 default_kilo_inodes_required = CONFIG.get_config_value(
1466 'SERVER', 'kilo_inodes_required', type=int, default=100)
1467 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1468 kilo_inodes_required = CONFIG.get_config_value(
1469 'SERVER', 'kilo_inodes_required_%s' % board,
1470 type=int, default=default_kilo_inodes_required)
1471 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001472 self.check_diskspace(
1473 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001474 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001475 'SERVER', 'gb_diskspace_required', type=float,
1476 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001477 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1478 # Not all targets build with encrypted stateful support.
1479 if self.path_exists(encrypted_stateful_path):
1480 self.check_diskspace(
1481 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001482 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001483 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1484 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001485
Simran Basiec564392014-08-25 16:48:09 -07001486 if not self.upstart_status('system-services'):
Prashanth B5d0a0512014-04-25 12:26:08 -07001487 raise error.AutoservError('Chrome failed to reach login. '
1488 'System services not running.')
1489
beepsc87ff602013-07-31 21:53:00 -07001490 # Factory images don't run update engine,
1491 # goofy controls dbus on these DUTs.
1492 if not self._is_factory_image():
1493 self.run('update_engine_client --status')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001494
Dan Shi549fb822015-03-24 18:01:11 -07001495 self.verify_cros_version_label()
1496
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001497 self.verify_firmware_status()
1498
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001499
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001500 def verify(self):
1501 self._repair_strategy.verify(self)
1502
1503
Fang Deng96667ca2013-08-01 17:46:18 -07001504 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
1505 connect_timeout=None, alive_interval=None):
1506 """Override default make_ssh_command to use options tuned for Chrome OS.
1507
1508 Tuning changes:
1509 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1510 connection failure. Consistency with remote_access.sh.
1511
Samuel Tan2ce155b2015-06-23 18:24:38 -07001512 - ServerAliveInterval=900; which causes SSH to ping connection every
1513 900 seconds. In conjunction with ServerAliveCountMax ensures
1514 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001515 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001516 the test completed successfully. Later increased from 180 seconds to
1517 900 seconds to account for tests where the DUT is suspended for
1518 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001519
1520 - ServerAliveCountMax=3; consistency with remote_access.sh.
1521
1522 - ConnectAttempts=4; reduce flakiness in connection errors;
1523 consistency with remote_access.sh.
1524
1525 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1526 Host keys change with every new installation, don't waste
1527 memory/space saving them.
1528
1529 - SSH protocol forced to 2; needed for ServerAliveInterval.
1530
1531 @param user User name to use for the ssh connection.
1532 @param port Port on the target host to use for ssh connection.
1533 @param opts Additional options to the ssh command.
1534 @param hosts_file Ignored.
1535 @param connect_timeout Ignored.
1536 @param alive_interval Ignored.
1537 """
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001538 base_command = ('/usr/bin/ssh -a -x %s %s %s'
1539 ' -o StrictHostKeyChecking=no'
Fang Deng96667ca2013-08-01 17:46:18 -07001540 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Samuel Tan2ce155b2015-06-23 18:24:38 -07001541 ' -o ConnectTimeout=30 -o ServerAliveInterval=900'
Fang Deng96667ca2013-08-01 17:46:18 -07001542 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
1543 ' -o Protocol=2 -l %s -p %d')
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001544 return base_command % (self._ssh_verbosity_flag, self._ssh_options,
1545 opts, user, port)
Jason Abeleb6f924f2013-11-13 16:01:54 -08001546 def syslog(self, message, tag='autotest'):
1547 """Logs a message to syslog on host.
1548
1549 @param message String message to log into syslog
1550 @param tag String tag prefix for syslog
1551
1552 """
1553 self.run('logger -t "%s" "%s"' % (tag, message))
1554
1555
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001556 def _ping_check_status(self, status):
1557 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001558
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001559 @param status Check the ping status against this value.
1560 @return True iff `status` and the result of ping are the same
1561 (i.e. both True or both False).
1562
1563 """
1564 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1565 return not (status ^ (ping_val == 0))
1566
1567 def _ping_wait_for_status(self, status, timeout):
1568 """Wait for the host to have a given status (UP or DOWN).
1569
1570 Status is checked by polling. Polling will not last longer
1571 than the number of seconds in `timeout`. The polling
1572 interval will be long enough that only approximately
1573 _PING_WAIT_COUNT polling cycles will be executed, subject
1574 to a maximum interval of about one minute.
1575
1576 @param status Waiting will stop immediately if `ping` of the
1577 host returns this status.
1578 @param timeout Poll for at most this many seconds.
1579 @return True iff the host status from `ping` matched the
1580 requested status at the time of return.
1581
1582 """
1583 # _ping_check_status() takes about 1 second, hence the
1584 # "- 1" in the formula below.
1585 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1586 end_time = time.time() + timeout
1587 while time.time() <= end_time:
1588 if self._ping_check_status(status):
1589 return True
1590 if poll_interval > 0:
1591 time.sleep(poll_interval)
1592
1593 # The last thing we did was sleep(poll_interval), so it may
1594 # have been too long since the last `ping`. Check one more
1595 # time, just to be sure.
1596 return self._ping_check_status(status)
1597
1598 def ping_wait_up(self, timeout):
1599 """Wait for the host to respond to `ping`.
1600
1601 N.B. This method is not a reliable substitute for
1602 `wait_up()`, because a host that responds to ping will not
1603 necessarily respond to ssh. This method should only be used
1604 if the target DUT can be considered functional even if it
1605 can't be reached via ssh.
1606
1607 @param timeout Minimum time to allow before declaring the
1608 host to be non-responsive.
1609 @return True iff the host answered to ping before the timeout.
1610
1611 """
1612 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001613
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001614 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001615 """Wait until the host no longer responds to `ping`.
1616
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001617 This function can be used as a slightly faster version of
1618 `wait_down()`, by avoiding potentially long ssh timeouts.
1619
1620 @param timeout Minimum time to allow for the host to become
1621 non-responsive.
1622 @return True iff the host quit answering ping before the
1623 timeout.
1624
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001625 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001626 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001627
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001628 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001629 """Wait for the client to enter low-power sleep mode.
1630
1631 The test for "is asleep" can't distinguish a system that is
1632 powered off; to confirm that the unit was asleep, it is
1633 necessary to force resume, and then call
1634 `test_wait_for_resume()`.
1635
1636 This function is expected to be called from a test as part
1637 of a sequence like the following:
1638
1639 ~~~~~~~~
1640 boot_id = host.get_boot_id()
1641 # trigger sleep on the host
1642 host.test_wait_for_sleep()
1643 # trigger resume on the host
1644 host.test_wait_for_resume(boot_id)
1645 ~~~~~~~~
1646
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001647 @param sleep_timeout time limit in seconds to allow the host sleep.
1648
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001649 @exception TestFail The host did not go to sleep within
1650 the allowed time.
1651 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001652 if sleep_timeout is None:
1653 sleep_timeout = self.SLEEP_TIMEOUT
1654
1655 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001656 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001657 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001658
1659
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001660 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001661 """Wait for the client to resume from low-power sleep mode.
1662
1663 The `old_boot_id` parameter should be the value from
1664 `get_boot_id()` obtained prior to entering sleep mode. A
1665 `TestFail` exception is raised if the boot id changes.
1666
1667 See @ref test_wait_for_sleep for more on this function's
1668 usage.
1669
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001670 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001671 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001672 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001673
1674 @exception TestFail The host did not respond within the
1675 allowed time.
1676 @exception TestFail The host responded, but the boot id test
1677 indicated a reboot rather than a sleep
1678 cycle.
1679 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001680 if resume_timeout is None:
1681 resume_timeout = self.RESUME_TIMEOUT
1682
1683 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001684 raise error.TestFail(
1685 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001686 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001687 else:
1688 new_boot_id = self.get_boot_id()
1689 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001690 logging.error('client rebooted (old boot %s, new boot %s)',
1691 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001692 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001693 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001694
1695
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001696 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001697 """Wait for the client to shut down.
1698
1699 The test for "has shut down" can't distinguish a system that
1700 is merely asleep; to confirm that the unit was down, it is
1701 necessary to force boot, and then call test_wait_for_boot().
1702
1703 This function is expected to be called from a test as part
1704 of a sequence like the following:
1705
1706 ~~~~~~~~
1707 boot_id = host.get_boot_id()
1708 # trigger shutdown on the host
1709 host.test_wait_for_shutdown()
1710 # trigger boot on the host
1711 host.test_wait_for_boot(boot_id)
1712 ~~~~~~~~
1713
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001714 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001715 @exception TestFail The host did not shut down within the
1716 allowed time.
1717 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001718 if shutdown_timeout is None:
1719 shutdown_timeout = self.SHUTDOWN_TIMEOUT
1720
1721 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001722 raise error.TestFail(
1723 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001724 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001725
1726
1727 def test_wait_for_boot(self, old_boot_id=None):
1728 """Wait for the client to boot from cold power.
1729
1730 The `old_boot_id` parameter should be the value from
1731 `get_boot_id()` obtained prior to shutting down. A
1732 `TestFail` exception is raised if the boot id does not
1733 change. The boot id test is omitted if `old_boot_id` is not
1734 specified.
1735
1736 See @ref test_wait_for_shutdown for more on this function's
1737 usage.
1738
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001739 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001740 shut down.
1741
1742 @exception TestFail The host did not respond within the
1743 allowed time.
1744 @exception TestFail The host responded, but the boot id test
1745 indicated that there was no reboot.
1746 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001747 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001748 raise error.TestFail(
1749 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001750 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001751 elif old_boot_id:
1752 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001753 logging.error('client not rebooted (boot %s)',
1754 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001755 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001756 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07001757
1758
1759 @staticmethod
1760 def check_for_rpm_support(hostname):
1761 """For a given hostname, return whether or not it is powered by an RPM.
1762
Simran Basi1df55112013-09-06 11:25:09 -07001763 @param hostname: hostname to check for rpm support.
1764
Simran Basid5e5e272012-09-24 15:23:59 -07001765 @return None if this host does not follows the defined naming format
1766 for RPM powered DUT's in the lab. If it does follow the format,
1767 it returns a regular expression MatchObject instead.
1768 """
Fang Dengbaff9082015-01-06 13:46:15 -08001769 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001770
1771
1772 def has_power(self):
1773 """For this host, return whether or not it is powered by an RPM.
1774
1775 @return True if this host is in the CROS lab and follows the defined
1776 naming format.
1777 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001778 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001779
1780
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001781 def _set_power(self, state, power_method):
1782 """Sets the power to the host via RPM, Servo or manual.
1783
1784 @param state Specifies which power state to set to DUT
1785 @param power_method Specifies which method of power control to
1786 use. By default "RPM" will be used. Valid values
1787 are the strings "RPM", "manual", "servoj10".
1788
1789 """
1790 ACCEPTABLE_STATES = ['ON', 'OFF']
1791
1792 if state.upper() not in ACCEPTABLE_STATES:
1793 raise error.TestError('State must be one of: %s.'
1794 % (ACCEPTABLE_STATES,))
1795
1796 if power_method == self.POWER_CONTROL_SERVO:
1797 logging.info('Setting servo port J10 to %s', state)
1798 self.servo.set('prtctl3_pwren', state.lower())
1799 time.sleep(self._USB_POWER_TIMEOUT)
1800 elif power_method == self.POWER_CONTROL_MANUAL:
1801 logging.info('You have %d seconds to set the AC power to %s.',
1802 self._POWER_CYCLE_TIMEOUT, state)
1803 time.sleep(self._POWER_CYCLE_TIMEOUT)
1804 else:
1805 if not self.has_power():
1806 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07001807 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1808 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
1809 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07001810 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07001811
1812
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001813 def power_off(self, power_method=POWER_CONTROL_RPM):
1814 """Turn off power to this host via RPM, Servo or manual.
1815
1816 @param power_method Specifies which method of power control to
1817 use. By default "RPM" will be used. Valid values
1818 are the strings "RPM", "manual", "servoj10".
1819
1820 """
1821 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001822
1823
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001824 def power_on(self, power_method=POWER_CONTROL_RPM):
1825 """Turn on power to this host via RPM, Servo or manual.
1826
1827 @param power_method Specifies which method of power control to
1828 use. By default "RPM" will be used. Valid values
1829 are the strings "RPM", "manual", "servoj10".
1830
1831 """
1832 self._set_power('ON', power_method)
1833
1834
1835 def power_cycle(self, power_method=POWER_CONTROL_RPM):
1836 """Cycle power to this host by turning it OFF, then ON.
1837
1838 @param power_method Specifies which method of power control to
1839 use. By default "RPM" will be used. Valid values
1840 are the strings "RPM", "manual", "servoj10".
1841
1842 """
1843 if power_method in (self.POWER_CONTROL_SERVO,
1844 self.POWER_CONTROL_MANUAL):
1845 self.power_off(power_method=power_method)
1846 time.sleep(self._POWER_CYCLE_TIMEOUT)
1847 self.power_on(power_method=power_method)
1848 else:
1849 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001850
1851
1852 def get_platform(self):
1853 """Determine the correct platform label for this host.
1854
1855 @returns a string representing this host's platform.
1856 """
1857 crossystem = utils.Crossystem(self)
1858 crossystem.init()
1859 # Extract fwid value and use the leading part as the platform id.
1860 # fwid generally follow the format of {platform}.{firmware version}
1861 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1862 platform = crossystem.fwid().split('.')[0].lower()
1863 # Newer platforms start with 'Google_' while the older ones do not.
1864 return platform.replace('google_', '')
1865
1866
Hung-ying Tyanb1328032014-04-01 14:18:54 +08001867 def get_architecture(self):
1868 """Determine the correct architecture label for this host.
1869
1870 @returns a string representing this host's architecture.
1871 """
1872 crossystem = utils.Crossystem(self)
1873 crossystem.init()
1874 return crossystem.arch()
1875
1876
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001877 def get_chrome_version(self):
1878 """Gets the Chrome version number and milestone as strings.
1879
1880 Invokes "chrome --version" to get the version number and milestone.
1881
1882 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
1883 current Chrome version number as a string (in the form "W.X.Y.Z")
1884 and "milestone" is the first component of the version number
1885 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
1886 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
1887 of "chrome --version" and the milestone will be the empty string.
1888
1889 """
MK Ryu35d661e2014-09-25 17:44:10 -07001890 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001891 return utils.parse_chrome_version(version_string)
1892
J. Richard Barnetted2af5852016-02-05 15:03:10 -08001893
Kevin Chenga2619dc2016-03-28 11:42:08 -07001894 # TODO(kevcheng): change this to just return the board without the
1895 # 'board:' prefix and fix up all the callers. Also look into removing the
1896 # need for this method.
Simran Basic6f1f7a2012-10-16 10:47:46 -07001897 def get_board(self):
1898 """Determine the correct board label for this host.
1899
1900 @returns a string representing this host's board.
1901 """
1902 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1903 run_method=self.run)
J. Richard Barnetted2af5852016-02-05 15:03:10 -08001904 return (ds_constants.BOARD_PREFIX +
1905 release_info['CHROMEOS_RELEASE_BOARD'])
Simran Basic6f1f7a2012-10-16 10:47:46 -07001906
1907
Kevin Chenga328da62016-03-31 10:49:04 -07001908
1909 def has_lightsensor(self):
1910 """Determine the correct board label for this host.
1911
1912 @returns the string 'lightsensor' if this host has a lightsensor or
1913 None if it does not.
1914 """
1915 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
1916 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
1917 try:
1918 # Run the search cmd following the symlinks. Stderr_tee is set to
1919 # None as there can be a symlink loop, but the command will still
1920 # execute correctly with a few messages printed to stderr.
1921 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
1922 return 'lightsensor'
1923 except error.AutoservRunError:
1924 # egrep exited with a return code of 1 meaning none of the possible
1925 # lightsensor files existed.
1926 return None
1927
1928
1929 def has_bluetooth(self):
1930 """Determine the correct board label for this host.
1931
1932 @returns the string 'bluetooth' if this host has bluetooth or
1933 None if it does not.
1934 """
1935 try:
1936 self.run('test -d /sys/class/bluetooth/hci0')
1937 # test exited with a return code of 0.
1938 return 'bluetooth'
1939 except error.AutoservRunError:
1940 # test exited with a return code 1 meaning the directory did not
1941 # exist.
1942 return None
1943
1944
Kevin Chenga328da62016-03-31 10:49:04 -07001945 def get_accels(self):
1946 """
1947 Determine the type of accelerometers on this host.
1948
1949 @returns a string representing this host's accelerometer type.
1950 At present, it only returns "accel:cros-ec", for accelerometers
1951 attached to a Chrome OS EC, or none, if no accelerometers.
1952 """
1953 # Check to make sure we have ectool
1954 rv = self.run('which ectool', ignore_status=True)
1955 if rv.exit_status:
1956 logging.info("No ectool cmd found, assuming no EC accelerometers")
1957 return None
1958
1959 # Check that the EC supports the motionsense command
1960 rv = self.run('ectool motionsense', ignore_status=True)
1961 if rv.exit_status:
1962 logging.info("EC does not support motionsense command "
1963 "assuming no EC accelerometers")
1964 return None
1965
1966 # Check that EC motion sensors are active
1967 active = self.run('ectool motionsense active').stdout.split('\n')
1968 if active[0] == "0":
1969 logging.info("Motion sense inactive, assuming no EC accelerometers")
1970 return None
1971
1972 logging.info("EC accelerometers found")
1973 return 'accel:cros-ec'
1974
1975
1976 def has_chameleon(self):
1977 """Determine if a Chameleon connected to this host.
1978
1979 @returns a list containing two strings ('chameleon' and
1980 'chameleon:' + label, e.g. 'chameleon:hdmi') if this host
1981 has a Chameleon or None if it has not.
1982 """
1983 if self._chameleon_host:
1984 return ['chameleon', 'chameleon:' + self.chameleon.get_label()]
1985 else:
1986 return None
1987
1988
1989 def has_loopback_dongle(self):
1990 """Determine if an audio loopback dongle is plugged to this host.
1991
1992 @returns 'audio_loopback_dongle' when there is an audio loopback dongle
1993 plugged to this host.
1994 None when there is no audio loopback dongle
1995 plugged to this host.
1996 """
1997 nodes_info = self.run(command=cras_utils.get_cras_nodes_cmd(),
1998 ignore_status=True).stdout
1999 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
2000 cras_utils.node_type_is_plugged('MIC', nodes_info)):
2001 return 'audio_loopback_dongle'
2002 else:
2003 return None
2004
2005
2006 def get_power_supply(self):
2007 """
2008 Determine what type of power supply the host has
2009
2010 @returns a string representing this host's power supply.
2011 'power:battery' when the device has a battery intended for
2012 extended use
2013 'power:AC_primary' when the device has a battery not intended
2014 for extended use (for moving the machine, etc)
2015 'power:AC_only' when the device has no battery at all.
2016 """
2017 psu = self.run(command='mosys psu type', ignore_status=True)
2018 if psu.exit_status:
2019 # The psu command for mosys is not included for all platforms. The
2020 # assumption is that the device will have a battery if the command
2021 # is not found.
2022 return 'power:battery'
2023
2024 psu_str = psu.stdout.strip()
2025 if psu_str == 'unknown':
2026 return None
2027
2028 return 'power:%s' % psu_str
2029
2030
2031 def get_storage(self):
2032 """
2033 Determine the type of boot device for this host.
2034
2035 Determine if the internal device is SCSI or dw_mmc device.
2036 Then check that it is SSD or HDD or eMMC or something else.
2037
2038 @returns a string representing this host's internal device type.
2039 'storage:ssd' when internal device is solid state drive
2040 'storage:hdd' when internal device is hard disk drive
2041 'storage:mmc' when internal device is mmc drive
2042 None When internal device is something else or
2043 when we are unable to determine the type
2044 """
2045 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
2046 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2047 '. /usr/share/misc/chromeos-common.sh;',
2048 'load_base_vars;',
2049 'get_fixed_dst_drive'])
2050 rootdev = self.run(command=rootdev_cmd, ignore_status=True)
2051 if rootdev.exit_status:
2052 logging.info("Fail to run %s", rootdev_cmd)
2053 return None
2054 rootdev_str = rootdev.stdout.strip()
2055
2056 if not rootdev_str:
2057 return None
2058
2059 rootdev_base = os.path.basename(rootdev_str)
2060
2061 mmc_pattern = '/dev/mmcblk[0-9]'
2062 if re.match(mmc_pattern, rootdev_str):
2063 # Use type to determine if the internal device is eMMC or somthing
2064 # else. We can assume that MMC is always an internal device.
2065 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
2066 type = self.run(command=type_cmd, ignore_status=True)
2067 if type.exit_status:
2068 logging.info("Fail to run %s", type_cmd)
2069 return None
2070 type_str = type.stdout.strip()
2071
2072 if type_str == 'MMC':
2073 return 'storage:mmc'
2074
2075 scsi_pattern = '/dev/sd[a-z]+'
2076 if re.match(scsi_pattern, rootdev.stdout):
2077 # Read symlink for /sys/block/sd* to determine if the internal
2078 # device is connected via ata or usb.
2079 link_cmd = 'readlink /sys/block/%s' % rootdev_base
2080 link = self.run(command=link_cmd, ignore_status=True)
2081 if link.exit_status:
2082 logging.info("Fail to run %s", link_cmd)
2083 return None
2084 link_str = link.stdout.strip()
2085 if 'usb' in link_str:
2086 return None
2087
2088 # Read rotation to determine if the internal device is ssd or hdd.
2089 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
2090 % rootdev_base)
2091 rotate = self.run(command=rotate_cmd, ignore_status=True)
2092 if rotate.exit_status:
2093 logging.info("Fail to run %s", rotate_cmd)
2094 return None
2095 rotate_str = rotate.stdout.strip()
2096
2097 rotate_dict = {'0':'storage:ssd', '1':'storage:hdd'}
2098 return rotate_dict.get(rotate_str)
2099
2100 # All other internal device / error case will always fall here
2101 return None
2102
2103
2104 def get_servo(self):
2105 """Determine if the host has a servo attached.
2106
2107 If the host has a working servo attached, it should have a servo label.
2108
2109 @return: string 'servo' if the host has servo attached. Otherwise,
2110 returns None.
2111 """
2112 return 'servo' if self._servo_host else None
2113
2114
2115 def get_video_labels(self):
2116 """Run /usr/local/bin/avtest_label_detect to get a list of video labels.
2117
2118 Sample output of avtest_label_detect:
2119 Detected label: hw_video_acc_vp8
2120 Detected label: webcam
2121
2122 @return: A list of labels detected by tool avtest_label_detect.
2123 """
2124 try:
2125 result = self.run('/usr/local/bin/avtest_label_detect').stdout
2126 return re.findall('^Detected label: (\w+)$', result, re.M)
2127 except error.AutoservRunError:
2128 # The tool is not installed.
2129 return []
2130
2131
2132 def is_video_glitch_detection_supported(self):
2133 """ Determine if a board under test is supported for video glitch
2134 detection tests.
2135
2136 @return: 'video_glitch_detection' if board is supported, None otherwise.
2137 """
2138 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2139
2140 if board in video_test_constants.SUPPORTED_BOARDS:
2141 return 'video_glitch_detection'
2142
2143 return None
2144
2145
2146 def get_touch(self):
2147 """
2148 Determine whether board under test has a touchpad or touchscreen.
2149
2150 @return: A list of some combination of 'touchscreen' and 'touchpad',
2151 depending on what is present on the device.
2152
2153 """
2154 labels = []
2155 looking_for = ['touchpad', 'touchscreen']
2156 player = input_playback.InputPlayback()
2157 input_events = self.run('ls /dev/input/event*').stdout.strip().split()
2158 filename = '/tmp/touch_labels'
2159 for event in input_events:
2160 self.run('evtest %s > %s' % (event, filename), timeout=1,
2161 ignore_timeout=True)
2162 properties = self.run('cat %s' % filename).stdout
2163 input_type = player._determine_input_type(properties)
2164 if input_type in looking_for:
2165 labels.append(input_type)
2166 looking_for.remove(input_type)
2167 if len(looking_for) == 0:
2168 break
2169 self.run('rm %s' % filename)
2170
2171 return labels
2172
2173
2174 def has_internal_display(self):
2175 """Determine if the device under test is equipped with an internal
2176 display.
2177
2178 @return: 'internal_display' if one is present; None otherwise.
2179 """
2180 from autotest_lib.client.cros.graphics import graphics_utils
2181 from autotest_lib.client.common_lib import utils as common_utils
2182
2183 def __system_output(cmd):
2184 return self.run(cmd).stdout
2185
2186 def __read_file(remote_path):
2187 return self.run('cat %s' % remote_path).stdout
2188
2189 # Hijack the necessary client functions so that we can take advantage
2190 # of the client lib here.
2191 # FIXME: find a less hacky way than this
2192 original_system_output = utils.system_output
2193 original_read_file = common_utils.read_file
2194 utils.system_output = __system_output
2195 common_utils.read_file = __read_file
2196 try:
2197 return ('internal_display' if graphics_utils.has_internal_display()
2198 else None)
2199 finally:
2200 utils.system_output = original_system_output
2201 common_utils.read_file = original_read_file
2202
2203
2204 def has_lucid_sleep_support(self):
2205 """Determine if the device under test has support for lucid sleep.
2206
2207 @return 'lucidsleep' if this board supports lucid sleep; None otherwise
2208 """
2209 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2210 return 'lucidsleep' if board in LUCID_SLEEP_BOARDS else None
2211
2212
Dan Shi85276d42014-04-08 22:11:45 -07002213 def is_boot_from_usb(self):
2214 """Check if DUT is boot from USB.
2215
2216 @return: True if DUT is boot from usb.
2217 """
2218 device = self.run('rootdev -s -d').stdout.strip()
2219 removable = int(self.run('cat /sys/block/%s/removable' %
2220 os.path.basename(device)).stdout.strip())
2221 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002222
2223
2224 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002225 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002226
2227 @param key: meminfo requested
2228
2229 @return the memory value as a string
2230
2231 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002232 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2233 logging.debug('%s', meminfo)
2234 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002235
2236
Rohit Makasana98e696f2016-06-03 18:48:10 -07002237 def get_cpu_arch(self):
2238 """Returns CPU arch of the device.
2239
2240 @return CPU architecture of the DUT.
2241 """
2242 # Add CPUs by following logic in client/bin/base_utils.py.
2243 if self.run("grep '^flags.*:.* lm .*' /proc/cpuinfo",
2244 ignore_status=True).stdout:
2245 return 'x86_64'
2246 if self.run("grep -Ei 'ARM|CPU implementer' /proc/cpuinfo",
2247 ignore_status=True).stdout:
2248 return 'arm'
2249 return 'i386'
2250
2251
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002252 def get_board_type(self):
2253 """
2254 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002255 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2256
2257 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002258 """
Danny Chan471a8d12015-08-18 14:57:41 -07002259 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2260 ignore_status=True).stdout
2261 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002262 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002263 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002264
2265
2266 def get_os_type(self):
2267 return 'cros'
Simran Basia5522a32015-10-06 11:01:24 -07002268
2269
2270 def enable_adb_testing(self):
2271 """Mark this host as an adb tester."""
Dan Shia2872172015-10-31 01:16:51 -07002272 self.run('touch %s' % constants.ANDROID_TESTER_FILEFLAG)
Kevin Chenga2619dc2016-03-28 11:42:08 -07002273
2274
2275 def get_labels(self):
Kevin Chengf8660142016-08-12 10:17:41 -07002276 """Return the detected labels on the host."""
Kevin Chenga2619dc2016-03-28 11:42:08 -07002277 return self.labels.get_labels(self)
Kevin Chengf8660142016-08-12 10:17:41 -07002278
2279
2280 def update_labels(self):
2281 """Update the labels on the host."""
2282 self.labels.update_labels(self)