blob: 0d5f62456dd44df23668959c51abb267691be2e1 [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
Aviv Keshet74c89a92013-02-04 15:18:30 -08005import functools
J. Richard Barnette1d78b012012-05-15 13:56:30 -07006import logging
Dan Shi0f466e82013-02-22 15:44:58 -08007import os
Simran Basid5e5e272012-09-24 15:23:59 -07008import re
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07009import time
10
mussa584b4462014-06-20 15:13:28 -070011import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070012from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070013from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080014from autotest_lib.client.common_lib import error
15from autotest_lib.client.common_lib import global_config
J. Richard Barnette91137f02016-03-10 16:52:26 -080016from autotest_lib.client.common_lib import hosts
Dan Shi549fb822015-03-24 18:01:11 -070017from autotest_lib.client.common_lib import lsbrelease_utils
J. Richard Barnette45e93de2012-04-11 17:24:15 -070018from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette03a0c132012-11-05 12:40:35 -080019from autotest_lib.client.common_lib.cros import dev_server
Gabe Blackb72f4fb2015-01-20 16:47:13 -080020from autotest_lib.client.common_lib.cros.graphite import autotest_es
Gabe Black1e1c41b2015-02-04 23:55:15 -080021from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000022from autotest_lib.client.cros import constants as client_constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080023from autotest_lib.client.cros import cros_ui
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +080024from autotest_lib.client.cros.audio import cras_utils
Katherine Threlkeldab83d392015-06-18 16:45:57 -070025from autotest_lib.client.cros.input_playback import input_playback
Mussa5b589052015-10-26 17:55:26 -070026from autotest_lib.client.cros.video import constants as video_test_constants
Simran Basi5ace6f22016-01-06 17:30:44 -080027from autotest_lib.server import afe_utils
MK Ryu35d661e2014-09-25 17:44:10 -070028from autotest_lib.server import autoserv_parser
29from autotest_lib.server import autotest
30from autotest_lib.server import constants
Dan Shia1ecd5c2013-06-06 11:21:31 -070031from autotest_lib.server import utils as server_utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070032from autotest_lib.server.cros import provision
Scott Zawalski89c44dd2013-02-26 09:28:02 -050033from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070034from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
Dan Shi9cb0eec2014-06-03 09:04:50 -070035from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
Scottfe06ed82015-11-05 17:15:01 -080036from autotest_lib.server.cros.servo import plankton
Fang Deng96667ca2013-08-01 17:46:18 -070037from autotest_lib.server.hosts import abstract_ssh
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +080038from autotest_lib.server.hosts import chameleon_host
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -080039from autotest_lib.server.hosts import cros_repair
Scottfe06ed82015-11-05 17:15:01 -080040from autotest_lib.server.hosts import plankton_host
Fang Deng5d518f42013-08-02 14:04:32 -070041from autotest_lib.server.hosts import servo_host
Simran Basidcff4252012-11-20 16:13:20 -080042from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070043
44
Dan Shib8540a52015-07-16 14:18:23 -070045CONFIG = global_config.global_config
46
Eric Carusoee673ac2015-08-05 17:03:04 -070047LUCID_SLEEP_BOARDS = ['samus', 'lulu']
48
Dan Shid07ee2e2015-09-24 14:49:25 -070049
beepsc87ff602013-07-31 21:53:00 -070050class FactoryImageCheckerException(error.AutoservError):
51 """Exception raised when an image is a factory image."""
52 pass
53
54
Fang Deng0ca40e22013-08-27 17:47:44 -070055class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070056 """Chromium OS specific subclass of Host."""
57
Simran Basi5ace6f22016-01-06 17:30:44 -080058 VERSION_PREFIX = provision.CROS_VERSION_PREFIX
59
J. Richard Barnette45e93de2012-04-11 17:24:15 -070060 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -050061 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070062
Richard Barnette03a0c132012-11-05 12:40:35 -080063 # Timeout values (in seconds) associated with various Chrome OS
64 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070065 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080066 # In general, a good rule of thumb is that the timeout can be up
67 # to twice the typical measured value on the slowest platform.
68 # The times here have not necessarily been empirically tested to
69 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070070 #
71 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080072 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
73 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080074 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070075 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080076 # other things, this must account for the 30 second dev-mode
J. Richard Barnette417cc792015-10-01 09:56:36 -070077 # screen delay, time to start the network on the DUT, and the
78 # ssh timeout of 120 seconds.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070079 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080080 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080081 # network.
beepsf079cfb2013-09-18 17:49:51 -070082 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080083 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
84 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070085
86 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080087 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +080088 SHUTDOWN_TIMEOUT = 10
J. Richard Barnette417cc792015-10-01 09:56:36 -070089 BOOT_TIMEOUT = 150
J. Richard Barnette5bab5f52015-08-03 13:14:38 -070090 USB_BOOT_TIMEOUT = 300
J. Richard Barnette7817b052014-08-28 09:47:29 -070091 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -080092 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -070093
Dan Shica503482015-03-30 17:23:25 -070094 # Minimum OS version that supports server side packaging. Older builds may
95 # not have server side package built or with Autotest code change to support
96 # server-side packaging.
Dan Shib8540a52015-07-16 14:18:23 -070097 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value(
Dan Shiced09e42015-04-17 16:09:34 -070098 'AUTOSERV', 'min_version_support_ssp', type=int)
Dan Shica503482015-03-30 17:23:25 -070099
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800100 # REBOOT_TIMEOUT: How long to wait for a reboot.
101 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700102 # We have a long timeout to ensure we don't flakily fail due to other
103 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700104 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
105 # return from reboot' bug is solved.
106 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700107
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800108 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
109 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
110 _USB_POWER_TIMEOUT = 5
111 _POWER_CYCLE_TIMEOUT = 10
112
Dan Shib8540a52015-07-16 14:18:23 -0700113 _RPM_RECOVERY_BOARDS = CONFIG.get_config_value('CROS',
Richard Barnette82c35912012-11-20 10:09:10 -0800114 'rpm_recovery_boards', type=str).split(',')
115
Richard Barnette82c35912012-11-20 10:09:10 -0800116 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Fang Dengdeba14f2014-11-14 11:54:09 -0800117 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
118 '-host(\d+)')
Katherine Threlkeldab83d392015-06-18 16:45:57 -0700119 _LIGHTSENSOR_FILES = [ "in_illuminance0_input",
120 "in_illuminance_input",
121 "in_illuminance0_raw",
122 "in_illuminance_raw",
123 "illuminance0_input"]
Richard Barnette82c35912012-11-20 10:09:10 -0800124 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
125 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800126 _DETECTABLE_LABELS = []
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700127 label_decorator = functools.partial(server_utils.add_label_detector,
128 _LABEL_FUNCTIONS,
Aviv Keshet74c89a92013-02-04 15:18:30 -0800129 _DETECTABLE_LABELS)
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 _extract_arguments(args_dict, key_subset):
195 """Extract options from `args_dict` and return a subset result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800196
197 Take the provided dictionary of argument options and return
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800198 a subset that represent standard arguments needed to construct
199 a test-assistant object (chameleon or servo) for a host. The
200 intent is to provide standard argument processing from
Christopher Wiley644ef3e2015-05-15 13:14:14 -0700201 CrosHost for tests that require a test-assistant board
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800202 to operate.
203
204 @param args_dict Dictionary from which to extract the arguments.
205 @param key_subset Tuple of keys to extract from the args_dict, e.g.
206 ('servo_host', 'servo_port').
207 """
208 result = {}
209 for arg in key_subset:
210 if arg in args_dict:
211 result[arg] = args_dict[arg]
212 return result
213
214
215 @staticmethod
216 def get_chameleon_arguments(args_dict):
217 """Extract chameleon options from `args_dict` and return the result.
218
219 Recommended usage:
220 ~~~~~~~~
221 args_dict = utils.args_to_dict(args)
222 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
223 host = hosts.create_host(machine, chameleon_args=chameleon_args)
224 ~~~~~~~~
225
226 @param args_dict Dictionary from which to extract the chameleon
227 arguments.
228 """
229 return CrosHost._extract_arguments(
230 args_dict, ('chameleon_host', 'chameleon_port'))
231
232
233 @staticmethod
Scottfe06ed82015-11-05 17:15:01 -0800234 def get_plankton_arguments(args_dict):
235 """Extract chameleon options from `args_dict` and return the result.
236
237 Recommended usage:
238 ~~~~~~~~
239 args_dict = utils.args_to_dict(args)
240 plankon_args = hosts.CrosHost.get_plankton_arguments(args_dict)
241 host = hosts.create_host(machine, plankton_args=polankton_args)
242 ~~~~~~~~
243
244 @param args_dict Dictionary from which to extract the plankton
245 arguments.
246 """
247 args = CrosHost._extract_arguments(
248 args_dict, ('plankton_host', 'plankton_port'))
249 return args
250
251
252 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800253 def get_servo_arguments(args_dict):
254 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800255
256 Recommended usage:
257 ~~~~~~~~
258 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700259 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800260 host = hosts.create_host(machine, servo_args=servo_args)
261 ~~~~~~~~
262
263 @param args_dict Dictionary from which to extract the servo
264 arguments.
265 """
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800266 return CrosHost._extract_arguments(
267 args_dict, ('servo_host', 'servo_port'))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700268
J. Richard Barnette964fba02012-10-24 17:34:29 -0700269
J. Richard Barnette91137f02016-03-10 16:52:26 -0800270 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
271 plankton_args=None, try_lab_servo=False,
272 ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700273 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800274 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700275
Fang Denge545abb2014-12-30 18:43:47 -0800276 This method will attempt to create the test-assistant object
277 (chameleon/servo) when it is needed by the test. Check
278 the docstring of chameleon_host.create_chameleon_host and
279 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700280
Fang Denge545abb2014-12-30 18:43:47 -0800281 @param hostname: Hostname of the dut.
282 @param chameleon_args: A dictionary that contains args for creating
283 a ChameleonHost. See chameleon_host for details.
284 @param servo_args: A dictionary that contains args for creating
285 a ServoHost object. See servo_host for details.
286 @param try_lab_servo: Boolean, False indicates that ServoHost should
287 not be created for a device in Cros test lab.
288 See servo_host for details.
289 @param ssh_verbosity_flag: String, to pass to the ssh command to control
290 verbosity.
291 @param ssh_options: String, other ssh options to pass to the ssh
292 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700293 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700294 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700295 *args, **dargs)
J. Richard Barnette91137f02016-03-10 16:52:26 -0800296 self._repair_strategy = cros_repair.create_cros_repair_strategy()
J. Richard Barnettef0859852012-08-20 14:55:50 -0700297 # self.env is a dictionary of environment variable settings
298 # to be exported for commands run on the host.
299 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
300 # errors that might happen.
301 self.env['LIBC_FATAL_STDERR_'] = '1'
Fang Dengd1c2b732013-08-20 12:59:46 -0700302 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700303 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700304 # TODO(fdeng): We need to simplify the
305 # process of servo and servo_host initialization.
306 # crbug.com/298432
Fang Denge545abb2014-12-30 18:43:47 -0800307 self._servo_host = servo_host.create_servo_host(
308 dut=self.hostname, servo_args=servo_args,
309 try_lab_servo=try_lab_servo)
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800310 # TODO(waihong): Do the simplication on Chameleon too.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800311 self._chameleon_host = chameleon_host.create_chameleon_host(
312 dut=self.hostname, chameleon_args=chameleon_args)
Scottfe06ed82015-11-05 17:15:01 -0800313 # Add plankton host if plankton args were added on command line
314 self._plankton_host = plankton_host.create_plankton_host(plankton_args)
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800315
Dan Shi4d478522014-02-14 13:46:32 -0800316 if self._servo_host is not None:
317 self.servo = self._servo_host.get_servo()
318 else:
319 self.servo = None
320
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800321 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800322 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800323 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800324 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700325
Scottfe06ed82015-11-05 17:15:01 -0800326 if self._plankton_host:
327 self.plankton_servo = self._plankton_host.get_servo()
328 logging.info('plankton_servo: %r', self.plankton_servo)
329 # Create the plankton object used to access the ec uart
Scott07a848f2016-01-12 15:04:52 -0800330 self.plankton = plankton.Plankton(self.plankton_servo,
331 self._plankton_host.get_servod_server_proxy())
Scottfe06ed82015-11-05 17:15:01 -0800332 else:
Scott07a848f2016-01-12 15:04:52 -0800333 self.plankton = None
Scottfe06ed82015-11-05 17:15:01 -0800334
Fang Deng5d518f42013-08-02 14:04:32 -0700335
Dan Shi3d7a0e12015-10-12 11:55:45 -0700336 def get_repair_image_name(self, image_type='cros'):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500337 """Generate a image_name from variables in the global config.
338
Dan Shi3d7a0e12015-10-12 11:55:45 -0700339 image_type is used to differentiate different images. Default is CrOS,
340 in which case, repair image's name follows the naming convention defined
341 in global setting CROS/stable_build_pattern.
342 If the image_type is not `cros`, the repair image will be looked up
343 using key `board_name/image_type`, e.g., daisy_spring/firmware.
344
345 @param image_type: Type of the image. Default is `cros`.
346
Dan Shi08173202015-11-12 13:08:45 -0800347 @returns a str of $board-version/$BUILD. Returns None if stable version
348 for the board and the default are both not set, e.g., stable
349 firmware version for a new board.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500350
351 """
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500352 board = self._get_board_from_afe()
353 if board is None:
354 raise error.AutoservError('DUT has no board attribute, '
355 'cannot be repaired.')
Dan Shi3d7a0e12015-10-12 11:55:45 -0700356 if image_type != 'cros':
357 board = '%s/%s' % (board, image_type)
Simran Basibeb2bb22016-02-03 15:25:48 -0800358 stable_version = afe_utils.get_stable_version(board=board)
Dan Shi3d7a0e12015-10-12 11:55:45 -0700359 if image_type == 'cros':
360 build_pattern = CONFIG.get_config_value(
361 'CROS', 'stable_build_pattern')
362 stable_version = build_pattern % (board, stable_version)
Dan Shi08173202015-11-12 13:08:45 -0800363 elif image_type == 'firmware':
364 # If firmware stable version is not specified, `stable_version`
365 # from the RPC is the default stable version for CrOS image.
366 # firmware stable version must be from firmware branch, thus its
367 # value must be like board-firmware/R31-1234.0.0. Check if
368 # firmware exists in the stable version, if not, return None.
369 if 'firmware' not in stable_version:
370 return None
Dan Shi3d7a0e12015-10-12 11:55:45 -0700371 return stable_version
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500372
373
Chris Sosab76e0ee2013-05-22 16:55:41 -0700374 def lookup_job_repo_url(self):
375 """Looks up the job_repo_url for the host.
376
Dan Shi8190eb82016-02-11 17:15:58 -0800377 This is kept for backwards compatibility as AU test code in older
378 branch does not use server-side packaging and calls this method through
379 the host object.
380
381 TODO(dshi): Once R50 falls off the stable branch, we should remove this
382 method.
383
Chris Sosab76e0ee2013-05-22 16:55:41 -0700384 @returns job_repo_url from AFE or None if not found.
385
386 @raises KeyError if the host does not have a job_repo_url
387 """
Dan Shibe3636a2016-02-14 22:48:01 -0800388 return afe_utils.get_host_attribute(self, ds_constants.JOB_REPO_URL)
beepscb6f1e22013-06-28 19:14:10 -0700389
390
beepsdae65fd2013-07-26 16:24:41 -0700391 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700392 """
393 Make sure job_repo_url of this host is valid.
394
joychen03eaad92013-06-26 09:55:21 -0700395 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700396 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
397 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
398 download and extract it. If the devserver embedded in the url is
399 unresponsive, update the job_repo_url of the host after staging it on
400 another devserver.
401
402 @param job_repo_url: A url pointing to the devserver where the autotest
403 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700404 @param tag: The tag from the server job, in the format
405 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700406
407 @raises DevServerException: If we could not resolve a devserver.
408 @raises AutoservError: If we're unable to save the new job_repo_url as
409 a result of choosing a new devserver because the old one failed to
410 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700411 @raises urllib2.URLError: If the devserver embedded in job_repo_url
412 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700413 """
Dan Shibe3636a2016-02-14 22:48:01 -0800414 job_repo_url = afe_utils.get_host_attribute(self,
415 ds_constants.JOB_REPO_URL)
beepscb6f1e22013-06-28 19:14:10 -0700416 if not job_repo_url:
417 logging.warning('No job repo url set on host %s', self.hostname)
418 return
419
420 logging.info('Verifying job repo url %s', job_repo_url)
421 devserver_url, image_name = tools.get_devserver_build_from_package_url(
422 job_repo_url)
423
beeps0c865032013-07-30 11:37:06 -0700424 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700425
426 logging.info('Staging autotest artifacts for %s on devserver %s',
427 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700428
429 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700430 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700431 stage_time = time.time() - start_time
432
433 # Record how much of the verification time comes from a devserver
434 # restage. If we're doing things right we should not see multiple
435 # devservers for a given board/build/branch path.
436 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800437 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700438 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800439 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700440 pass
441 else:
beeps0c865032013-07-30 11:37:06 -0700442 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700443 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700444 stats_key = {
445 'board': board,
446 'build_type': build_type,
447 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700448 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700449 }
Gabe Black1e1c41b2015-02-04 23:55:15 -0800450 autotest_stats.Gauge('verify_job_repo_url').send(
beeps687243d2013-07-18 15:29:27 -0700451 '%(board)s.%(build_type)s.%(branch)s.%(devserver)s' % stats_key,
452 stage_time)
beepscb6f1e22013-06-28 19:14:10 -0700453
Scott Zawalskieadbf702013-03-14 09:23:06 -0400454
Dan Shicf4d2032015-03-12 15:04:21 -0700455 def stage_server_side_package(self, image=None):
456 """Stage autotest server-side package on devserver.
457
458 @param image: Full path of an OS image to install or a build name.
459
460 @return: A url to the autotest server-side package.
461 """
462 if image:
463 image_name = tools.get_build_from_image(image)
464 if not image_name:
465 raise error.AutoservError(
466 'Failed to parse build name from %s' % image)
467 ds = dev_server.ImageServer.resolve(image_name)
468 else:
Dan Shibe3636a2016-02-14 22:48:01 -0800469 job_repo_url = afe_utils.get_host_attribute(
470 self, ds_constants.JOB_REPO_URL)
Dan Shicf4d2032015-03-12 15:04:21 -0700471 if job_repo_url:
472 devserver_url, image_name = (
473 tools.get_devserver_build_from_package_url(job_repo_url))
474 ds = dev_server.ImageServer(devserver_url)
475 else:
476 labels = self._AFE.get_labels(
477 name__startswith=ds_constants.VERSION_PREFIX,
478 host__hostname=self.hostname)
479 if not labels:
480 raise error.AutoservError(
481 'Failed to stage server-side package. The host has '
482 'no job_report_url attribute or version label.')
483 image_name = labels[0].name[len(ds_constants.VERSION_PREFIX):]
484 ds = dev_server.ImageServer.resolve(image_name)
Dan Shica503482015-03-30 17:23:25 -0700485
486 # Get the OS version of the build, for any build older than
487 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
488 match = re.match('.*/R\d+-(\d+)\.', image_name)
489 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
490 logging.warn('Build %s is older than %s. Server side packaging is '
491 'disabled.', image_name, self.MIN_VERSION_SUPPORT_SSP)
492 return None
493
Dan Shicf4d2032015-03-12 15:04:21 -0700494 ds.stage_artifacts(image_name, ['autotest_server_package'])
495 return '%s/static/%s/%s' % (ds.url(), image_name,
496 'autotest_server_package.tar.bz2')
497
498
Dan Shi0f466e82013-02-22 15:44:58 -0800499 def _try_stateful_update(self, update_url, force_update, updater):
500 """Try to use stateful update to initialize DUT.
501
502 When DUT is already running the same version that machine_install
503 tries to install, stateful update is a much faster way to clean up
504 the DUT for testing, compared to a full reimage. It is implemeted
505 by calling autoupdater.run_update, but skipping updating root, as
506 updating the kernel is time consuming and not necessary.
507
508 @param update_url: url of the image.
509 @param force_update: Set to True to update the image even if the DUT
510 is running the same version.
511 @param updater: ChromiumOSUpdater instance used to update the DUT.
512 @returns: True if the DUT was updated with stateful update.
513
514 """
Dan Shi10b98482016-02-02 14:38:50 -0800515 # Stop service ap-update-manager to prevent rebooting during autoupdate.
516 # The service is used in jetstream boards, but not other CrOS devices.
517 self.run('sudo stop ap-update-manager', ignore_status=True)
518
J. Richard Barnette3f731032014-04-07 17:42:59 -0700519 # TODO(jrbarnette): Yes, I hate this re.match() test case.
520 # It's better than the alternative: see crbug.com/360944.
521 image_name = autoupdater.url_to_image_name(update_url)
522 release_pattern = r'^.*-release/R[0-9]+-[0-9]+\.[0-9]+\.0$'
523 if not re.match(release_pattern, image_name):
524 return False
Dan Shi0f466e82013-02-22 15:44:58 -0800525 if not updater.check_version():
526 return False
527 if not force_update:
528 logging.info('Canceling stateful update because the new and '
529 'old versions are the same.')
530 return False
531 # Following folders should be rebuilt after stateful update.
532 # A test file is used to confirm each folder gets rebuilt after
533 # the stateful update.
534 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
535 test_file = '.test_file_to_be_deleted'
536 for folder in folders_to_check:
537 touch_path = os.path.join(folder, test_file)
538 self.run('touch %s' % touch_path)
539
Chris Sosae92399e2015-04-24 11:32:59 -0700540 updater.run_update(update_root=False)
Dan Shi0f466e82013-02-22 15:44:58 -0800541
542 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700543 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800544 check_file_cmd = 'test -f %s; echo $?'
545 for folder in folders_to_check:
546 test_file_path = os.path.join(folder, test_file)
547 result = self.run(check_file_cmd % test_file_path,
548 ignore_status=True)
549 if result.exit_status == 1:
550 return False
551 return True
552
553
J. Richard Barnette7275b612013-06-04 18:13:11 -0700554 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800555 """After the DUT is updated, confirm machine_install succeeded.
556
557 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700558 @param expected_kernel: kernel expected to be active after reboot,
559 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800560
561 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700562 # Touch the lab machine file to leave a marker that
563 # distinguishes this image from other test images.
564 # Afterwards, we must re-run the autoreboot script because
565 # it depends on the _LAB_MACHINE_FILE.
J. Richard Barnette71cc1862015-12-02 10:32:38 -0800566 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
567 '( touch "$FILE" ; start autoreboot )')
568 self.run(autoreboot_cmd % self._LAB_MACHINE_FILE)
Chris Sosa65425082013-10-16 13:26:22 -0700569 updater.verify_boot_expectations(
570 expected_kernel, rollback_message=
Gilad Arnoldc26ae1f2015-10-22 16:09:41 -0700571 'Build %s failed to boot on %s; system rolled back to previous '
Chris Sosa65425082013-10-16 13:26:22 -0700572 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700573 # Check that we've got the build we meant to install.
574 if not updater.check_version_to_confirm_install():
575 raise autoupdater.ChromiumOSError(
576 'Failed to update %s to build %s; found build '
577 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700578 updater.update_version,
Dan Shi0942b1d2015-03-31 11:07:00 -0700579 self.get_release_version()))
Dan Shi0f466e82013-02-22 15:44:58 -0800580
Chris Sosae92399e2015-04-24 11:32:59 -0700581 logging.debug('Cleaning up old autotest directories.')
582 try:
583 installed_autodir = autotest.Autotest.get_installed_autodir(self)
584 self.run('rm -rf ' + installed_autodir)
585 except autotest.AutodirNotFoundError:
586 logging.debug('No autotest installed directory found.')
587
Dan Shi0f466e82013-02-22 15:44:58 -0800588
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700589 def _stage_image_for_update(self, image_name=None):
Chris Sosae92399e2015-04-24 11:32:59 -0700590 """Stage a build on a devserver and return the update_url and devserver.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400591
592 @param image_name: a name like lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700593 @returns a tuple with an update URL like:
Scott Zawalskieadbf702013-03-14 09:23:06 -0400594 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700595 and the devserver instance.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400596 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700597 if not image_name:
598 image_name = self.get_repair_image_name()
Chris Sosae92399e2015-04-24 11:32:59 -0700599
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700600 logging.info('Staging build for AU: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800601 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400602 devserver.trigger_download(image_name, synchronous=False)
Chris Sosae92399e2015-04-24 11:32:59 -0700603 return (tools.image_url_pattern() % (devserver.url(), image_name),
604 devserver)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400605
606
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700607 def stage_image_for_servo(self, image_name=None):
608 """Stage a build on a devserver and return the update_url.
609
610 @param image_name: a name like lumpy-release/R27-3837.0.0
611 @returns an update URL like:
612 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
613 """
614 if not image_name:
615 image_name = self.get_repair_image_name()
616 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800617 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700618 devserver.stage_artifacts(image_name, ['test_image'])
619 return devserver.get_test_image_url(image_name)
620
621
beepse539be02013-07-31 21:57:39 -0700622 def stage_factory_image_for_servo(self, image_name):
623 """Stage a build on a devserver and return the update_url.
624
625 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700626
beepse539be02013-07-31 21:57:39 -0700627 @return: An update URL, eg:
628 http://<devserver>/static/canary-channel/\
629 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700630
631 @raises: ValueError if the factory artifact name is missing from
632 the config.
633
beepse539be02013-07-31 21:57:39 -0700634 """
635 if not image_name:
636 logging.error('Need an image_name to stage a factory image.')
637 return
638
Dan Shib8540a52015-07-16 14:18:23 -0700639 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700640 'CROS', 'factory_artifact', type=str, default='')
641 if not factory_artifact:
642 raise ValueError('Cannot retrieve the factory artifact name from '
643 'autotest config, and hence cannot stage factory '
644 'artifacts.')
645
beepse539be02013-07-31 21:57:39 -0700646 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800647 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
beepse539be02013-07-31 21:57:39 -0700648 devserver.stage_artifacts(
649 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700650 [factory_artifact],
651 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700652
653 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
654
655
Chris Sosaa3ac2152012-05-23 22:23:13 -0700656 def machine_install(self, update_url=None, force_update=False,
Richard Barnette0b023a72015-04-24 16:07:30 +0000657 local_devserver=False, repair=False,
658 force_full_update=False):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500659 """Install the DUT.
660
Dan Shi0f466e82013-02-22 15:44:58 -0800661 Use stateful update if the DUT is already running the same build.
662 Stateful update does not update kernel and tends to run much faster
663 than a full reimage. If the DUT is running a different build, or it
664 failed to do a stateful update, full update, including kernel update,
665 will be applied to the DUT.
666
Simran Basi5ace6f22016-01-06 17:30:44 -0800667 Once a host enters machine_install its host attribute job_repo_url
668 (used for package install) will be removed and then updated.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400669
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500670 @param update_url: The url to use for the update
671 pattern: http://$devserver:###/update/$build
672 If update_url is None and repair is True we will install the
Dan Shi6964fa52014-12-18 11:04:27 -0800673 stable image listed in afe_stable_versions table. If the table
674 is not setup, global_config value under CROS.stable_cros_version
675 will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500676 @param force_update: Force an update even if the version installed
677 is the same. Default:False
Christopher Wiley6a4ff932015-05-15 14:00:47 -0700678 @param local_devserver: Used by test_that to allow people to
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500679 use their local devserver. Default: False
Chris Sosae92399e2015-04-24 11:32:59 -0700680 @param repair: Forces update to repair image. Implies force_update.
Fang Deng3d3b9272014-12-22 12:20:28 -0800681 @param force_full_update: If True, do not attempt to run stateful
682 update, force a full reimage. If False, try stateful update
683 first when the dut is already installed with the same version.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500684 @raises autoupdater.ChromiumOSError
685
Dan Shibe3636a2016-02-14 22:48:01 -0800686 @returns A tuple of (image_name, host_attributes).
687 image_name is the name of image installed, e.g.,
688 veyron_jerry-release/R50-7871.0.0
689 host_attributes is a dictionary of (attribute, value), which
690 can be saved to afe_host_attributes table in database. This
691 method returns a dictionary with a single entry of
692 `job_repo_url`: repo_url, where repo_url is a devserver url to
693 autotest packages.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500694 """
Chris Sosae92399e2015-04-24 11:32:59 -0700695 devserver = None
Richard Barnette0b023a72015-04-24 16:07:30 +0000696 if repair:
Chris Sosae92399e2015-04-24 11:32:59 -0700697 update_url, devserver = self._stage_image_for_update()
Richard Barnette0b023a72015-04-24 16:07:30 +0000698 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800699
Chris Sosae92399e2015-04-24 11:32:59 -0700700 if not update_url and not self._parser.options.image:
701 raise error.AutoservError(
Dan Shid07ee2e2015-09-24 14:49:25 -0700702 'There is no update URL, nor a method to get one.')
Chris Sosae92399e2015-04-24 11:32:59 -0700703
704 if not update_url and self._parser.options.image:
705 # This is the base case where we have no given update URL i.e.
706 # dynamic suites logic etc. This is the most flexible case where we
707 # can serve an update from any of our fleet of devservers.
708 requested_build = self._parser.options.image
709 if not requested_build.startswith('http://'):
710 logging.debug('Update will be staged for this installation')
711 update_url, devserver = self._stage_image_for_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700712 requested_build)
Chris Sosae92399e2015-04-24 11:32:59 -0700713 else:
714 update_url = requested_build
715
716 logging.debug('Update URL is %s', update_url)
717
Dan Shif48f8132016-02-18 10:34:30 -0800718 # Report provision stats.
xixuan9e2c98d2016-02-26 19:04:53 -0800719 server_name = dev_server.ImageServer.get_server_name(update_url)
Dan Shif48f8132016-02-18 10:34:30 -0800720 server_name = server_name.replace('.', '_')
721 autotest_stats.Counter('cros_host_provision.' + server_name).increment()
722 autotest_stats.Counter('cros_host_provision.total').increment()
723
Dan Shid07ee2e2015-09-24 14:49:25 -0700724 # Create a file to indicate if provision fails. The file will be removed
725 # by stateful update or full install.
J. Richard Barnette91137f02016-03-10 16:52:26 -0800726 self.run('touch %s' % self.PROVISION_FAILED)
Dan Shid07ee2e2015-09-24 14:49:25 -0700727
Chris Sosae92399e2015-04-24 11:32:59 -0700728 update_complete = False
729 updater = autoupdater.ChromiumOSUpdater(
730 update_url, host=self, local_devserver=local_devserver)
Fang Deng3d3b9272014-12-22 12:20:28 -0800731 if not force_full_update:
732 try:
Chris Sosae92399e2015-04-24 11:32:59 -0700733 # If the DUT is already running the same build, try stateful
734 # update first as it's much quicker than a full re-image.
735 update_complete = self._try_stateful_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700736 update_url, force_update, updater)
Fang Deng3d3b9272014-12-22 12:20:28 -0800737 except Exception as e:
738 logging.exception(e)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700739
Dan Shi0f466e82013-02-22 15:44:58 -0800740 inactive_kernel = None
Chris Sosae92399e2015-04-24 11:32:59 -0700741 if update_complete or (not force_update and updater.check_version()):
742 logging.info('Install complete without full update')
743 else:
744 logging.info('DUT requires full update.')
745 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi10b98482016-02-02 14:38:50 -0800746 # Stop service ap-update-manager to prevent rebooting during
747 # autoupdate. The service is used in jetstream boards, but not other
748 # CrOS devices.
749 self.run('sudo stop ap-update-manager', ignore_status=True)
750
Chris Sosae92399e2015-04-24 11:32:59 -0700751 num_of_attempts = provision.FLAKY_DEVSERVER_ATTEMPTS
Chris Sosab7612bc2013-03-21 10:32:37 -0700752
Chris Sosae92399e2015-04-24 11:32:59 -0700753 while num_of_attempts > 0:
754 num_of_attempts -= 1
755 try:
756 updater.run_update()
757 except Exception:
758 logging.warn('Autoupdate did not complete.')
759 # Do additional check for the devserver health. Ideally,
760 # the autoupdater.py could raise an exception when it
761 # detected network flake but that would require
762 # instrumenting the update engine and parsing it log.
763 if (num_of_attempts <= 0 or
764 devserver is None or
xixuan9e2c98d2016-02-26 19:04:53 -0800765 dev_server.ImageServer.devserver_healthy(
Chris Sosae92399e2015-04-24 11:32:59 -0700766 devserver.url())):
Dan Shid07ee2e2015-09-24 14:49:25 -0700767 raise
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700768
Chris Sosae92399e2015-04-24 11:32:59 -0700769 logging.warn('Devserver looks unhealthy. Trying another')
770 update_url, devserver = self._stage_image_for_update(
771 requested_build)
772 logging.debug('New Update URL is %s', update_url)
773 updater = autoupdater.ChromiumOSUpdater(
774 update_url, host=self,
775 local_devserver=local_devserver)
776 else:
777 break
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700778
Chris Sosae92399e2015-04-24 11:32:59 -0700779 # Give it some time in case of IO issues.
780 time.sleep(10)
Dan Shi5699ac22014-12-19 10:55:49 -0800781
Chris Sosae92399e2015-04-24 11:32:59 -0700782 # Figure out active and inactive kernel.
783 active_kernel, inactive_kernel = updater.get_kernel_state()
Simran Basi13fa1ba2013-03-04 10:56:47 -0800784
Chris Sosae92399e2015-04-24 11:32:59 -0700785 # Ensure inactive kernel has higher priority than active.
786 if (updater.get_kernel_priority(inactive_kernel)
787 < updater.get_kernel_priority(active_kernel)):
788 raise autoupdater.ChromiumOSError(
789 'Update failed. The priority of the inactive kernel'
790 ' partition is less than that of the active kernel'
791 ' partition.')
792
793 # Updater has returned successfully; reboot the host.
794 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
795
796 self._post_update_processing(updater, inactive_kernel)
Simran Basi5ace6f22016-01-06 17:30:44 -0800797 image_name = autoupdater.url_to_image_name(update_url)
Dan Shibe3636a2016-02-14 22:48:01 -0800798 # update_url is different from devserver url needed to stage autotest
799 # packages, therefore, resolve a new devserver url here.
800 devserver_url = dev_server.ImageServer.resolve(image_name,
801 self.hostname).url()
802 repo_url = tools.get_package_url(devserver_url, image_name)
Simran Basi9cbdbc32016-02-11 18:15:46 -0800803 self.verify_software()
Dan Shibe3636a2016-02-14 22:48:01 -0800804 return image_name, {ds_constants.JOB_REPO_URL: repo_url}
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700805
806
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800807 def _clear_fw_version_labels(self, rw_only):
808 """Clear firmware version labels from the machine.
809
810 @param rw_only: True to only clear fwrw_version; otherewise, clear
811 both fwro_version and fwrw_version.
812 """
Dan Shi9cb0eec2014-06-03 09:04:50 -0700813 labels = self._AFE.get_labels(
Dan Shi0723bf52015-06-24 10:52:38 -0700814 name__startswith=provision.FW_RW_VERSION_PREFIX,
Dan Shi9cb0eec2014-06-03 09:04:50 -0700815 host__hostname=self.hostname)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800816 if not rw_only:
817 labels = labels + self._AFE.get_labels(
818 name__startswith=provision.FW_RO_VERSION_PREFIX,
819 host__hostname=self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700820 for label in labels:
821 label.remove_hosts(hosts=[self.hostname])
822
823
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800824 def _add_fw_version_label(self, build, rw_only):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700825 """Add firmware version label to the machine.
826
827 @param build: Build of firmware.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800828 @param rw_only: True to only add fwrw_version; otherwise, add both
829 fwro_version and fwrw_version.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700830
831 """
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800832 fw_label = provision.fwrw_version_to_label(build)
MK Ryu73be9862015-07-06 12:25:00 -0700833 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800834 if not rw_only:
835 fw_label = provision.fwro_version_to_label(build)
836 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Dan Shi9cb0eec2014-06-03 09:04:50 -0700837
838
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800839 def firmware_install(self, build=None, rw_only=False):
Dan Shi9cb0eec2014-06-03 09:04:50 -0700840 """Install firmware to the DUT.
841
842 Use stateful update if the DUT is already running the same build.
843 Stateful update does not update kernel and tends to run much faster
844 than a full reimage. If the DUT is running a different build, or it
845 failed to do a stateful update, full update, including kernel update,
846 will be applied to the DUT.
847
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800848 Once a host enters firmware_install its fw[ro|rw]_version label will
849 be removed. After the firmware is updated successfully, a new
850 fw[ro|rw]_version label will be added to the host.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700851
852 @param build: The build version to which we want to provision the
853 firmware of the machine,
854 e.g. 'link-firmware/R22-2695.1.144'.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800855 @param rw_only: True to only install firmware to its RW portions. Keep
856 the RO portions unchanged.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700857
858 TODO(dshi): After bug 381718 is fixed, update here with corresponding
859 exceptions that could be raised.
860
861 """
862 if not self.servo:
863 raise error.TestError('Host %s does not have servo.' %
864 self.hostname)
865
866 # TODO(fdeng): use host.get_board() after
867 # crbug.com/271834 is fixed.
868 board = self._get_board_from_afe()
869
Chris Sosae92399e2015-04-24 11:32:59 -0700870 # If build is not set, try to install firmware from stable CrOS.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700871 if not build:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700872 build = self.get_repair_image_name(image_type='firmware')
873 if not build:
874 raise error.TestError(
875 'Failed to find stable firmware build for %s.',
876 self.hostname)
877 logging.info('Will install firmware from build %s.', build)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700878
879 config = FAFTConfig(board)
880 if config.use_u_boot:
881 ap_image = 'image-%s.bin' % board
882 else: # Depthcharge platform
883 ap_image = 'image.bin'
884 ec_image = 'ec.bin'
Dan Shi216389c2015-12-22 11:03:06 -0800885 ds = dev_server.ImageServer.resolve(build, self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700886 ds.stage_artifacts(build, ['firmware'])
887
888 tmpd = autotemp.tempdir(unique_id='fwimage')
889 try:
890 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
891 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
892 server_utils.system('wget -O %s %s' % (local_tarball, fwurl),
893 timeout=60)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700894
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800895 self._clear_fw_version_labels(rw_only)
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +0800896 if self.get_ec():
897 logging.info('Will re-program EC %snow', 'RW ' if rw_only else '')
898 server_utils.system('tar xf %s -C %s %s' %
899 (local_tarball, tmpd.name, ec_image),
900 timeout=60)
901 self.servo.program_ec(os.path.join(tmpd.name, ec_image), rw_only)
902 else:
903 logging.info('Not a Chrome EC, ignore re-programing it')
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800904 logging.info('Will re-program BIOS %snow', 'RW ' if rw_only else '')
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +0800905 server_utils.system('tar xf %s -C %s %s' %
906 (local_tarball, tmpd.name, ap_image),
907 timeout=60)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800908 self.servo.program_bios(os.path.join(tmpd.name, ap_image), rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700909 self.servo.get_power_state_controller().reset()
910 time.sleep(self.servo.BOOT_DELAY)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800911 self._add_fw_version_label(build, rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700912 finally:
913 tmpd.clean()
914
915
Dan Shi10e992b2013-08-30 11:02:59 -0700916 def show_update_engine_log(self):
917 """Output update engine log."""
MK Ryu35d661e2014-09-25 17:44:10 -0700918 logging.debug('Dumping %s', client_constants.UPDATE_ENGINE_LOG)
919 self.run('cat %s' % client_constants.UPDATE_ENGINE_LOG)
Dan Shi10e992b2013-08-30 11:02:59 -0700920
921
Richard Barnette82c35912012-11-20 10:09:10 -0800922 def _get_board_from_afe(self):
923 """Retrieve this host's board from its labels in the AFE.
924
925 Looks for a host label of the form "board:<board>", and
926 returns the "<board>" part of the label. `None` is returned
927 if there is not a single, unique label matching the pattern.
928
929 @returns board from label, or `None`.
930 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700931 return server_utils.get_board_from_afe(self.hostname, self._AFE)
Simran Basi833814b2013-01-29 13:13:43 -0800932
933
Dan Shib3b6db32016-02-03 14:54:05 -0800934 def get_build(self):
935 """Retrieve the current build for this Host from the AFE.
936
937 Looks through this host's labels in the AFE to determine its build.
938 This method is replaced by afe_utils.get_build. It's kept here to
939 maintain backwards compatibility for test control files in older CrOS
940 builds (R48, R49 etc.) still call host.get_build, e.g.,
941 `provision_AutoUpdate.double`.
942 TODO(sbasi): Once R50 falls into release branch, this method can be
943 removed.
944
945 @returns The current build or None if it could not find it or if there
946 were multiple build labels assigned to this host.
947 """
948 return afe_utils.get_build(self)
949
950
beepsf079cfb2013-09-18 17:49:51 -0700951 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
952 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500953 """
954 Re-install the OS on the DUT by:
955 1) installing a test image on a USB storage device attached to the Servo
956 board,
Richard Barnette03a0c132012-11-05 12:40:35 -0800957 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700958 3) installing the image with chromeos-install.
959
Scott Zawalski62bacae2013-03-05 10:40:32 -0500960 @param image_url: If specified use as the url to install on the DUT.
961 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -0700962 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
963 Factory images need a longer usb_boot_timeout than regular
964 cros images.
965 @param install_timeout: The timeout to use when installing the chromeos
966 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -0800967
Scott Zawalski62bacae2013-03-05 10:40:32 -0500968 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -0700969
J. Richard Barnette0199cc82014-12-05 17:08:40 -0800970 """
beepsf079cfb2013-09-18 17:49:51 -0700971 usb_boot_timer_key = ('servo_install.usb_boot_timeout_%s'
972 % usb_boot_timeout)
973 logging.info('Downloading image to USB, then booting from it. Usb boot '
974 'timeout = %s', usb_boot_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800975 timer = autotest_stats.Timer(usb_boot_timer_key)
beepsf079cfb2013-09-18 17:49:51 -0700976 timer.start()
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700977 self.servo.install_recovery_image(image_url)
beepsf079cfb2013-09-18 17:49:51 -0700978 if not self.wait_up(timeout=usb_boot_timeout):
J. Richard Barnette91137f02016-03-10 16:52:26 -0800979 raise hosts.AutoservRepairError(
Scott Zawalski62bacae2013-03-05 10:40:32 -0500980 'DUT failed to boot from USB after %d seconds' %
beepsf079cfb2013-09-18 17:49:51 -0700981 usb_boot_timeout)
982 timer.stop()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500983
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +0800984 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
985 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +0800986 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +0800987 try:
988 self.run('chromeos-tpm-recovery')
989 except error.AutoservRunError:
990 logging.warn('chromeos-tpm-recovery is too old.')
991
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +0800992
beepsf079cfb2013-09-18 17:49:51 -0700993 install_timer_key = ('servo_install.install_timeout_%s'
994 % install_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800995 timer = autotest_stats.Timer(install_timer_key)
beepsf079cfb2013-09-18 17:49:51 -0700996 timer.start()
997 logging.info('Installing image through chromeos-install.')
J. Richard Barnette9af19632015-09-25 12:18:03 -0700998 self.run('chromeos-install --yes', timeout=install_timeout)
999 self.halt()
beepsf079cfb2013-09-18 17:49:51 -07001000 timer.stop()
1001
1002 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001003 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -07001004 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001005 # N.B. The Servo API requires that we use power_on() here
1006 # for two reasons:
1007 # 1) After turning on a DUT in recovery mode, you must turn
1008 # it off and then on with power_on() once more to
1009 # disable recovery mode (this is a Parrot specific
1010 # requirement).
1011 # 2) After power_off(), the only way to turn on is with
1012 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -07001013 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001014
1015 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001016 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
1017 raise error.AutoservError('DUT failed to reboot installed '
1018 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -05001019 self.BOOT_TIMEOUT)
1020
1021
Dan Shic1b8bdd2015-09-14 23:11:24 -07001022 def _setup_servo(self):
1023 """Try to force to create servo object if it's not set up yet.
1024 """
1025 if self.servo:
1026 return
1027
1028 try:
1029 # Setting servo_args to {} will force it to create the servo_host
1030 # object if possible.
1031 self._servo_host = servo_host.create_servo_host(
1032 dut=self.hostname, servo_args={})
1033 if self._servo_host:
1034 self.servo = self._servo_host.get_servo()
1035 else:
1036 logging.error('Failed to create servo_host object.')
1037 except Exception as e:
1038 logging.error('Failed to create servo object: %s', e)
1039
1040
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001041 def _is_firmware_repair_supported(self):
1042 """Check if the firmware repair is supported.
Dan Shi3d7a0e12015-10-12 11:55:45 -07001043
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001044 The firmware repair is only applicable to DUTs in pools listed in
1045 global config CROS/pools_support_firmware_repair.
Dan Shi3d7a0e12015-10-12 11:55:45 -07001046
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001047 @return: True if it is supported; otherwise False.
Dan Shi3d7a0e12015-10-12 11:55:45 -07001048 """
1049 logging.info('Checking if host %s can be repaired with firmware '
1050 'repair.', self.hostname)
1051 pools = server_utils.get_labels_from_afe(self.hostname, 'pool:',
1052 self._AFE)
1053 pools_support_firmware_repair = CONFIG.get_config_value('CROS',
1054 'pools_support_firmware_repair', type=str).split(',')
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001055
1056 return (pools and pools_support_firmware_repair and
1057 set(pools).intersection(set(pools_support_firmware_repair)))
1058
1059
Dan Shi90466352015-09-22 15:01:05 -07001060 def confirm_servo(self):
1061 """Confirm servo is initialized and verified.
1062
1063 @raise AutoservError: If servo is not initialized and verified.
1064 """
Tom Wai-Hong Tam244acc22016-01-09 07:04:21 +08001065 if self.servo and self._servo_host.required_by_test:
Dan Shi90466352015-09-22 15:01:05 -07001066 return
1067
1068 # Force to re-create the servo object to make sure servo is verified.
1069 logging.debug('Rebuilding the servo object.')
1070 self.servo = None
1071 self._servo_host = None
1072 self._setup_servo()
1073 if not self.servo:
1074 raise error.AutoservError('Failed to create servo object.')
1075
1076
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -08001077 def repair(self):
1078 """Attempt to get the DUT to pass `self.verify()`.
Richard Barnette82c35912012-11-20 10:09:10 -08001079
1080 This overrides the base class function for repair; it does
J. Richard Barnette91137f02016-03-10 16:52:26 -08001081 not call back to the parent class, but instead relies on
1082 `self._repair_strategy` to coordinate the verification and
1083 repair steps needed to get the DUT working.
Richard Barnette82c35912012-11-20 10:09:10 -08001084
J. Richard Barnette91137f02016-03-10 16:52:26 -08001085 TODO(jrbarnette) Prior to invoking the repair strategy's
1086 `repair()` method, repair checks our servo, and if necessary,
1087 tries to repair it. This flow should instead be integrated
1088 into the main repair strategy.
Richard Barnette82c35912012-11-20 10:09:10 -08001089 """
J. Richard Barnette91137f02016-03-10 16:52:26 -08001090 # For a DUT connected to a moblab, the servo host creation flow
1091 # may not have created the servo object. Try again now that we
1092 # know we want the servo.
Nicolas Boichate30c7e12015-11-05 11:12:50 +08001093 if utils.is_moblab():
1094 self._setup_servo()
1095
Dan Shi4d478522014-02-14 13:46:32 -08001096 if self._servo_host and not self.servo:
Fang Deng03590af2013-10-07 17:34:20 -07001097 try:
J. Richard Barnette4fc59c42015-12-15 16:58:50 -08001098 self._servo_host.repair()
Fang Deng03590af2013-10-07 17:34:20 -07001099 except Exception as e:
Fang Deng03590af2013-10-07 17:34:20 -07001100 logging.error('Could not create a healthy servo: %s', e)
Dan Shi4d478522014-02-14 13:46:32 -08001101 self.servo = self._servo_host.get_servo()
Fang Deng5d518f42013-08-02 14:04:32 -07001102
J. Richard Barnette91137f02016-03-10 16:52:26 -08001103 self._repair_strategy.repair(self)
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001104
J. Richard Barnette91137f02016-03-10 16:52:26 -08001105 # TODO(jrbarnette) When deleting a shard, the shard's DUTs may
1106 # be left still running jobs. The master can't know what's going
1107 # on with the DUT, so the first thing it does is to schedule
1108 # a repair job. If we get here (meaning, if the DUT is now
1109 # working), reboot the DUT to handle that situation.
1110 #
1111 # This is a gross hack. crbug.com/499865.
1112 self.reboot()
Richard Barnette82c35912012-11-20 10:09:10 -08001113
1114
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001115 def close(self):
Fang Deng0ca40e22013-08-27 17:47:44 -07001116 super(CrosHost, self).close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001117
1118
Dan Shi49ca0932014-11-14 11:22:27 -08001119 def get_power_supply_info(self):
1120 """Get the output of power_supply_info.
1121
1122 power_supply_info outputs the info of each power supply, e.g.,
1123 Device: Line Power
1124 online: no
1125 type: Mains
1126 voltage (V): 0
1127 current (A): 0
1128 Device: Battery
1129 state: Discharging
1130 percentage: 95.9276
1131 technology: Li-ion
1132
1133 Above output shows two devices, Line Power and Battery, with details of
1134 each device listed. This function parses the output into a dictionary,
1135 with key being the device name, and value being a dictionary of details
1136 of the device info.
1137
1138 @return: The dictionary of power_supply_info, e.g.,
1139 {'Line Power': {'online': 'yes', 'type': 'main'},
1140 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001141 @raise error.AutoservRunError if power_supply_info tool is not found in
1142 the DUT. Caller should handle this error to avoid false failure
1143 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001144 """
1145 result = self.run('power_supply_info').stdout.strip()
1146 info = {}
1147 device_name = None
1148 device_info = {}
1149 for line in result.split('\n'):
1150 pair = [v.strip() for v in line.split(':')]
1151 if len(pair) != 2:
1152 continue
1153 if pair[0] == 'Device':
1154 if device_name:
1155 info[device_name] = device_info
1156 device_name = pair[1]
1157 device_info = {}
1158 else:
1159 device_info[pair[0]] = pair[1]
1160 if device_name and not device_name in info:
1161 info[device_name] = device_info
1162 return info
1163
1164
1165 def get_battery_percentage(self):
1166 """Get the battery percentage.
1167
1168 @return: The percentage of battery level, value range from 0-100. Return
1169 None if the battery info cannot be retrieved.
1170 """
1171 try:
1172 info = self.get_power_supply_info()
1173 logging.info(info)
1174 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001175 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001176 return None
1177
1178
1179 def is_ac_connected(self):
1180 """Check if the dut has power adapter connected and charging.
1181
1182 @return: True if power adapter is connected and charging.
1183 """
1184 try:
1185 info = self.get_power_supply_info()
1186 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001187 except (KeyError, error.AutoservRunError):
1188 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001189
1190
Simran Basi5e6339a2013-03-21 11:34:32 -07001191 def _cleanup_poweron(self):
1192 """Special cleanup method to make sure hosts always get power back."""
1193 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1194 hosts = afe.get_hosts(hostname=self.hostname)
1195 if not hosts or not (self._RPM_OUTLET_CHANGED in
1196 hosts[0].attributes):
1197 return
1198 logging.debug('This host has recently interacted with the RPM'
1199 ' Infrastructure. Ensuring power is on.')
1200 try:
1201 self.power_on()
Dan Shi7dca56e2014-11-11 17:07:56 -08001202 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1203 hostname=self.hostname)
Simran Basi5e6339a2013-03-21 11:34:32 -07001204 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001205 logging.error('Failed to turn Power On for this host after '
1206 'cleanup through the RPM Infrastructure.')
Gabe Blackb72f4fb2015-01-20 16:47:13 -08001207 autotest_es.post(
Dan Shi7dca56e2014-11-11 17:07:56 -08001208 type_str='RPM_poweron_failure',
1209 metadata={'hostname': self.hostname})
Dan Shi49ca0932014-11-14 11:22:27 -08001210
1211 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -08001212 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -08001213 raise
1214 elif self.is_ac_connected():
1215 logging.info('The device has power adapter connected and '
1216 'charging. No need to try to turn RPM on '
1217 'again.')
1218 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1219 hostname=self.hostname)
1220 logging.info('Battery level is now at %s%%. The device may '
1221 'still have enough power to run test, so no '
1222 'exception will be raised.', battery_percentage)
1223
Simran Basi5e6339a2013-03-21 11:34:32 -07001224
beepsc87ff602013-07-31 21:53:00 -07001225 def _is_factory_image(self):
1226 """Checks if the image on the DUT is a factory image.
1227
1228 @return: True if the image on the DUT is a factory image.
1229 False otherwise.
1230 """
1231 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1232 return result.exit_status == 0
1233
1234
1235 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001236 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001237
1238 @raises: FactoryImageCheckerException for factory images, since
1239 we cannot attempt to restart ui on them.
1240 error.AutoservRunError for any other type of error that
1241 occurs while restarting ui.
1242 """
1243 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001244 raise FactoryImageCheckerException('Cannot restart ui on factory '
1245 'images')
beepsc87ff602013-07-31 21:53:00 -07001246
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001247 # TODO(jrbarnette): The command to stop/start the ui job
1248 # should live inside cros_ui, too. However that would seem
1249 # to imply interface changes to the existing start()/restart()
1250 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -07001251 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001252 self.run('stop ui; start ui')
1253 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001254
1255
Dan Shi549fb822015-03-24 18:01:11 -07001256 def get_release_version(self):
1257 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1258
1259 @returns The version string in lsb-release, under attribute
1260 CHROMEOS_RELEASE_VERSION.
1261 """
1262 lsb_release_content = self.run(
1263 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1264 return lsbrelease_utils.get_chromeos_release_version(
1265 lsb_release_content=lsb_release_content)
1266
1267
1268 def verify_cros_version_label(self):
1269 """ Make sure host's cros-version label match the actual image in dut.
1270
1271 Remove any cros-version: label that doesn't match that installed in
1272 the dut.
1273
1274 @param raise_error: Set to True to raise exception if any mismatch found
1275
1276 @raise error.AutoservError: If any mismatch between cros-version label
1277 and the build installed in dut is found.
1278 """
1279 labels = self._AFE.get_labels(
1280 name__startswith=ds_constants.VERSION_PREFIX,
1281 host__hostname=self.hostname)
1282 mismatch_found = False
1283 if labels:
1284 # Get CHROMEOS_RELEASE_VERSION from lsb-release, e.g., 6908.0.0.
1285 # Note that it's different from cros-version label, which has
1286 # builder and branch info, e.g.,
1287 # cros-version:peppy-release/R43-6908.0.0
1288 release_version = self.get_release_version()
1289 host_list = [self.hostname]
1290 for label in labels:
1291 # Remove any cros-version label that does not match
1292 # release_version.
1293 build_version = label.name[len(ds_constants.VERSION_PREFIX):]
1294 if not utils.version_match(build_version, release_version):
1295 logging.warn('cros-version label "%s" does not match '
1296 'release version %s. Removing the label.',
1297 label.name, release_version)
1298 label.remove_hosts(hosts=host_list)
1299 mismatch_found = True
1300 if mismatch_found:
Dan Shi1057bae2015-03-30 11:35:09 -07001301 autotest_es.post(use_http=True,
1302 type_str='cros_version_label_mismatch',
1303 metadata={'hostname': self.hostname})
Dan Shi549fb822015-03-24 18:01:11 -07001304 raise error.AutoservError('The host has wrong cros-version label.')
1305
1306
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001307 def verify_firmware_status(self):
1308 """Verify the host's firmware is in a good state.
1309
1310 @raise error.AutoservError: If state is not good.
1311 """
1312 if self._is_firmware_repair_supported():
Tom Wai-Hong Tam93a29562016-01-23 04:16:53 +08001313 try:
1314 # Read the AP firmware and dump the sections we are interested.
1315 cmd = ('mkdir /tmp/verify_firmware; '
1316 'cd /tmp/verify_firmware; '
1317 'for section in VBLOCK_A VBLOCK_B FW_MAIN_A FW_MAIN_B; '
1318 'do flashrom -r image.bin -i $section:$section; '
1319 'done')
1320 self.run(cmd)
1321
1322 # Verify the firmware blocks A and B.
1323 cmd = ('vbutil_firmware --verify /tmp/verify_firmware/VBLOCK_%c'
1324 ' --signpubkey /usr/share/vboot/devkeys/root_key.vbpubk'
1325 ' --fv /tmp/verify_firmware/FW_MAIN_%c')
1326 for c in ('A', 'B'):
1327 rv = self.run(cmd % (c, c), ignore_status=True)
1328 if rv.exit_status:
1329 raise error.AutoservError(
1330 'Firmware %c is in a bad state.' % c)
1331 finally:
1332 # Remove the tempoary files.
1333 self.run('rm -rf /tmp/verify_firmware')
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001334 else:
1335 logging.info('Do not care about firmware status when the host '
1336 'is not in pools that support firmware repair.')
1337
1338
Shelley Chena11b9e72016-01-21 15:15:18 -08001339 def verify_filesystem_write_status(self):
1340 """Verify the DUT's filesystem is read/writable
1341
1342 @raise error.AutoservError: if filesystem is not writable.
1343 """
1344 # try to create & delete a file
1345 filename = "/mnt/stateful_partition/test.txt"
1346 cmd = 'touch %s && rm %s' % (filename, filename)
1347 rv = self.run(command=cmd, ignore_status=True)
1348
1349 if rv.exit_status == 1:
1350 raise error.AutoservError('DUT filesystem is read-only.')
1351
1352
beepsc87ff602013-07-31 21:53:00 -07001353 def cleanup(self):
MK Ryu35d661e2014-09-25 17:44:10 -07001354 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001355 try:
beepsc87ff602013-07-31 21:53:00 -07001356 self._restart_ui()
1357 except (error.AutotestRunError, error.AutoservRunError,
1358 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001359 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001360 # Since restarting the UI fails fall back to normal Autotest
1361 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001362 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001363 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001364 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001365 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001366 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001367
1368
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001369 def reboot(self, **dargs):
1370 """
1371 This function reboots the site host. The more generic
1372 RemoteHost.reboot() performs sync and sleeps for 5
1373 seconds. This is not necessary for Chrome OS devices as the
1374 sync should be finished in a short time during the reboot
1375 command.
1376 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001377 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001378 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001379 dargs['reboot_cmd'] = ('sleep 1; '
1380 'reboot & sleep %d; '
1381 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001382 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001383 if 'fastsync' not in dargs:
1384 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001385
Charlie Mooneya8e6dab2014-05-29 14:37:55 -07001386 # For purposes of logging reboot times:
1387 # Get the board name i.e. 'daisy_spring'
Michael Liangca4f5a62014-07-10 15:45:13 -07001388 board_fullname = self.get_board()
1389
1390 # Strip the prefix and add it to dargs.
1391 dargs['board'] = board_fullname[board_fullname.find(':')+1:]
Fang Deng0ca40e22013-08-27 17:47:44 -07001392 super(CrosHost, self).reboot(**dargs)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001393
1394
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001395 def suspend(self, **dargs):
1396 """
1397 This function suspends the site host.
1398 """
1399 suspend_time = dargs.get('suspend_time', 60)
1400 dargs['timeout'] = suspend_time
1401 if 'suspend_cmd' not in dargs:
J. Richard Barnette9af19632015-09-25 12:18:03 -07001402 dargs['suspend_cmd'] = ' && '.join([
1403 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001404 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
J. Richard Barnette9af19632015-09-25 12:18:03 -07001405 'powerd_dbus_suspend --delay=0'])
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001406 super(CrosHost, self).suspend(**dargs)
1407
1408
Simran Basiec564392014-08-25 16:48:09 -07001409 def upstart_status(self, service_name):
1410 """Check the status of an upstart init script.
1411
1412 @param service_name: Service to look up.
1413
1414 @returns True if the service is running, False otherwise.
1415 """
1416 return self.run('status %s | grep start/running' %
1417 service_name).stdout.strip() != ''
1418
1419
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001420 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001421 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001422
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001423 Tests for the following conditions:
1424 1. All conditions tested by the parent version of this
1425 function.
1426 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001427 3. Sufficient space in /mnt/stateful_partition/encrypted.
1428 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001429
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001430 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001431 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001432 default_kilo_inodes_required = CONFIG.get_config_value(
1433 'SERVER', 'kilo_inodes_required', type=int, default=100)
1434 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1435 kilo_inodes_required = CONFIG.get_config_value(
1436 'SERVER', 'kilo_inodes_required_%s' % board,
1437 type=int, default=default_kilo_inodes_required)
1438 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001439 self.check_diskspace(
1440 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001441 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001442 'SERVER', 'gb_diskspace_required', type=float,
1443 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001444 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1445 # Not all targets build with encrypted stateful support.
1446 if self.path_exists(encrypted_stateful_path):
1447 self.check_diskspace(
1448 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001449 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001450 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1451 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001452
Simran Basiec564392014-08-25 16:48:09 -07001453 if not self.upstart_status('system-services'):
Prashanth B5d0a0512014-04-25 12:26:08 -07001454 raise error.AutoservError('Chrome failed to reach login. '
1455 'System services not running.')
1456
beepsc87ff602013-07-31 21:53:00 -07001457 # Factory images don't run update engine,
1458 # goofy controls dbus on these DUTs.
1459 if not self._is_factory_image():
1460 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -05001461 # Makes sure python is present, loads and can use built in functions.
1462 # We have seen cases where importing cPickle fails with undefined
1463 # symbols in cPickle.so.
1464 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001465
Dan Shi549fb822015-03-24 18:01:11 -07001466 self.verify_cros_version_label()
1467
Tom Wai-Hong Tambea741c2016-01-21 07:20:14 +08001468 self.verify_firmware_status()
1469
Shelley Chena11b9e72016-01-21 15:15:18 -08001470 self.verify_filesystem_write_status()
1471
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001472
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001473 def verify(self):
1474 self._repair_strategy.verify(self)
1475
1476
Fang Deng96667ca2013-08-01 17:46:18 -07001477 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
1478 connect_timeout=None, alive_interval=None):
1479 """Override default make_ssh_command to use options tuned for Chrome OS.
1480
1481 Tuning changes:
1482 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1483 connection failure. Consistency with remote_access.sh.
1484
Samuel Tan2ce155b2015-06-23 18:24:38 -07001485 - ServerAliveInterval=900; which causes SSH to ping connection every
1486 900 seconds. In conjunction with ServerAliveCountMax ensures
1487 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001488 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001489 the test completed successfully. Later increased from 180 seconds to
1490 900 seconds to account for tests where the DUT is suspended for
1491 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001492
1493 - ServerAliveCountMax=3; consistency with remote_access.sh.
1494
1495 - ConnectAttempts=4; reduce flakiness in connection errors;
1496 consistency with remote_access.sh.
1497
1498 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1499 Host keys change with every new installation, don't waste
1500 memory/space saving them.
1501
1502 - SSH protocol forced to 2; needed for ServerAliveInterval.
1503
1504 @param user User name to use for the ssh connection.
1505 @param port Port on the target host to use for ssh connection.
1506 @param opts Additional options to the ssh command.
1507 @param hosts_file Ignored.
1508 @param connect_timeout Ignored.
1509 @param alive_interval Ignored.
1510 """
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001511 base_command = ('/usr/bin/ssh -a -x %s %s %s'
1512 ' -o StrictHostKeyChecking=no'
Fang Deng96667ca2013-08-01 17:46:18 -07001513 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Samuel Tan2ce155b2015-06-23 18:24:38 -07001514 ' -o ConnectTimeout=30 -o ServerAliveInterval=900'
Fang Deng96667ca2013-08-01 17:46:18 -07001515 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
1516 ' -o Protocol=2 -l %s -p %d')
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001517 return base_command % (self._ssh_verbosity_flag, self._ssh_options,
1518 opts, user, port)
Jason Abeleb6f924f2013-11-13 16:01:54 -08001519 def syslog(self, message, tag='autotest'):
1520 """Logs a message to syslog on host.
1521
1522 @param message String message to log into syslog
1523 @param tag String tag prefix for syslog
1524
1525 """
1526 self.run('logger -t "%s" "%s"' % (tag, message))
1527
1528
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001529 def _ping_check_status(self, status):
1530 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001531
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001532 @param status Check the ping status against this value.
1533 @return True iff `status` and the result of ping are the same
1534 (i.e. both True or both False).
1535
1536 """
1537 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1538 return not (status ^ (ping_val == 0))
1539
1540 def _ping_wait_for_status(self, status, timeout):
1541 """Wait for the host to have a given status (UP or DOWN).
1542
1543 Status is checked by polling. Polling will not last longer
1544 than the number of seconds in `timeout`. The polling
1545 interval will be long enough that only approximately
1546 _PING_WAIT_COUNT polling cycles will be executed, subject
1547 to a maximum interval of about one minute.
1548
1549 @param status Waiting will stop immediately if `ping` of the
1550 host returns this status.
1551 @param timeout Poll for at most this many seconds.
1552 @return True iff the host status from `ping` matched the
1553 requested status at the time of return.
1554
1555 """
1556 # _ping_check_status() takes about 1 second, hence the
1557 # "- 1" in the formula below.
1558 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1559 end_time = time.time() + timeout
1560 while time.time() <= end_time:
1561 if self._ping_check_status(status):
1562 return True
1563 if poll_interval > 0:
1564 time.sleep(poll_interval)
1565
1566 # The last thing we did was sleep(poll_interval), so it may
1567 # have been too long since the last `ping`. Check one more
1568 # time, just to be sure.
1569 return self._ping_check_status(status)
1570
1571 def ping_wait_up(self, timeout):
1572 """Wait for the host to respond to `ping`.
1573
1574 N.B. This method is not a reliable substitute for
1575 `wait_up()`, because a host that responds to ping will not
1576 necessarily respond to ssh. This method should only be used
1577 if the target DUT can be considered functional even if it
1578 can't be reached via ssh.
1579
1580 @param timeout Minimum time to allow before declaring the
1581 host to be non-responsive.
1582 @return True iff the host answered to ping before the timeout.
1583
1584 """
1585 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001586
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001587 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001588 """Wait until the host no longer responds to `ping`.
1589
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001590 This function can be used as a slightly faster version of
1591 `wait_down()`, by avoiding potentially long ssh timeouts.
1592
1593 @param timeout Minimum time to allow for the host to become
1594 non-responsive.
1595 @return True iff the host quit answering ping before the
1596 timeout.
1597
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001598 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001599 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001600
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001601 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001602 """Wait for the client to enter low-power sleep mode.
1603
1604 The test for "is asleep" can't distinguish a system that is
1605 powered off; to confirm that the unit was asleep, it is
1606 necessary to force resume, and then call
1607 `test_wait_for_resume()`.
1608
1609 This function is expected to be called from a test as part
1610 of a sequence like the following:
1611
1612 ~~~~~~~~
1613 boot_id = host.get_boot_id()
1614 # trigger sleep on the host
1615 host.test_wait_for_sleep()
1616 # trigger resume on the host
1617 host.test_wait_for_resume(boot_id)
1618 ~~~~~~~~
1619
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001620 @param sleep_timeout time limit in seconds to allow the host sleep.
1621
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001622 @exception TestFail The host did not go to sleep within
1623 the allowed time.
1624 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001625 if sleep_timeout is None:
1626 sleep_timeout = self.SLEEP_TIMEOUT
1627
1628 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001629 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001630 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001631
1632
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001633 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001634 """Wait for the client to resume from low-power sleep mode.
1635
1636 The `old_boot_id` parameter should be the value from
1637 `get_boot_id()` obtained prior to entering sleep mode. A
1638 `TestFail` exception is raised if the boot id changes.
1639
1640 See @ref test_wait_for_sleep for more on this function's
1641 usage.
1642
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001643 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001644 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001645 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001646
1647 @exception TestFail The host did not respond within the
1648 allowed time.
1649 @exception TestFail The host responded, but the boot id test
1650 indicated a reboot rather than a sleep
1651 cycle.
1652 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001653 if resume_timeout is None:
1654 resume_timeout = self.RESUME_TIMEOUT
1655
1656 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001657 raise error.TestFail(
1658 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001659 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001660 else:
1661 new_boot_id = self.get_boot_id()
1662 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001663 logging.error('client rebooted (old boot %s, new boot %s)',
1664 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001665 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001666 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001667
1668
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001669 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001670 """Wait for the client to shut down.
1671
1672 The test for "has shut down" can't distinguish a system that
1673 is merely asleep; to confirm that the unit was down, it is
1674 necessary to force boot, and then call test_wait_for_boot().
1675
1676 This function is expected to be called from a test as part
1677 of a sequence like the following:
1678
1679 ~~~~~~~~
1680 boot_id = host.get_boot_id()
1681 # trigger shutdown on the host
1682 host.test_wait_for_shutdown()
1683 # trigger boot on the host
1684 host.test_wait_for_boot(boot_id)
1685 ~~~~~~~~
1686
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001687 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001688 @exception TestFail The host did not shut down within the
1689 allowed time.
1690 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001691 if shutdown_timeout is None:
1692 shutdown_timeout = self.SHUTDOWN_TIMEOUT
1693
1694 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001695 raise error.TestFail(
1696 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001697 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001698
1699
1700 def test_wait_for_boot(self, old_boot_id=None):
1701 """Wait for the client to boot from cold power.
1702
1703 The `old_boot_id` parameter should be the value from
1704 `get_boot_id()` obtained prior to shutting down. A
1705 `TestFail` exception is raised if the boot id does not
1706 change. The boot id test is omitted if `old_boot_id` is not
1707 specified.
1708
1709 See @ref test_wait_for_shutdown for more on this function's
1710 usage.
1711
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001712 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001713 shut down.
1714
1715 @exception TestFail The host did not respond within the
1716 allowed time.
1717 @exception TestFail The host responded, but the boot id test
1718 indicated that there was no reboot.
1719 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001720 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001721 raise error.TestFail(
1722 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001723 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001724 elif old_boot_id:
1725 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001726 logging.error('client not rebooted (boot %s)',
1727 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001728 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001729 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07001730
1731
1732 @staticmethod
1733 def check_for_rpm_support(hostname):
1734 """For a given hostname, return whether or not it is powered by an RPM.
1735
Simran Basi1df55112013-09-06 11:25:09 -07001736 @param hostname: hostname to check for rpm support.
1737
Simran Basid5e5e272012-09-24 15:23:59 -07001738 @return None if this host does not follows the defined naming format
1739 for RPM powered DUT's in the lab. If it does follow the format,
1740 it returns a regular expression MatchObject instead.
1741 """
Fang Dengbaff9082015-01-06 13:46:15 -08001742 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001743
1744
1745 def has_power(self):
1746 """For this host, return whether or not it is powered by an RPM.
1747
1748 @return True if this host is in the CROS lab and follows the defined
1749 naming format.
1750 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001751 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001752
1753
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001754 def _set_power(self, state, power_method):
1755 """Sets the power to the host via RPM, Servo or manual.
1756
1757 @param state Specifies which power state to set to DUT
1758 @param power_method Specifies which method of power control to
1759 use. By default "RPM" will be used. Valid values
1760 are the strings "RPM", "manual", "servoj10".
1761
1762 """
1763 ACCEPTABLE_STATES = ['ON', 'OFF']
1764
1765 if state.upper() not in ACCEPTABLE_STATES:
1766 raise error.TestError('State must be one of: %s.'
1767 % (ACCEPTABLE_STATES,))
1768
1769 if power_method == self.POWER_CONTROL_SERVO:
1770 logging.info('Setting servo port J10 to %s', state)
1771 self.servo.set('prtctl3_pwren', state.lower())
1772 time.sleep(self._USB_POWER_TIMEOUT)
1773 elif power_method == self.POWER_CONTROL_MANUAL:
1774 logging.info('You have %d seconds to set the AC power to %s.',
1775 self._POWER_CYCLE_TIMEOUT, state)
1776 time.sleep(self._POWER_CYCLE_TIMEOUT)
1777 else:
1778 if not self.has_power():
1779 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07001780 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1781 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
1782 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07001783 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07001784
1785
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001786 def power_off(self, power_method=POWER_CONTROL_RPM):
1787 """Turn off power to this host via RPM, Servo or manual.
1788
1789 @param power_method Specifies which method of power control to
1790 use. By default "RPM" will be used. Valid values
1791 are the strings "RPM", "manual", "servoj10".
1792
1793 """
1794 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001795
1796
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001797 def power_on(self, power_method=POWER_CONTROL_RPM):
1798 """Turn on power to this host via RPM, Servo or manual.
1799
1800 @param power_method Specifies which method of power control to
1801 use. By default "RPM" will be used. Valid values
1802 are the strings "RPM", "manual", "servoj10".
1803
1804 """
1805 self._set_power('ON', power_method)
1806
1807
1808 def power_cycle(self, power_method=POWER_CONTROL_RPM):
1809 """Cycle power to this host by turning it OFF, then ON.
1810
1811 @param power_method Specifies which method of power control to
1812 use. By default "RPM" will be used. Valid values
1813 are the strings "RPM", "manual", "servoj10".
1814
1815 """
1816 if power_method in (self.POWER_CONTROL_SERVO,
1817 self.POWER_CONTROL_MANUAL):
1818 self.power_off(power_method=power_method)
1819 time.sleep(self._POWER_CYCLE_TIMEOUT)
1820 self.power_on(power_method=power_method)
1821 else:
1822 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001823
1824
1825 def get_platform(self):
1826 """Determine the correct platform label for this host.
1827
1828 @returns a string representing this host's platform.
1829 """
1830 crossystem = utils.Crossystem(self)
1831 crossystem.init()
1832 # Extract fwid value and use the leading part as the platform id.
1833 # fwid generally follow the format of {platform}.{firmware version}
1834 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1835 platform = crossystem.fwid().split('.')[0].lower()
1836 # Newer platforms start with 'Google_' while the older ones do not.
1837 return platform.replace('google_', '')
1838
1839
Hung-ying Tyanb1328032014-04-01 14:18:54 +08001840 def get_architecture(self):
1841 """Determine the correct architecture label for this host.
1842
1843 @returns a string representing this host's architecture.
1844 """
1845 crossystem = utils.Crossystem(self)
1846 crossystem.init()
1847 return crossystem.arch()
1848
1849
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001850 def get_chrome_version(self):
1851 """Gets the Chrome version number and milestone as strings.
1852
1853 Invokes "chrome --version" to get the version number and milestone.
1854
1855 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
1856 current Chrome version number as a string (in the form "W.X.Y.Z")
1857 and "milestone" is the first component of the version number
1858 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
1859 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
1860 of "chrome --version" and the milestone will be the empty string.
1861
1862 """
MK Ryu35d661e2014-09-25 17:44:10 -07001863 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001864 return utils.parse_chrome_version(version_string)
1865
J. Richard Barnetted2af5852016-02-05 15:03:10 -08001866
Aviv Keshet74c89a92013-02-04 15:18:30 -08001867 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07001868 def get_board(self):
1869 """Determine the correct board label for this host.
1870
1871 @returns a string representing this host's board.
1872 """
1873 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1874 run_method=self.run)
J. Richard Barnetted2af5852016-02-05 15:03:10 -08001875 return (ds_constants.BOARD_PREFIX +
1876 release_info['CHROMEOS_RELEASE_BOARD'])
Simran Basic6f1f7a2012-10-16 10:47:46 -07001877
1878
Aviv Keshet74c89a92013-02-04 15:18:30 -08001879 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001880 def has_lightsensor(self):
1881 """Determine the correct board label for this host.
1882
1883 @returns the string 'lightsensor' if this host has a lightsensor or
1884 None if it does not.
1885 """
1886 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08001887 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07001888 try:
1889 # Run the search cmd following the symlinks. Stderr_tee is set to
1890 # None as there can be a symlink loop, but the command will still
1891 # execute correctly with a few messages printed to stderr.
1892 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
1893 return 'lightsensor'
1894 except error.AutoservRunError:
1895 # egrep exited with a return code of 1 meaning none of the possible
1896 # lightsensor files existed.
1897 return None
1898
1899
Aviv Keshet74c89a92013-02-04 15:18:30 -08001900 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001901 def has_bluetooth(self):
1902 """Determine the correct board label for this host.
1903
1904 @returns the string 'bluetooth' if this host has bluetooth or
1905 None if it does not.
1906 """
1907 try:
1908 self.run('test -d /sys/class/bluetooth/hci0')
1909 # test exited with a return code of 0.
1910 return 'bluetooth'
1911 except error.AutoservRunError:
1912 # test exited with a return code 1 meaning the directory did not
1913 # exist.
1914 return None
1915
1916
Bill Richardson4f595f52014-02-13 16:20:26 -08001917 @label_decorator('ec')
1918 def get_ec(self):
1919 """
1920 Determine the type of EC on this host.
1921
1922 @returns a string representing this host's embedded controller type.
1923 At present, it only returns "ec:cros", for Chrome OS ECs. Other types
1924 of EC (or none) don't return any strings, since no tests depend on
1925 those.
1926 """
1927 cmd = 'mosys ec info'
1928 # The output should look like these, so that the last field should
1929 # match our EC version scheme:
1930 #
1931 # stm | stm32f100 | snow_v1.3.139-375eb9f
1932 # ti | Unknown-10de | peppy_v1.5.114-5d52788
1933 #
1934 # Non-Chrome OS ECs will look like these:
1935 #
1936 # ENE | KB932 | 00BE107A00
1937 # ite | it8518 | 3.08
1938 #
1939 # And some systems don't have ECs at all (Lumpy, for example).
1940 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
1941
1942 ecinfo = self.run(command=cmd, ignore_status=True)
1943 if ecinfo.exit_status == 0:
1944 res = re.search(regexp, ecinfo.stdout)
1945 if res:
1946 logging.info("EC version is %s", res.groups()[0])
1947 return 'ec:cros'
1948 logging.info("%s got: %s", cmd, ecinfo.stdout)
1949 # Has an EC, but it's not a Chrome OS EC
1950 return None
1951 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
1952 # No EC present
1953 return None
1954
1955
Alec Berg31b932b2014-04-04 16:09:11 -07001956 @label_decorator('accels')
1957 def get_accels(self):
1958 """
1959 Determine the type of accelerometers on this host.
1960
1961 @returns a string representing this host's accelerometer type.
1962 At present, it only returns "accel:cros-ec", for accelerometers
1963 attached to a Chrome OS EC, or none, if no accelerometers.
1964 """
1965 # Check to make sure we have ectool
1966 rv = self.run('which ectool', ignore_status=True)
1967 if rv.exit_status:
1968 logging.info("No ectool cmd found, assuming no EC accelerometers")
1969 return None
1970
1971 # Check that the EC supports the motionsense command
1972 rv = self.run('ectool motionsense', ignore_status=True)
1973 if rv.exit_status:
1974 logging.info("EC does not support motionsense command "
1975 "assuming no EC accelerometers")
1976 return None
1977
1978 # Check that EC motion sensors are active
1979 active = self.run('ectool motionsense active').stdout.split('\n')
1980 if active[0] == "0":
1981 logging.info("Motion sense inactive, assuming no EC accelerometers")
1982 return None
1983
1984 logging.info("EC accelerometers found")
1985 return 'accel:cros-ec'
1986
1987
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08001988 @label_decorator('chameleon')
1989 def has_chameleon(self):
1990 """Determine if a Chameleon connected to this host.
1991
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08001992 @returns a list containing two strings ('chameleon' and
1993 'chameleon:' + label, e.g. 'chameleon:hdmi') if this host
1994 has a Chameleon or None if it has not.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08001995 """
1996 if self._chameleon_host:
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08001997 return ['chameleon', 'chameleon:' + self.chameleon.get_label()]
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08001998 else:
1999 return None
2000
2001
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +08002002 @label_decorator('audio_loopback_dongle')
2003 def has_loopback_dongle(self):
2004 """Determine if an audio loopback dongle is plugged to this host.
2005
2006 @returns 'audio_loopback_dongle' when there is an audio loopback dongle
2007 plugged to this host.
2008 None when there is no audio loopback dongle
2009 plugged to this host.
2010 """
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +08002011 nodes_info = self.run(command=cras_utils.get_cras_nodes_cmd(),
2012 ignore_status=True).stdout
2013 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
2014 cras_utils.node_type_is_plugged('MIC', nodes_info)):
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +08002015 return 'audio_loopback_dongle'
2016 else:
2017 return None
2018
2019
Derek Basehorec71ff622014-07-07 15:18:40 -07002020 @label_decorator('power_supply')
2021 def get_power_supply(self):
2022 """
2023 Determine what type of power supply the host has
2024
2025 @returns a string representing this host's power supply.
2026 'power:battery' when the device has a battery intended for
2027 extended use
2028 'power:AC_primary' when the device has a battery not intended
2029 for extended use (for moving the machine, etc)
2030 'power:AC_only' when the device has no battery at all.
2031 """
2032 psu = self.run(command='mosys psu type', ignore_status=True)
2033 if psu.exit_status:
2034 # The psu command for mosys is not included for all platforms. The
2035 # assumption is that the device will have a battery if the command
2036 # is not found.
2037 return 'power:battery'
2038
2039 psu_str = psu.stdout.strip()
2040 if psu_str == 'unknown':
2041 return None
2042
2043 return 'power:%s' % psu_str
2044
2045
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002046 @label_decorator('storage')
2047 def get_storage(self):
2048 """
2049 Determine the type of boot device for this host.
2050
2051 Determine if the internal device is SCSI or dw_mmc device.
2052 Then check that it is SSD or HDD or eMMC or something else.
2053
2054 @returns a string representing this host's internal device type.
2055 'storage:ssd' when internal device is solid state drive
2056 'storage:hdd' when internal device is hard disk drive
2057 'storage:mmc' when internal device is mmc drive
2058 None When internal device is something else or
2059 when we are unable to determine the type
2060 """
2061 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
2062 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2063 '. /usr/share/misc/chromeos-common.sh;',
2064 'load_base_vars;',
2065 'get_fixed_dst_drive'])
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002066 rootdev = self.run(command=rootdev_cmd, ignore_status=True)
2067 if rootdev.exit_status:
2068 logging.info("Fail to run %s", rootdev_cmd)
2069 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002070 rootdev_str = rootdev.stdout.strip()
2071
2072 if not rootdev_str:
2073 return None
2074
2075 rootdev_base = os.path.basename(rootdev_str)
2076
2077 mmc_pattern = '/dev/mmcblk[0-9]'
2078 if re.match(mmc_pattern, rootdev_str):
2079 # Use type to determine if the internal device is eMMC or somthing
2080 # else. We can assume that MMC is always an internal device.
2081 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002082 type = self.run(command=type_cmd, ignore_status=True)
2083 if type.exit_status:
2084 logging.info("Fail to run %s", type_cmd)
2085 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002086 type_str = type.stdout.strip()
2087
2088 if type_str == 'MMC':
2089 return 'storage:mmc'
2090
2091 scsi_pattern = '/dev/sd[a-z]+'
2092 if re.match(scsi_pattern, rootdev.stdout):
2093 # Read symlink for /sys/block/sd* to determine if the internal
2094 # device is connected via ata or usb.
2095 link_cmd = 'readlink /sys/block/%s' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002096 link = self.run(command=link_cmd, ignore_status=True)
2097 if link.exit_status:
2098 logging.info("Fail to run %s", link_cmd)
2099 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002100 link_str = link.stdout.strip()
2101 if 'usb' in link_str:
2102 return None
2103
2104 # Read rotation to determine if the internal device is ssd or hdd.
2105 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
2106 % rootdev_base)
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002107 rotate = self.run(command=rotate_cmd, ignore_status=True)
2108 if rotate.exit_status:
2109 logging.info("Fail to run %s", rotate_cmd)
2110 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002111 rotate_str = rotate.stdout.strip()
2112
2113 rotate_dict = {'0':'storage:ssd', '1':'storage:hdd'}
2114 return rotate_dict.get(rotate_str)
2115
2116 # All other internal device / error case will always fall here
2117 return None
2118
2119
Dan Shi4e9a2aa2014-03-24 14:28:42 -07002120 @label_decorator('servo')
2121 def get_servo(self):
2122 """Determine if the host has a servo attached.
2123
2124 If the host has a working servo attached, it should have a servo label.
2125
2126 @return: string 'servo' if the host has servo attached. Otherwise,
2127 returns None.
2128 """
2129 return 'servo' if self._servo_host else None
2130
2131
Dan Shi5beba472014-05-28 22:46:07 -07002132 @label_decorator('video_labels')
2133 def get_video_labels(self):
2134 """Run /usr/local/bin/avtest_label_detect to get a list of video labels.
2135
2136 Sample output of avtest_label_detect:
2137 Detected label: hw_video_acc_vp8
2138 Detected label: webcam
2139
2140 @return: A list of labels detected by tool avtest_label_detect.
2141 """
2142 try:
2143 result = self.run('/usr/local/bin/avtest_label_detect').stdout
2144 return re.findall('^Detected label: (\w+)$', result, re.M)
2145 except error.AutoservRunError:
2146 # The tool is not installed.
2147 return []
2148
2149
mussa584b4462014-06-20 15:13:28 -07002150 @label_decorator('video_glitch_detection')
2151 def is_video_glitch_detection_supported(self):
2152 """ Determine if a board under test is supported for video glitch
2153 detection tests.
2154
2155 @return: 'video_glitch_detection' if board is supported, None otherwise.
2156 """
Mussa5b589052015-10-26 17:55:26 -07002157 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
mussa584b4462014-06-20 15:13:28 -07002158
Mussa5b589052015-10-26 17:55:26 -07002159 if board in video_test_constants.SUPPORTED_BOARDS:
2160 return 'video_glitch_detection'
mussa584b4462014-06-20 15:13:28 -07002161
Mussa5b589052015-10-26 17:55:26 -07002162 return None
mussa584b4462014-06-20 15:13:28 -07002163
mussa584b4462014-06-20 15:13:28 -07002164
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002165 @label_decorator('touch_labels')
2166 def get_touch(self):
2167 """
2168 Determine whether board under test has a touchpad or touchscreen.
2169
2170 @return: A list of some combination of 'touchscreen' and 'touchpad',
2171 depending on what is present on the device.
Katherine Threlkeldab83d392015-06-18 16:45:57 -07002172
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002173 """
2174 labels = []
Katherine Threlkeldab83d392015-06-18 16:45:57 -07002175 looking_for = ['touchpad', 'touchscreen']
2176 player = input_playback.InputPlayback()
2177 input_events = self.run('ls /dev/input/event*').stdout.strip().split()
2178 filename = '/tmp/touch_labels'
2179 for event in input_events:
2180 self.run('evtest %s > %s' % (event, filename), timeout=1,
2181 ignore_timeout=True)
2182 properties = self.run('cat %s' % filename).stdout
2183 input_type = player._determine_input_type(properties)
2184 if input_type in looking_for:
2185 labels.append(input_type)
2186 looking_for.remove(input_type)
2187 if len(looking_for) == 0:
2188 break
2189 self.run('rm %s' % filename)
2190
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002191 return labels
2192
Hung-ying Tyana39b0542015-06-30 10:36:42 +08002193
2194 @label_decorator('internal_display')
2195 def has_internal_display(self):
2196 """Determine if the device under test is equipped with an internal
2197 display.
2198
2199 @return: 'internal_display' if one is present; None otherwise.
2200 """
2201 from autotest_lib.client.cros.graphics import graphics_utils
2202 from autotest_lib.client.common_lib import utils as common_utils
2203
2204 def __system_output(cmd):
2205 return self.run(cmd).stdout
2206
2207 def __read_file(remote_path):
2208 return self.run('cat %s' % remote_path).stdout
2209
2210 # Hijack the necessary client functions so that we can take advantage
2211 # of the client lib here.
2212 # FIXME: find a less hacky way than this
2213 original_system_output = utils.system_output
2214 original_read_file = common_utils.read_file
2215 utils.system_output = __system_output
2216 common_utils.read_file = __read_file
2217 try:
2218 return ('internal_display' if graphics_utils.has_internal_display()
2219 else None)
2220 finally:
2221 utils.system_output = original_system_output
2222 common_utils.read_file = original_read_file
2223
2224
Eric Carusoee673ac2015-08-05 17:03:04 -07002225 @label_decorator('lucidsleep')
2226 def has_lucid_sleep_support(self):
2227 """Determine if the device under test has support for lucid sleep.
2228
2229 @return 'lucidsleep' if this board supports lucid sleep; None otherwise
2230 """
2231 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2232 return 'lucidsleep' if board in LUCID_SLEEP_BOARDS else None
2233
2234
Dan Shi85276d42014-04-08 22:11:45 -07002235 def is_boot_from_usb(self):
2236 """Check if DUT is boot from USB.
2237
2238 @return: True if DUT is boot from usb.
2239 """
2240 device = self.run('rootdev -s -d').stdout.strip()
2241 removable = int(self.run('cat /sys/block/%s/removable' %
2242 os.path.basename(device)).stdout.strip())
2243 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002244
2245
2246 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002247 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002248
2249 @param key: meminfo requested
2250
2251 @return the memory value as a string
2252
2253 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002254 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2255 logging.debug('%s', meminfo)
2256 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002257
2258
2259 def get_board_type(self):
2260 """
2261 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002262 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2263
2264 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002265 """
Danny Chan471a8d12015-08-18 14:57:41 -07002266 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2267 ignore_status=True).stdout
2268 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002269 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002270 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002271
2272
2273 def get_os_type(self):
2274 return 'cros'
Simran Basia5522a32015-10-06 11:01:24 -07002275
2276
2277 def enable_adb_testing(self):
2278 """Mark this host as an adb tester."""
Dan Shia2872172015-10-31 01:16:51 -07002279 self.run('touch %s' % constants.ANDROID_TESTER_FILEFLAG)