blob: aea213c2497c642d3ec0a916a3a5551f480a0fb3 [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
Darren Krahn0bd18e82015-10-28 23:30:46 +00006import json
J. Richard Barnette1d78b012012-05-15 13:56:30 -07007import logging
Dan Shi0f466e82013-02-22 15:44:58 -08008import os
Simran Basid5e5e272012-09-24 15:23:59 -07009import re
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070010import time
11
mussa584b4462014-06-20 15:13:28 -070012import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070013from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070014from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080015from autotest_lib.client.common_lib import error
16from autotest_lib.client.common_lib import global_config
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
MK Ryu35d661e2014-09-25 17:44:10 -070027from autotest_lib.server import autoserv_parser
28from autotest_lib.server import autotest
29from autotest_lib.server import constants
30from autotest_lib.server import crashcollect
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
Scottfe06ed82015-11-05 17:15:01 -080039from autotest_lib.server.hosts import plankton_host
Fang Deng5d518f42013-08-02 14:04:32 -070040from autotest_lib.server.hosts import servo_host
Simran Basidcff4252012-11-20 16:13:20 -080041from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070042
43
Dan Shib8540a52015-07-16 14:18:23 -070044CONFIG = global_config.global_config
45
Eric Carusoee673ac2015-08-05 17:03:04 -070046LUCID_SLEEP_BOARDS = ['samus', 'lulu']
47
Dan Shid07ee2e2015-09-24 14:49:25 -070048# A file to indicate provision failure and require Repair job to powerwash the
49# dut.
50PROVISION_FAILED = '/var/tmp/provision_failed'
51
beepsc87ff602013-07-31 21:53:00 -070052class FactoryImageCheckerException(error.AutoservError):
53 """Exception raised when an image is a factory image."""
54 pass
55
56
Fang Deng0ca40e22013-08-27 17:47:44 -070057class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070058 """Chromium OS specific subclass of Host."""
59
60 _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
116 _MAX_POWER_CYCLE_ATTEMPTS = 6
117 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Fang Dengdeba14f2014-11-14 11:54:09 -0800118 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
119 '-host(\d+)')
Katherine Threlkeldab83d392015-06-18 16:45:57 -0700120 _LIGHTSENSOR_FILES = [ "in_illuminance0_input",
121 "in_illuminance_input",
122 "in_illuminance0_raw",
123 "in_illuminance_raw",
124 "illuminance0_input"]
Richard Barnette82c35912012-11-20 10:09:10 -0800125 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
126 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800127 _DETECTABLE_LABELS = []
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700128 label_decorator = functools.partial(server_utils.add_label_detector,
129 _LABEL_FUNCTIONS,
Aviv Keshet74c89a92013-02-04 15:18:30 -0800130 _DETECTABLE_LABELS)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700131
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800132 # Constants used in ping_wait_up() and ping_wait_down().
133 #
134 # _PING_WAIT_COUNT is the approximate number of polling
135 # cycles to use when waiting for a host state change.
136 #
137 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
138 # for arguments to the internal _ping_wait_for_status()
139 # method.
140 _PING_WAIT_COUNT = 40
141 _PING_STATUS_DOWN = False
142 _PING_STATUS_UP = True
143
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800144 # Allowed values for the power_method argument.
145
146 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
147 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
148 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
149 POWER_CONTROL_RPM = 'RPM'
150 POWER_CONTROL_SERVO = 'servoj10'
151 POWER_CONTROL_MANUAL = 'manual'
152
153 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
154 POWER_CONTROL_SERVO,
155 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800156
Simran Basi5e6339a2013-03-21 11:34:32 -0700157 _RPM_OUTLET_CHANGED = 'outlet_changed'
158
Dan Shi9cb0eec2014-06-03 09:04:50 -0700159 # URL pattern to download firmware image.
Dan Shib8540a52015-07-16 14:18:23 -0700160 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value(
Dan Shi9cb0eec2014-06-03 09:04:50 -0700161 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700162
MK Ryu35d661e2014-09-25 17:44:10 -0700163 # File that has a list of directories to be collected
164 _LOGS_TO_COLLECT_FILE = os.path.join(
165 common.client_dir, 'common_lib', 'logs_to_collect')
166
167 # Prefix of logging message w.r.t. crash collection
168 _CRASHLOGS_PREFIX = 'collect_crashlogs'
169
170 # Time duration waiting for host up/down check
171 _CHECK_HOST_UP_TIMEOUT_SECS = 15
172
173 # A command that interacts with kernel and hardware (e.g., rm, mkdir, etc)
174 # might not be completely done deep through the hardware when the machine
175 # is powered down right after the command returns.
176 # We should wait for a few seconds to make them done. Finger crossed.
177 _SAFE_WAIT_SECS = 10
178
179
J. Richard Barnette964fba02012-10-24 17:34:29 -0700180 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800181 def check_host(host, timeout=10):
182 """
183 Check if the given host is a chrome-os host.
184
185 @param host: An ssh host representing a device.
186 @param timeout: The timeout for the run command.
187
188 @return: True if the host device is chromeos.
189
beeps46dadc92013-11-07 14:07:10 -0800190 """
191 try:
Simran Basi933c8af2015-04-29 14:05:07 -0700192 result = host.run(
193 'grep -q CHROMEOS /etc/lsb-release && '
194 '! test -f /mnt/stateful_partition/.android_tester && '
195 '! grep -q moblab /etc/lsb-release',
196 ignore_status=True, timeout=timeout)
beeps46dadc92013-11-07 14:07:10 -0800197 except (error.AutoservRunError, error.AutoservSSHTimeout):
198 return False
199 return result.exit_status == 0
200
201
202 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800203 def _extract_arguments(args_dict, key_subset):
204 """Extract options from `args_dict` and return a subset result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800205
206 Take the provided dictionary of argument options and return
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800207 a subset that represent standard arguments needed to construct
208 a test-assistant object (chameleon or servo) for a host. The
209 intent is to provide standard argument processing from
Christopher Wiley644ef3e2015-05-15 13:14:14 -0700210 CrosHost for tests that require a test-assistant board
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800211 to operate.
212
213 @param args_dict Dictionary from which to extract the arguments.
214 @param key_subset Tuple of keys to extract from the args_dict, e.g.
215 ('servo_host', 'servo_port').
216 """
217 result = {}
218 for arg in key_subset:
219 if arg in args_dict:
220 result[arg] = args_dict[arg]
221 return result
222
223
224 @staticmethod
225 def get_chameleon_arguments(args_dict):
226 """Extract chameleon options from `args_dict` and return the result.
227
228 Recommended usage:
229 ~~~~~~~~
230 args_dict = utils.args_to_dict(args)
231 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
232 host = hosts.create_host(machine, chameleon_args=chameleon_args)
233 ~~~~~~~~
234
235 @param args_dict Dictionary from which to extract the chameleon
236 arguments.
237 """
238 return CrosHost._extract_arguments(
239 args_dict, ('chameleon_host', 'chameleon_port'))
240
241
242 @staticmethod
Scottfe06ed82015-11-05 17:15:01 -0800243 def get_plankton_arguments(args_dict):
244 """Extract chameleon options from `args_dict` and return the result.
245
246 Recommended usage:
247 ~~~~~~~~
248 args_dict = utils.args_to_dict(args)
249 plankon_args = hosts.CrosHost.get_plankton_arguments(args_dict)
250 host = hosts.create_host(machine, plankton_args=polankton_args)
251 ~~~~~~~~
252
253 @param args_dict Dictionary from which to extract the plankton
254 arguments.
255 """
256 args = CrosHost._extract_arguments(
257 args_dict, ('plankton_host', 'plankton_port'))
258 return args
259
260
261 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800262 def get_servo_arguments(args_dict):
263 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800264
265 Recommended usage:
266 ~~~~~~~~
267 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700268 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800269 host = hosts.create_host(machine, servo_args=servo_args)
270 ~~~~~~~~
271
272 @param args_dict Dictionary from which to extract the servo
273 arguments.
274 """
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800275 return CrosHost._extract_arguments(
276 args_dict, ('servo_host', 'servo_port'))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700277
J. Richard Barnette964fba02012-10-24 17:34:29 -0700278
Scottfe06ed82015-11-05 17:15:01 -0800279 def _initialize(self, hostname, chameleon_args=None, servo_args=None, plankton_args=None,
Fang Denge545abb2014-12-30 18:43:47 -0800280 try_lab_servo=False, ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700281 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800282 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700283
Fang Denge545abb2014-12-30 18:43:47 -0800284 This method will attempt to create the test-assistant object
285 (chameleon/servo) when it is needed by the test. Check
286 the docstring of chameleon_host.create_chameleon_host and
287 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700288
Fang Denge545abb2014-12-30 18:43:47 -0800289 @param hostname: Hostname of the dut.
290 @param chameleon_args: A dictionary that contains args for creating
291 a ChameleonHost. See chameleon_host for details.
292 @param servo_args: A dictionary that contains args for creating
293 a ServoHost object. See servo_host for details.
294 @param try_lab_servo: Boolean, False indicates that ServoHost should
295 not be created for a device in Cros test lab.
296 See servo_host for details.
297 @param ssh_verbosity_flag: String, to pass to the ssh command to control
298 verbosity.
299 @param ssh_options: String, other ssh options to pass to the ssh
300 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700301 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700302 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700303 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700304 # self.env is a dictionary of environment variable settings
305 # to be exported for commands run on the host.
306 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
307 # errors that might happen.
308 self.env['LIBC_FATAL_STDERR_'] = '1'
Fang Dengd1c2b732013-08-20 12:59:46 -0700309 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700310 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700311 # TODO(fdeng): We need to simplify the
312 # process of servo and servo_host initialization.
313 # crbug.com/298432
Fang Denge545abb2014-12-30 18:43:47 -0800314 self._servo_host = servo_host.create_servo_host(
315 dut=self.hostname, servo_args=servo_args,
316 try_lab_servo=try_lab_servo)
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800317 # TODO(waihong): Do the simplication on Chameleon too.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800318 self._chameleon_host = chameleon_host.create_chameleon_host(
319 dut=self.hostname, chameleon_args=chameleon_args)
Scottfe06ed82015-11-05 17:15:01 -0800320 # Add plankton host if plankton args were added on command line
321 self._plankton_host = plankton_host.create_plankton_host(plankton_args)
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800322
Dan Shi4d478522014-02-14 13:46:32 -0800323 if self._servo_host is not None:
324 self.servo = self._servo_host.get_servo()
325 else:
326 self.servo = None
327
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800328 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800329 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800330 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800331 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700332
Scottfe06ed82015-11-05 17:15:01 -0800333 if self._plankton_host:
334 self.plankton_servo = self._plankton_host.get_servo()
335 logging.info('plankton_servo: %r', self.plankton_servo)
336 # Create the plankton object used to access the ec uart
337 self.plankton_console = plankton.PlanktonConsole(self.plankton_servo)
338 else:
339 self.plankton_console = None
340
Fang Deng5d518f42013-08-02 14:04:32 -0700341
Dan Shi3d7a0e12015-10-12 11:55:45 -0700342 def get_repair_image_name(self, image_type='cros'):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500343 """Generate a image_name from variables in the global config.
344
Dan Shi3d7a0e12015-10-12 11:55:45 -0700345 image_type is used to differentiate different images. Default is CrOS,
346 in which case, repair image's name follows the naming convention defined
347 in global setting CROS/stable_build_pattern.
348 If the image_type is not `cros`, the repair image will be looked up
349 using key `board_name/image_type`, e.g., daisy_spring/firmware.
350
351 @param image_type: Type of the image. Default is `cros`.
352
Dan Shi08173202015-11-12 13:08:45 -0800353 @returns a str of $board-version/$BUILD. Returns None if stable version
354 for the board and the default are both not set, e.g., stable
355 firmware version for a new board.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500356
357 """
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500358 board = self._get_board_from_afe()
359 if board is None:
360 raise error.AutoservError('DUT has no board attribute, '
361 'cannot be repaired.')
Dan Shi3d7a0e12015-10-12 11:55:45 -0700362 if image_type != 'cros':
363 board = '%s/%s' % (board, image_type)
Dan Shi6964fa52014-12-18 11:04:27 -0800364 stable_version = self._AFE.run('get_stable_version', board=board)
Dan Shi3d7a0e12015-10-12 11:55:45 -0700365 if image_type == 'cros':
366 build_pattern = CONFIG.get_config_value(
367 'CROS', 'stable_build_pattern')
368 stable_version = build_pattern % (board, stable_version)
Dan Shi08173202015-11-12 13:08:45 -0800369 elif image_type == 'firmware':
370 # If firmware stable version is not specified, `stable_version`
371 # from the RPC is the default stable version for CrOS image.
372 # firmware stable version must be from firmware branch, thus its
373 # value must be like board-firmware/R31-1234.0.0. Check if
374 # firmware exists in the stable version, if not, return None.
375 if 'firmware' not in stable_version:
376 return None
Dan Shi3d7a0e12015-10-12 11:55:45 -0700377 return stable_version
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500378
379
Scott Zawalski62bacae2013-03-05 10:40:32 -0500380 def _host_in_AFE(self):
381 """Check if the host is an object the AFE knows.
382
383 @returns the host object.
384 """
385 return self._AFE.get_hosts(hostname=self.hostname)
386
387
Chris Sosab76e0ee2013-05-22 16:55:41 -0700388 def lookup_job_repo_url(self):
389 """Looks up the job_repo_url for the host.
390
391 @returns job_repo_url from AFE or None if not found.
392
393 @raises KeyError if the host does not have a job_repo_url
394 """
Chris Sosab76e0ee2013-05-22 16:55:41 -0700395 hosts = self._AFE.get_hosts(hostname=self.hostname)
beepsb5efc532013-06-04 11:29:34 -0700396 if hosts and ds_constants.JOB_REPO_URL in hosts[0].attributes:
397 return hosts[0].attributes[ds_constants.JOB_REPO_URL]
J. Richard Barnette85d0aac2015-08-20 10:34:39 -0700398 else:
399 return None
Chris Sosab76e0ee2013-05-22 16:55:41 -0700400
401
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500402 def clear_cros_version_labels_and_job_repo_url(self):
403 """Clear cros_version labels and host attribute job_repo_url."""
Scott Zawalski62bacae2013-03-05 10:40:32 -0500404 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400405 return
406
Scott Zawalski62bacae2013-03-05 10:40:32 -0500407 host_list = [self.hostname]
408 labels = self._AFE.get_labels(
409 name__startswith=ds_constants.VERSION_PREFIX,
410 host__hostname=self.hostname)
Dan Shi0f466e82013-02-22 15:44:58 -0800411
Scott Zawalski62bacae2013-03-05 10:40:32 -0500412 for label in labels:
413 label.remove_hosts(hosts=host_list)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500414
beepscb6f1e22013-06-28 19:14:10 -0700415 self.update_job_repo_url(None, None)
416
417
418 def update_job_repo_url(self, devserver_url, image_name):
419 """
420 Updates the job_repo_url host attribute and asserts it's value.
421
422 @param devserver_url: The devserver to use in the job_repo_url.
423 @param image_name: The name of the image to use in the job_repo_url.
424
425 @raises AutoservError: If we failed to update the job_repo_url.
426 """
427 repo_url = None
428 if devserver_url and image_name:
429 repo_url = tools.get_package_url(devserver_url, image_name)
430 self._AFE.set_host_attribute(ds_constants.JOB_REPO_URL, repo_url,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500431 hostname=self.hostname)
beepscb6f1e22013-06-28 19:14:10 -0700432 if self.lookup_job_repo_url() != repo_url:
433 raise error.AutoservError('Failed to update job_repo_url with %s, '
434 'host %s' % (repo_url, self.hostname))
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500435
436
Dan Shie9309262013-06-19 22:50:21 -0700437 def add_cros_version_labels_and_job_repo_url(self, image_name):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400438 """Add cros_version labels and host attribute job_repo_url.
439
440 @param image_name: The name of the image e.g.
441 lumpy-release/R27-3837.0.0
Dan Shi7458bf62013-06-10 12:50:16 -0700442
Scott Zawalskieadbf702013-03-14 09:23:06 -0400443 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500444 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400445 return
Scott Zawalski62bacae2013-03-05 10:40:32 -0500446
Scott Zawalskieadbf702013-03-14 09:23:06 -0400447 cros_label = '%s%s' % (ds_constants.VERSION_PREFIX, image_name)
Dan Shie9309262013-06-19 22:50:21 -0700448 devserver_url = dev_server.ImageServer.resolve(image_name).url()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500449
MK Ryufb5e3a82015-07-01 12:21:20 -0700450 self._AFE.run('label_add_hosts', id=cros_label, hosts=[self.hostname])
beepscb6f1e22013-06-28 19:14:10 -0700451 self.update_job_repo_url(devserver_url, image_name)
452
453
beepsdae65fd2013-07-26 16:24:41 -0700454 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700455 """
456 Make sure job_repo_url of this host is valid.
457
joychen03eaad92013-06-26 09:55:21 -0700458 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700459 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
460 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
461 download and extract it. If the devserver embedded in the url is
462 unresponsive, update the job_repo_url of the host after staging it on
463 another devserver.
464
465 @param job_repo_url: A url pointing to the devserver where the autotest
466 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700467 @param tag: The tag from the server job, in the format
468 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700469
470 @raises DevServerException: If we could not resolve a devserver.
471 @raises AutoservError: If we're unable to save the new job_repo_url as
472 a result of choosing a new devserver because the old one failed to
473 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700474 @raises urllib2.URLError: If the devserver embedded in job_repo_url
475 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700476 """
477 job_repo_url = self.lookup_job_repo_url()
478 if not job_repo_url:
479 logging.warning('No job repo url set on host %s', self.hostname)
480 return
481
482 logging.info('Verifying job repo url %s', job_repo_url)
483 devserver_url, image_name = tools.get_devserver_build_from_package_url(
484 job_repo_url)
485
beeps0c865032013-07-30 11:37:06 -0700486 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700487
488 logging.info('Staging autotest artifacts for %s on devserver %s',
489 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700490
491 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700492 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700493 stage_time = time.time() - start_time
494
495 # Record how much of the verification time comes from a devserver
496 # restage. If we're doing things right we should not see multiple
497 # devservers for a given board/build/branch path.
498 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800499 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700500 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800501 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700502 pass
503 else:
beeps0c865032013-07-30 11:37:06 -0700504 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700505 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700506 stats_key = {
507 'board': board,
508 'build_type': build_type,
509 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700510 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700511 }
Gabe Black1e1c41b2015-02-04 23:55:15 -0800512 autotest_stats.Gauge('verify_job_repo_url').send(
beeps687243d2013-07-18 15:29:27 -0700513 '%(board)s.%(build_type)s.%(branch)s.%(devserver)s' % stats_key,
514 stage_time)
beepscb6f1e22013-06-28 19:14:10 -0700515
Scott Zawalskieadbf702013-03-14 09:23:06 -0400516
Dan Shicf4d2032015-03-12 15:04:21 -0700517 def stage_server_side_package(self, image=None):
518 """Stage autotest server-side package on devserver.
519
520 @param image: Full path of an OS image to install or a build name.
521
522 @return: A url to the autotest server-side package.
523 """
524 if image:
525 image_name = tools.get_build_from_image(image)
526 if not image_name:
527 raise error.AutoservError(
528 'Failed to parse build name from %s' % image)
529 ds = dev_server.ImageServer.resolve(image_name)
530 else:
531 job_repo_url = self.lookup_job_repo_url()
532 if job_repo_url:
533 devserver_url, image_name = (
534 tools.get_devserver_build_from_package_url(job_repo_url))
535 ds = dev_server.ImageServer(devserver_url)
536 else:
537 labels = self._AFE.get_labels(
538 name__startswith=ds_constants.VERSION_PREFIX,
539 host__hostname=self.hostname)
540 if not labels:
541 raise error.AutoservError(
542 'Failed to stage server-side package. The host has '
543 'no job_report_url attribute or version label.')
544 image_name = labels[0].name[len(ds_constants.VERSION_PREFIX):]
545 ds = dev_server.ImageServer.resolve(image_name)
Dan Shica503482015-03-30 17:23:25 -0700546
547 # Get the OS version of the build, for any build older than
548 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
549 match = re.match('.*/R\d+-(\d+)\.', image_name)
550 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
551 logging.warn('Build %s is older than %s. Server side packaging is '
552 'disabled.', image_name, self.MIN_VERSION_SUPPORT_SSP)
553 return None
554
Dan Shicf4d2032015-03-12 15:04:21 -0700555 ds.stage_artifacts(image_name, ['autotest_server_package'])
556 return '%s/static/%s/%s' % (ds.url(), image_name,
557 'autotest_server_package.tar.bz2')
558
559
Dan Shi0f466e82013-02-22 15:44:58 -0800560 def _try_stateful_update(self, update_url, force_update, updater):
561 """Try to use stateful update to initialize DUT.
562
563 When DUT is already running the same version that machine_install
564 tries to install, stateful update is a much faster way to clean up
565 the DUT for testing, compared to a full reimage. It is implemeted
566 by calling autoupdater.run_update, but skipping updating root, as
567 updating the kernel is time consuming and not necessary.
568
569 @param update_url: url of the image.
570 @param force_update: Set to True to update the image even if the DUT
571 is running the same version.
572 @param updater: ChromiumOSUpdater instance used to update the DUT.
573 @returns: True if the DUT was updated with stateful update.
574
575 """
J. Richard Barnette3f731032014-04-07 17:42:59 -0700576 # TODO(jrbarnette): Yes, I hate this re.match() test case.
577 # It's better than the alternative: see crbug.com/360944.
578 image_name = autoupdater.url_to_image_name(update_url)
579 release_pattern = r'^.*-release/R[0-9]+-[0-9]+\.[0-9]+\.0$'
580 if not re.match(release_pattern, image_name):
581 return False
Dan Shi0f466e82013-02-22 15:44:58 -0800582 if not updater.check_version():
583 return False
584 if not force_update:
585 logging.info('Canceling stateful update because the new and '
586 'old versions are the same.')
587 return False
588 # Following folders should be rebuilt after stateful update.
589 # A test file is used to confirm each folder gets rebuilt after
590 # the stateful update.
591 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
592 test_file = '.test_file_to_be_deleted'
593 for folder in folders_to_check:
594 touch_path = os.path.join(folder, test_file)
595 self.run('touch %s' % touch_path)
596
Chris Sosae92399e2015-04-24 11:32:59 -0700597 updater.run_update(update_root=False)
Dan Shi0f466e82013-02-22 15:44:58 -0800598
599 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700600 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800601 check_file_cmd = 'test -f %s; echo $?'
602 for folder in folders_to_check:
603 test_file_path = os.path.join(folder, test_file)
604 result = self.run(check_file_cmd % test_file_path,
605 ignore_status=True)
606 if result.exit_status == 1:
607 return False
608 return True
609
610
J. Richard Barnette7275b612013-06-04 18:13:11 -0700611 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800612 """After the DUT is updated, confirm machine_install succeeded.
613
614 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700615 @param expected_kernel: kernel expected to be active after reboot,
616 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800617
618 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700619 # Touch the lab machine file to leave a marker that
620 # distinguishes this image from other test images.
621 # Afterwards, we must re-run the autoreboot script because
622 # it depends on the _LAB_MACHINE_FILE.
J. Richard Barnette71cc1862015-12-02 10:32:38 -0800623 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
624 '( touch "$FILE" ; start autoreboot )')
625 self.run(autoreboot_cmd % self._LAB_MACHINE_FILE)
Chris Sosa65425082013-10-16 13:26:22 -0700626 updater.verify_boot_expectations(
627 expected_kernel, rollback_message=
Gilad Arnoldc26ae1f2015-10-22 16:09:41 -0700628 'Build %s failed to boot on %s; system rolled back to previous '
Chris Sosa65425082013-10-16 13:26:22 -0700629 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700630 # Check that we've got the build we meant to install.
631 if not updater.check_version_to_confirm_install():
632 raise autoupdater.ChromiumOSError(
633 'Failed to update %s to build %s; found build '
634 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700635 updater.update_version,
Dan Shi0942b1d2015-03-31 11:07:00 -0700636 self.get_release_version()))
Dan Shi0f466e82013-02-22 15:44:58 -0800637
Chris Sosae92399e2015-04-24 11:32:59 -0700638 logging.debug('Cleaning up old autotest directories.')
639 try:
640 installed_autodir = autotest.Autotest.get_installed_autodir(self)
641 self.run('rm -rf ' + installed_autodir)
642 except autotest.AutodirNotFoundError:
643 logging.debug('No autotest installed directory found.')
644
Dan Shi0f466e82013-02-22 15:44:58 -0800645
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700646 def _stage_image_for_update(self, image_name=None):
Chris Sosae92399e2015-04-24 11:32:59 -0700647 """Stage a build on a devserver and return the update_url and devserver.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400648
649 @param image_name: a name like lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700650 @returns a tuple with an update URL like:
Scott Zawalskieadbf702013-03-14 09:23:06 -0400651 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700652 and the devserver instance.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400653 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700654 if not image_name:
655 image_name = self.get_repair_image_name()
Chris Sosae92399e2015-04-24 11:32:59 -0700656
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700657 logging.info('Staging build for AU: %s', image_name)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400658 devserver = dev_server.ImageServer.resolve(image_name)
659 devserver.trigger_download(image_name, synchronous=False)
Chris Sosae92399e2015-04-24 11:32:59 -0700660 return (tools.image_url_pattern() % (devserver.url(), image_name),
661 devserver)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400662
663
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700664 def stage_image_for_servo(self, image_name=None):
665 """Stage a build on a devserver and return the update_url.
666
667 @param image_name: a name like lumpy-release/R27-3837.0.0
668 @returns an update URL like:
669 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
670 """
671 if not image_name:
672 image_name = self.get_repair_image_name()
673 logging.info('Staging build for servo install: %s', image_name)
674 devserver = dev_server.ImageServer.resolve(image_name)
675 devserver.stage_artifacts(image_name, ['test_image'])
676 return devserver.get_test_image_url(image_name)
677
678
beepse539be02013-07-31 21:57:39 -0700679 def stage_factory_image_for_servo(self, image_name):
680 """Stage a build on a devserver and return the update_url.
681
682 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700683
beepse539be02013-07-31 21:57:39 -0700684 @return: An update URL, eg:
685 http://<devserver>/static/canary-channel/\
686 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700687
688 @raises: ValueError if the factory artifact name is missing from
689 the config.
690
beepse539be02013-07-31 21:57:39 -0700691 """
692 if not image_name:
693 logging.error('Need an image_name to stage a factory image.')
694 return
695
Dan Shib8540a52015-07-16 14:18:23 -0700696 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700697 'CROS', 'factory_artifact', type=str, default='')
698 if not factory_artifact:
699 raise ValueError('Cannot retrieve the factory artifact name from '
700 'autotest config, and hence cannot stage factory '
701 'artifacts.')
702
beepse539be02013-07-31 21:57:39 -0700703 logging.info('Staging build for servo install: %s', image_name)
704 devserver = dev_server.ImageServer.resolve(image_name)
705 devserver.stage_artifacts(
706 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700707 [factory_artifact],
708 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700709
710 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
711
712
Chris Sosaa3ac2152012-05-23 22:23:13 -0700713 def machine_install(self, update_url=None, force_update=False,
Richard Barnette0b023a72015-04-24 16:07:30 +0000714 local_devserver=False, repair=False,
715 force_full_update=False):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500716 """Install the DUT.
717
Dan Shi0f466e82013-02-22 15:44:58 -0800718 Use stateful update if the DUT is already running the same build.
719 Stateful update does not update kernel and tends to run much faster
720 than a full reimage. If the DUT is running a different build, or it
721 failed to do a stateful update, full update, including kernel update,
722 will be applied to the DUT.
723
Scott Zawalskieadbf702013-03-14 09:23:06 -0400724 Once a host enters machine_install its cros_version label will be
725 removed as well as its host attribute job_repo_url (used for
726 package install).
727
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500728 @param update_url: The url to use for the update
729 pattern: http://$devserver:###/update/$build
730 If update_url is None and repair is True we will install the
Dan Shi6964fa52014-12-18 11:04:27 -0800731 stable image listed in afe_stable_versions table. If the table
732 is not setup, global_config value under CROS.stable_cros_version
733 will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500734 @param force_update: Force an update even if the version installed
735 is the same. Default:False
Christopher Wiley6a4ff932015-05-15 14:00:47 -0700736 @param local_devserver: Used by test_that to allow people to
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500737 use their local devserver. Default: False
Chris Sosae92399e2015-04-24 11:32:59 -0700738 @param repair: Forces update to repair image. Implies force_update.
Fang Deng3d3b9272014-12-22 12:20:28 -0800739 @param force_full_update: If True, do not attempt to run stateful
740 update, force a full reimage. If False, try stateful update
741 first when the dut is already installed with the same version.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500742 @raises autoupdater.ChromiumOSError
743
744 """
Chris Sosae92399e2015-04-24 11:32:59 -0700745 devserver = None
Richard Barnette0b023a72015-04-24 16:07:30 +0000746 if repair:
Chris Sosae92399e2015-04-24 11:32:59 -0700747 update_url, devserver = self._stage_image_for_update()
Richard Barnette0b023a72015-04-24 16:07:30 +0000748 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800749
Chris Sosae92399e2015-04-24 11:32:59 -0700750 if not update_url and not self._parser.options.image:
751 raise error.AutoservError(
Dan Shid07ee2e2015-09-24 14:49:25 -0700752 'There is no update URL, nor a method to get one.')
Chris Sosae92399e2015-04-24 11:32:59 -0700753
754 if not update_url and self._parser.options.image:
755 # This is the base case where we have no given update URL i.e.
756 # dynamic suites logic etc. This is the most flexible case where we
757 # can serve an update from any of our fleet of devservers.
758 requested_build = self._parser.options.image
759 if not requested_build.startswith('http://'):
760 logging.debug('Update will be staged for this installation')
761 update_url, devserver = self._stage_image_for_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700762 requested_build)
Chris Sosae92399e2015-04-24 11:32:59 -0700763 else:
764 update_url = requested_build
765
766 logging.debug('Update URL is %s', update_url)
767
Scott Zawalskieadbf702013-03-14 09:23:06 -0400768 # Remove cros-version and job_repo_url host attribute from host.
769 self.clear_cros_version_labels_and_job_repo_url()
Chris Sosae92399e2015-04-24 11:32:59 -0700770
Dan Shid07ee2e2015-09-24 14:49:25 -0700771 # Create a file to indicate if provision fails. The file will be removed
772 # by stateful update or full install.
773 self.run('touch %s' % PROVISION_FAILED)
774
Chris Sosae92399e2015-04-24 11:32:59 -0700775 update_complete = False
776 updater = autoupdater.ChromiumOSUpdater(
777 update_url, host=self, local_devserver=local_devserver)
Fang Deng3d3b9272014-12-22 12:20:28 -0800778 if not force_full_update:
779 try:
Chris Sosae92399e2015-04-24 11:32:59 -0700780 # If the DUT is already running the same build, try stateful
781 # update first as it's much quicker than a full re-image.
782 update_complete = self._try_stateful_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700783 update_url, force_update, updater)
Fang Deng3d3b9272014-12-22 12:20:28 -0800784 except Exception as e:
785 logging.exception(e)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700786
Dan Shi0f466e82013-02-22 15:44:58 -0800787 inactive_kernel = None
Chris Sosae92399e2015-04-24 11:32:59 -0700788 if update_complete or (not force_update and updater.check_version()):
789 logging.info('Install complete without full update')
790 else:
791 logging.info('DUT requires full update.')
792 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
793 num_of_attempts = provision.FLAKY_DEVSERVER_ATTEMPTS
Chris Sosab7612bc2013-03-21 10:32:37 -0700794
Chris Sosae92399e2015-04-24 11:32:59 -0700795 while num_of_attempts > 0:
796 num_of_attempts -= 1
797 try:
798 updater.run_update()
799 except Exception:
800 logging.warn('Autoupdate did not complete.')
801 # Do additional check for the devserver health. Ideally,
802 # the autoupdater.py could raise an exception when it
803 # detected network flake but that would require
804 # instrumenting the update engine and parsing it log.
805 if (num_of_attempts <= 0 or
806 devserver is None or
807 dev_server.DevServer.devserver_healthy(
808 devserver.url())):
Dan Shid07ee2e2015-09-24 14:49:25 -0700809 raise
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700810
Chris Sosae92399e2015-04-24 11:32:59 -0700811 logging.warn('Devserver looks unhealthy. Trying another')
812 update_url, devserver = self._stage_image_for_update(
813 requested_build)
814 logging.debug('New Update URL is %s', update_url)
815 updater = autoupdater.ChromiumOSUpdater(
816 update_url, host=self,
817 local_devserver=local_devserver)
818 else:
819 break
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700820
Chris Sosae92399e2015-04-24 11:32:59 -0700821 # Give it some time in case of IO issues.
822 time.sleep(10)
Dan Shi5699ac22014-12-19 10:55:49 -0800823
Chris Sosae92399e2015-04-24 11:32:59 -0700824 # Figure out active and inactive kernel.
825 active_kernel, inactive_kernel = updater.get_kernel_state()
Simran Basi13fa1ba2013-03-04 10:56:47 -0800826
Chris Sosae92399e2015-04-24 11:32:59 -0700827 # Ensure inactive kernel has higher priority than active.
828 if (updater.get_kernel_priority(inactive_kernel)
829 < updater.get_kernel_priority(active_kernel)):
830 raise autoupdater.ChromiumOSError(
831 'Update failed. The priority of the inactive kernel'
832 ' partition is less than that of the active kernel'
833 ' partition.')
834
835 # Updater has returned successfully; reboot the host.
836 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
837
838 self._post_update_processing(updater, inactive_kernel)
839 self.add_cros_version_labels_and_job_repo_url(
840 autoupdater.url_to_image_name(update_url))
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700841
842
Dan Shi9cb0eec2014-06-03 09:04:50 -0700843 def _clear_fw_version_labels(self):
844 """Clear firmware version labels from the machine."""
845 labels = self._AFE.get_labels(
Dan Shi0723bf52015-06-24 10:52:38 -0700846 name__startswith=provision.FW_RW_VERSION_PREFIX,
Dan Shi9cb0eec2014-06-03 09:04:50 -0700847 host__hostname=self.hostname)
848 for label in labels:
849 label.remove_hosts(hosts=[self.hostname])
850
851
852 def _add_fw_version_label(self, build):
853 """Add firmware version label to the machine.
854
855 @param build: Build of firmware.
856
857 """
858 fw_label = provision.fw_version_to_label(build)
MK Ryu73be9862015-07-06 12:25:00 -0700859 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Dan Shi9cb0eec2014-06-03 09:04:50 -0700860
861
862 def firmware_install(self, build=None):
863 """Install firmware to the DUT.
864
865 Use stateful update if the DUT is already running the same build.
866 Stateful update does not update kernel and tends to run much faster
867 than a full reimage. If the DUT is running a different build, or it
868 failed to do a stateful update, full update, including kernel update,
869 will be applied to the DUT.
870
871 Once a host enters firmware_install its fw_version label will be
872 removed. After the firmware is updated successfully, a new fw_version
873 label will be added to the host.
874
875 @param build: The build version to which we want to provision the
876 firmware of the machine,
877 e.g. 'link-firmware/R22-2695.1.144'.
878
879 TODO(dshi): After bug 381718 is fixed, update here with corresponding
880 exceptions that could be raised.
881
882 """
883 if not self.servo:
884 raise error.TestError('Host %s does not have servo.' %
885 self.hostname)
886
887 # TODO(fdeng): use host.get_board() after
888 # crbug.com/271834 is fixed.
889 board = self._get_board_from_afe()
890
Chris Sosae92399e2015-04-24 11:32:59 -0700891 # If build is not set, try to install firmware from stable CrOS.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700892 if not build:
Dan Shi3d7a0e12015-10-12 11:55:45 -0700893 build = self.get_repair_image_name(image_type='firmware')
894 if not build:
895 raise error.TestError(
896 'Failed to find stable firmware build for %s.',
897 self.hostname)
898 logging.info('Will install firmware from build %s.', build)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700899
900 config = FAFTConfig(board)
901 if config.use_u_boot:
902 ap_image = 'image-%s.bin' % board
903 else: # Depthcharge platform
904 ap_image = 'image.bin'
905 ec_image = 'ec.bin'
906 ds = dev_server.ImageServer.resolve(build)
907 ds.stage_artifacts(build, ['firmware'])
908
909 tmpd = autotemp.tempdir(unique_id='fwimage')
910 try:
911 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
912 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
913 server_utils.system('wget -O %s %s' % (local_tarball, fwurl),
914 timeout=60)
915 server_utils.system('tar xf %s -C %s %s %s' %
916 (local_tarball, tmpd.name, ap_image, ec_image),
917 timeout=60)
918 server_utils.system('tar xf %s --wildcards -C %s "dts/*"' %
919 (local_tarball, tmpd.name),
920 timeout=60, ignore_status=True)
921
922 self._clear_fw_version_labels()
923 logging.info('Will re-program EC now')
924 self.servo.program_ec(os.path.join(tmpd.name, ec_image))
925 logging.info('Will re-program BIOS now')
926 self.servo.program_bios(os.path.join(tmpd.name, ap_image))
927 self.servo.get_power_state_controller().reset()
928 time.sleep(self.servo.BOOT_DELAY)
Dan Shia5fef052015-05-18 23:28:47 -0700929 self._add_fw_version_label(build)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700930 finally:
931 tmpd.clean()
932
933
Dan Shi10e992b2013-08-30 11:02:59 -0700934 def show_update_engine_log(self):
935 """Output update engine log."""
MK Ryu35d661e2014-09-25 17:44:10 -0700936 logging.debug('Dumping %s', client_constants.UPDATE_ENGINE_LOG)
937 self.run('cat %s' % client_constants.UPDATE_ENGINE_LOG)
Dan Shi10e992b2013-08-30 11:02:59 -0700938
939
Richard Barnette82c35912012-11-20 10:09:10 -0800940 def _get_board_from_afe(self):
941 """Retrieve this host's board from its labels in the AFE.
942
943 Looks for a host label of the form "board:<board>", and
944 returns the "<board>" part of the label. `None` is returned
945 if there is not a single, unique label matching the pattern.
946
947 @returns board from label, or `None`.
948 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700949 return server_utils.get_board_from_afe(self.hostname, self._AFE)
Simran Basi833814b2013-01-29 13:13:43 -0800950
951
952 def get_build(self):
953 """Retrieve the current build for this Host from the AFE.
954
955 Looks through this host's labels in the AFE to determine its build.
956
957 @returns The current build or None if it could not find it or if there
958 were multiple build labels assigned to this host.
959 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700960 return server_utils.get_build_from_afe(self.hostname, self._AFE)
Richard Barnette82c35912012-11-20 10:09:10 -0800961
962
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500963 def _install_repair(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700964 """Attempt to repair this host using the update-engine.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500965
966 If the host is up, try installing the DUT with a stable
Dan Shi6964fa52014-12-18 11:04:27 -0800967 "repair" version of Chrome OS as defined in afe_stable_versions table.
968 If the table is not setup, global_config value under
969 CROS.stable_cros_version will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500970
Scott Zawalski62bacae2013-03-05 10:40:32 -0500971 @raises AutoservRepairMethodNA if the DUT is not reachable.
972 @raises ChromiumOSError if the install failed for some reason.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500973
974 """
975 if not self.is_up():
Scott Zawalski62bacae2013-03-05 10:40:32 -0500976 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500977 logging.info('Attempting to reimage machine to repair image.')
978 try:
Richard Barnette0b023a72015-04-24 16:07:30 +0000979 self.machine_install(repair=True)
Fang Dengd0672f32013-03-18 17:18:09 -0700980 except autoupdater.ChromiumOSError as e:
981 logging.exception(e)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500982 logging.info('Repair via install failed.')
Scott Zawalski62bacae2013-03-05 10:40:32 -0500983 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500984
985
Dan Shi2c88eed2013-11-12 10:18:38 -0800986 def _install_repair_with_powerwash(self):
Dan Shi9cc48452013-11-12 12:39:26 -0800987 """Attempt to powerwash first then repair this host using update-engine.
Dan Shi2c88eed2013-11-12 10:18:38 -0800988
Dan Shi9cc48452013-11-12 12:39:26 -0800989 update-engine may fail due to a bad image. In such case, powerwash
990 may help to cleanup the DUT for update-engine to work again.
Dan Shi2c88eed2013-11-12 10:18:38 -0800991
992 @raises AutoservRepairMethodNA if the DUT is not reachable.
993 @raises ChromiumOSError if the install failed for some reason.
994
995 """
996 if not self.is_up():
997 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
998
999 logging.info('Attempting to powerwash the DUT.')
1000 self.run('echo "fast safe" > '
1001 '/mnt/stateful_partition/factory_install_reset')
1002 self.reboot(timeout=self.POWERWASH_BOOT_TIMEOUT, wait=True)
1003 if not self.is_up():
Dan Shi9cc48452013-11-12 12:39:26 -08001004 logging.error('Powerwash failed. DUT did not come back after '
Dan Shi2c88eed2013-11-12 10:18:38 -08001005 'reboot.')
1006 raise error.AutoservRepairFailure(
1007 'DUT failed to boot from powerwash after %d seconds' %
1008 self.POWERWASH_BOOT_TIMEOUT)
1009
1010 logging.info('Powerwash succeeded.')
1011 self._install_repair()
1012
1013
beepsf079cfb2013-09-18 17:49:51 -07001014 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
1015 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001016 """
1017 Re-install the OS on the DUT by:
1018 1) installing a test image on a USB storage device attached to the Servo
1019 board,
Richard Barnette03a0c132012-11-05 12:40:35 -08001020 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001021 3) installing the image with chromeos-install.
1022
Scott Zawalski62bacae2013-03-05 10:40:32 -05001023 @param image_url: If specified use as the url to install on the DUT.
1024 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -07001025 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
1026 Factory images need a longer usb_boot_timeout than regular
1027 cros images.
1028 @param install_timeout: The timeout to use when installing the chromeos
1029 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -08001030
Scott Zawalski62bacae2013-03-05 10:40:32 -05001031 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -07001032
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001033 """
beepsf079cfb2013-09-18 17:49:51 -07001034 usb_boot_timer_key = ('servo_install.usb_boot_timeout_%s'
1035 % usb_boot_timeout)
1036 logging.info('Downloading image to USB, then booting from it. Usb boot '
1037 'timeout = %s', usb_boot_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -08001038 timer = autotest_stats.Timer(usb_boot_timer_key)
beepsf079cfb2013-09-18 17:49:51 -07001039 timer.start()
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001040 self.servo.install_recovery_image(image_url)
beepsf079cfb2013-09-18 17:49:51 -07001041 if not self.wait_up(timeout=usb_boot_timeout):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001042 raise error.AutoservRepairFailure(
1043 'DUT failed to boot from USB after %d seconds' %
beepsf079cfb2013-09-18 17:49:51 -07001044 usb_boot_timeout)
1045 timer.stop()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001046
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001047 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
1048 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001049 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001050 try:
1051 self.run('chromeos-tpm-recovery')
1052 except error.AutoservRunError:
1053 logging.warn('chromeos-tpm-recovery is too old.')
1054
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001055
beepsf079cfb2013-09-18 17:49:51 -07001056 install_timer_key = ('servo_install.install_timeout_%s'
1057 % install_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -08001058 timer = autotest_stats.Timer(install_timer_key)
beepsf079cfb2013-09-18 17:49:51 -07001059 timer.start()
1060 logging.info('Installing image through chromeos-install.')
J. Richard Barnette9af19632015-09-25 12:18:03 -07001061 self.run('chromeos-install --yes', timeout=install_timeout)
1062 self.halt()
beepsf079cfb2013-09-18 17:49:51 -07001063 timer.stop()
1064
1065 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001066 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -07001067 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001068 # N.B. The Servo API requires that we use power_on() here
1069 # for two reasons:
1070 # 1) After turning on a DUT in recovery mode, you must turn
1071 # it off and then on with power_on() once more to
1072 # disable recovery mode (this is a Parrot specific
1073 # requirement).
1074 # 2) After power_off(), the only way to turn on is with
1075 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -07001076 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001077
1078 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001079 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
1080 raise error.AutoservError('DUT failed to reboot installed '
1081 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -05001082 self.BOOT_TIMEOUT)
1083
1084
Dan Shic1b8bdd2015-09-14 23:11:24 -07001085 def _setup_servo(self):
1086 """Try to force to create servo object if it's not set up yet.
1087 """
1088 if self.servo:
1089 return
1090
1091 try:
1092 # Setting servo_args to {} will force it to create the servo_host
1093 # object if possible.
1094 self._servo_host = servo_host.create_servo_host(
1095 dut=self.hostname, servo_args={})
1096 if self._servo_host:
1097 self.servo = self._servo_host.get_servo()
1098 else:
1099 logging.error('Failed to create servo_host object.')
1100 except Exception as e:
1101 logging.error('Failed to create servo object: %s', e)
1102
1103
J. Richard Barnettee4af8b92013-05-01 13:16:12 -07001104 def _servo_repair_reinstall(self):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001105 """Reinstall the DUT utilizing servo and a test image.
1106
1107 Re-install the OS on the DUT by:
1108 1) installing a test image on a USB storage device attached to the Servo
1109 board,
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001110 2) booting that image in recovery mode,
1111 3) resetting the TPM status, and then
1112 4) installing the image with chromeos-install.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001113
Scott Zawalski62bacae2013-03-05 10:40:32 -05001114 @raises AutoservRepairMethodNA if the device does not have servo
1115 support.
1116
1117 """
1118 if not self.servo:
1119 raise error.AutoservRepairMethodNA('Repair Reinstall NA: '
1120 'DUT has no servo support.')
1121
1122 logging.info('Attempting to recovery servo enabled device with '
1123 'servo_repair_reinstall')
1124
J. Richard Barnettee4af8b92013-05-01 13:16:12 -07001125 image_url = self.stage_image_for_servo()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001126 self.servo_install(image_url)
1127
1128
Dan Shi3d7a0e12015-10-12 11:55:45 -07001129 def _firmware_repair(self):
1130 """Reinstall the firmware image using servo.
1131
1132 This repair function attempts to install the stable firmware specified
1133 by the stable firmware version.
1134 Then reset the DUT and try to verify it. If verify fails, it will try to
1135 install the CrOS image using servo.
1136
1137 Note that the firmware repair is only applicable to DUTs in pools listed
1138 in global config CROS/pools_support_firmware_repair.
1139 """
1140 logging.info('Checking if host %s can be repaired with firmware '
1141 'repair.', self.hostname)
1142 pools = server_utils.get_labels_from_afe(self.hostname, 'pool:',
1143 self._AFE)
1144 pools_support_firmware_repair = CONFIG.get_config_value('CROS',
1145 'pools_support_firmware_repair', type=str).split(',')
1146 if (not pools or not pools_support_firmware_repair or
1147 not set(pools).intersection(set(pools_support_firmware_repair))):
1148 logging.info('Host %s is not in pools that support firmware repair.'
1149 ' pools supporting firmware repair are: %s.',
1150 self.hostname, pools_support_firmware_repair)
1151 raise error.AutoservRepairMethodNA(
1152 'Firmware repair is not applicable to host %s.' %
1153 self.hostname)
1154
1155 # To repair a DUT connected to a moblab, try to create a servo object if
1156 # it was failed to be created earlier as there may be a servo_host host
1157 # attribute for this host.
1158 if utils.is_moblab():
1159 self._setup_servo()
1160
1161 if not self.servo:
1162 raise error.AutoservRepairMethodNA('Repair Reinstall NA: '
1163 'DUT has no servo support.')
1164
1165 logging.info('Attempting to recovery servo enabled device with '
1166 'firmware_repair.')
1167 self.firmware_install()
1168
1169 logging.info('Firmware repaired. Check if the DUT can boot. If not, '
1170 'reinstall the CrOS using servo.')
1171 try:
1172 self.servo.reset()
1173 self.verify()
1174 except Exception as e:
1175 logging.warn('Failed to verify DUT, error: %s. Will try to repair '
1176 'the DUT with servo_repair_reinstall.', e)
1177 self._servo_repair_reinstall()
1178
1179
Scott Zawalski62bacae2013-03-05 10:40:32 -05001180 def _servo_repair_power(self):
1181 """Attempt to repair DUT using an attached Servo.
1182
1183 Attempt to power on the DUT via power_long_press.
1184
1185 @raises AutoservRepairMethodNA if the device does not have servo
1186 support.
1187 @raises AutoservRepairFailure if the repair fails for any reason.
1188 """
1189 if not self.servo:
1190 raise error.AutoservRepairMethodNA('Repair Power NA: '
1191 'DUT has no servo support.')
1192
1193 logging.info('Attempting to recover servo enabled device by '
1194 'powering it off and on.')
1195 self.servo.get_power_state_controller().power_off()
1196 self.servo.get_power_state_controller().power_on()
1197 if self.wait_up(self.BOOT_TIMEOUT):
1198 return
1199
1200 raise error.AutoservRepairFailure('DUT did not boot after long_press.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001201
1202
Richard Barnette82c35912012-11-20 10:09:10 -08001203 def _powercycle_to_repair(self):
1204 """Utilize the RPM Infrastructure to bring the host back up.
1205
1206 If the host is not up/repaired after the first powercycle we utilize
1207 auto fallback to the last good install by powercycling and rebooting the
1208 host 6 times.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001209
1210 @raises AutoservRepairMethodNA if the device does not support remote
1211 power.
1212 @raises AutoservRepairFailure if the repair fails for any reason.
1213
Richard Barnette82c35912012-11-20 10:09:10 -08001214 """
Scott Zawalski62bacae2013-03-05 10:40:32 -05001215 if not self.has_power():
1216 raise error.AutoservRepairMethodNA('Device does not support power.')
1217
Richard Barnette82c35912012-11-20 10:09:10 -08001218 logging.info('Attempting repair via RPM powercycle.')
1219 failed_cycles = 0
1220 self.power_cycle()
1221 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
1222 failed_cycles += 1
1223 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
Scott Zawalski62bacae2013-03-05 10:40:32 -05001224 raise error.AutoservRepairFailure(
1225 'Powercycled host %s %d times; device did not come back'
1226 ' online.' % (self.hostname, failed_cycles))
Richard Barnette82c35912012-11-20 10:09:10 -08001227 self.power_cycle()
1228 if failed_cycles == 0:
1229 logging.info('Powercycling was successful first time.')
1230 else:
1231 logging.info('Powercycling was successful after %d failures.',
1232 failed_cycles)
1233
1234
MK Ryu35d661e2014-09-25 17:44:10 -07001235 def _reboot_repair(self):
1236 """SSH to this host and reboot."""
1237 if not self.is_up(self._CHECK_HOST_UP_TIMEOUT_SECS):
1238 raise error.AutoservRepairMethodNA('DUT unreachable for reboot.')
1239 logging.info('Attempting repair via SSH reboot.')
1240 self.reboot(timeout=self.BOOT_TIMEOUT, wait=True)
1241
1242
Prashanth B4d8184f2014-05-05 12:22:02 -07001243 def check_device(self):
1244 """Check if a device is ssh-able, and if so, clean and verify it.
1245
1246 @raise AutoservSSHTimeout: If the ssh ping times out.
1247 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
1248 permissions.
1249 @raise AutoservSshPingHostError: For other AutoservRunErrors during
1250 ssh_ping.
1251 @raises AutoservError: As appropriate, during cleanup and verify.
1252 """
1253 self.ssh_ping()
1254 self.cleanup()
1255 self.verify()
1256
1257
Dan Shi90466352015-09-22 15:01:05 -07001258 def confirm_servo(self):
1259 """Confirm servo is initialized and verified.
1260
1261 @raise AutoservError: If servo is not initialized and verified.
1262 """
1263 if self._servo_host.required_by_test and self.servo:
1264 return
1265
1266 # Force to re-create the servo object to make sure servo is verified.
1267 logging.debug('Rebuilding the servo object.')
1268 self.servo = None
1269 self._servo_host = None
1270 self._setup_servo()
1271 if not self.servo:
1272 raise error.AutoservError('Failed to create servo object.')
1273
1274
Dan Shid07ee2e2015-09-24 14:49:25 -07001275 def _is_last_provision_failed(self):
1276 """Checks if the last provision job failed.
1277
1278 @return: True if there exists file /var/tmp/provision_failed, which
1279 indicates the last provision job failed.
1280 False if the file does not exist or the dut can't be reached.
1281 """
1282 try:
1283 result = self.run('test -f %s' % PROVISION_FAILED,
1284 ignore_status=True, timeout=5)
1285 return result.exit_status == 0
1286 except (error.AutoservRunError, error.AutoservSSHTimeout):
1287 # Default to False, for repair to try all repair method if the dut
1288 # can't be reached.
1289 return False
1290
1291
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -08001292 def repair(self):
1293 """Attempt to get the DUT to pass `self.verify()`.
Richard Barnette82c35912012-11-20 10:09:10 -08001294
1295 This overrides the base class function for repair; it does
1296 not call back to the parent class, but instead offers a
1297 simplified implementation based on the capabilities in the
1298 Chrome OS test lab.
1299
Fang Deng5d518f42013-08-02 14:04:32 -07001300 It first verifies and repairs servo if it is a DUT in CrOS
Fang Deng03590af2013-10-07 17:34:20 -07001301 lab and a servo is attached.
Fang Deng5d518f42013-08-02 14:04:32 -07001302
Jakob Juelich82b7d1c2014-09-15 16:10:57 -07001303 This escalates in order through the following procedures and verifies
1304 the status using `self.check_device()` after each of them. This is done
1305 until both the repair and the veryfing step succeed.
1306
MK Ryu35d661e2014-09-25 17:44:10 -07001307 Escalation order of repair procedures from less intrusive to
1308 more intrusive repairs:
1309 1. SSH to the DUT and reboot.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001310 2. If there's a servo for the DUT, try to power the DUT off and
1311 on.
MK Ryu35d661e2014-09-25 17:44:10 -07001312 3. If the DUT can be power-cycled via RPM, try to repair
Richard Barnette82c35912012-11-20 10:09:10 -08001313 by power-cycling.
MK Ryu35d661e2014-09-25 17:44:10 -07001314 4. Try to re-install to a known stable image using
1315 auto-update.
1316 5. If there's a servo for the DUT, try to re-install via
1317 the servo.
Richard Barnette82c35912012-11-20 10:09:10 -08001318
1319 As with the parent method, the last operation performed on
Prashanth B4d8184f2014-05-05 12:22:02 -07001320 the DUT must be to call `self.check_device()`; If that call fails the
1321 exception it raises is passed back to the caller.
J. Richard Barnettefde55fc2013-03-15 17:47:01 -07001322
Scott Zawalski62bacae2013-03-05 10:40:32 -05001323 @raises AutoservRepairTotalFailure if the repair process fails to
1324 fix the DUT.
Fang Deng5d518f42013-08-02 14:04:32 -07001325 @raises ServoHostRepairTotalFailure if the repair process fails to
1326 fix the servo host if one is attached to the DUT.
1327 @raises AutoservSshPermissionDeniedError if it is unable
1328 to ssh to the servo host due to permission error.
1329
Richard Barnette82c35912012-11-20 10:09:10 -08001330 """
Jakob Juelich82b7d1c2014-09-15 16:10:57 -07001331 # Caution: Deleting shards relies on repair to always reboot the DUT.
1332
Nicolas Boichate30c7e12015-11-05 11:12:50 +08001333 # To repair a DUT connected to a moblab, try to create a servo object if
1334 # it was failed to be created earlier as there may be a servo_host host
1335 # attribute for this host.
1336 if utils.is_moblab():
1337 self._setup_servo()
1338
Dan Shi4d478522014-02-14 13:46:32 -08001339 if self._servo_host and not self.servo:
Fang Deng03590af2013-10-07 17:34:20 -07001340 try:
J. Richard Barnette4fc59c42015-12-15 16:58:50 -08001341 self._servo_host.repair()
Fang Deng03590af2013-10-07 17:34:20 -07001342 except Exception as e:
Fang Deng03590af2013-10-07 17:34:20 -07001343 logging.error('Could not create a healthy servo: %s', e)
Dan Shi4d478522014-02-14 13:46:32 -08001344 self.servo = self._servo_host.get_servo()
Fang Deng5d518f42013-08-02 14:04:32 -07001345
MK Ryu35d661e2014-09-25 17:44:10 -07001346 self.try_collect_crashlogs()
1347
Scott Zawalski62bacae2013-03-05 10:40:32 -05001348 # TODO(scottz): This should use something similar to label_decorator,
1349 # but needs to be populated in order so DUTs are repaired with the
1350 # least amount of effort.
Dan Shid07ee2e2015-09-24 14:49:25 -07001351 force_powerwash = self._is_last_provision_failed()
1352 if force_powerwash:
1353 logging.info('Last provision failed, try powerwash first.')
1354 autotest_stats.Counter(
1355 'repair_force_powerwash.TOTAL').increment()
1356 repair_funcs = [self._install_repair_with_powerwash,
Dan Shi3d7a0e12015-10-12 11:55:45 -07001357 self._servo_repair_reinstall,
1358 self._firmware_repair]
Dan Shid07ee2e2015-09-24 14:49:25 -07001359 else:
1360 repair_funcs = [self._reboot_repair,
1361 self._servo_repair_power,
1362 self._powercycle_to_repair,
1363 self._install_repair,
1364 self._install_repair_with_powerwash,
Dan Shi3d7a0e12015-10-12 11:55:45 -07001365 self._servo_repair_reinstall,
1366 self._firmware_repair]
Scott Zawalski62bacae2013-03-05 10:40:32 -05001367 errors = []
Simran Basie6130932013-10-01 14:07:52 -07001368 board = self._get_board_from_afe()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001369 for repair_func in repair_funcs:
1370 try:
1371 repair_func()
MK Ryu35d661e2014-09-25 17:44:10 -07001372 self.try_collect_crashlogs()
Prashanth B4d8184f2014-05-05 12:22:02 -07001373 self.check_device()
Gabe Black1e1c41b2015-02-04 23:55:15 -08001374 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001375 '%s.SUCCEEDED' % repair_func.__name__).increment()
1376 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001377 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001378 '%s.%s.SUCCEEDED' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001379 board)).increment()
Dan Shid07ee2e2015-09-24 14:49:25 -07001380 if force_powerwash:
1381 autotest_stats.Counter(
1382 'repair_force_powerwash.SUCCEEDED').increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001383 return
Simran Basie6130932013-10-01 14:07:52 -07001384 except error.AutoservRepairMethodNA as e:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001385 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001386 '%s.RepairNA' % repair_func.__name__).increment()
1387 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001388 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001389 '%s.%s.RepairNA' % (repair_func.__name__,
1390 board)).increment()
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001391 logging.warning('Repair function NA: %s', e)
Simran Basie6130932013-10-01 14:07:52 -07001392 errors.append(str(e))
Scott Zawalski62bacae2013-03-05 10:40:32 -05001393 except Exception as e:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001394 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001395 '%s.FAILED' % repair_func.__name__).increment()
1396 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001397 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001398 '%s.%s.FAILED' % (repair_func.__name__,
1399 board)).increment()
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001400 logging.warning('Failed to repair device: %s', e)
Scott Zawalski62bacae2013-03-05 10:40:32 -05001401 errors.append(str(e))
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001402
Dan Shid07ee2e2015-09-24 14:49:25 -07001403 if force_powerwash:
1404 autotest_stats.Counter(
1405 'repair_force_powerwash.FAILED').increment()
Gabe Black1e1c41b2015-02-04 23:55:15 -08001406 autotest_stats.Counter('Full_Repair_Failed').increment()
Simran Basie6130932013-10-01 14:07:52 -07001407 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001408 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001409 'Full_Repair_Failed.%s' % board).increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001410 raise error.AutoservRepairTotalFailure(
1411 'All attempts at repairing the device failed:\n%s' %
1412 '\n'.join(errors))
Richard Barnette82c35912012-11-20 10:09:10 -08001413
1414
MK Ryu35d661e2014-09-25 17:44:10 -07001415 def try_collect_crashlogs(self, check_host_up=True):
1416 """
1417 Check if a host is up and logs need to be collected from the host,
1418 if yes, collect them.
1419
1420 @param check_host_up: Flag for checking host is up. Default is True.
1421 """
1422 try:
1423 crash_job = self._need_crash_logs()
1424 if crash_job:
1425 logging.debug('%s: Job %s was crashed', self._CRASHLOGS_PREFIX,
1426 crash_job)
1427 if not check_host_up or self.is_up(
1428 self._CHECK_HOST_UP_TIMEOUT_SECS):
1429 self._collect_crashlogs(crash_job)
1430 logging.debug('%s: Completed collecting logs for the '
1431 'crashed job %s', self._CRASHLOGS_PREFIX,
1432 crash_job)
1433 except Exception as e:
1434 # Exception should not result in repair failure.
1435 # Therefore, suppress all exceptions here.
1436 logging.error('%s: Failed while trying to collect crash-logs: %s',
1437 self._CRASHLOGS_PREFIX, e)
1438
1439
1440 def _need_crash_logs(self):
1441 """Get the value of need_crash_logs attribute of this host.
1442
1443 @return: Value string of need_crash_logs attribute
1444 None if there is no need_crash_logs attribute
1445 """
1446 attrs = self._AFE.get_host_attribute(constants.CRASHLOGS_HOST_ATTRIBUTE,
1447 hostname=self.hostname)
1448 assert len(attrs) < 2
1449 return attrs[0].value if attrs else None
1450
1451
1452 def _collect_crashlogs(self, job_id):
1453 """Grab logs from the host where a job was crashed.
1454
1455 First, check if PRIOR_LOGS_DIR exists in the host.
1456 If yes, collect them.
1457 Otherwise, check if a lab-machine marker (_LAB_MACHINE_FILE) exists
1458 in the host.
1459 If yes, the host was repaired automatically, and we collect normal
1460 system logs.
1461
1462 @param job_id: Id of the job that was crashed.
1463 """
1464 crashlogs_dir = crashcollect.get_crashinfo_dir(self,
1465 constants.CRASHLOGS_DEST_DIR_PREFIX)
1466 flag_prior_logs = False
1467
1468 if self.path_exists(client_constants.PRIOR_LOGS_DIR):
1469 flag_prior_logs = True
1470 self._collect_prior_logs(crashlogs_dir)
1471 elif self.path_exists(self._LAB_MACHINE_FILE):
1472 self._collect_system_logs(crashlogs_dir)
1473 else:
1474 logging.warning('%s: Host was manually re-installed without '
1475 '--lab_preserve_log option. Skip collecting '
1476 'crash-logs.', self._CRASHLOGS_PREFIX)
1477
1478 # We make crash collection be one-time effort.
1479 # _collect_prior_logs() and _collect_system_logs() will not throw
1480 # any exception, and following codes will be executed even when
1481 # those methods fail.
1482 # _collect_crashlogs() is called only when the host is up (refer
1483 # to try_collect_crashlogs()). We assume _collect_prior_logs() and
1484 # _collect_system_logs() fail rarely when the host is up.
1485 # In addition, it is not clear how many times we should try crash
1486 # collection again while not triggering next repair unnecessarily.
1487 # Threfore, we try crash collection one time.
1488
1489 # Create a marker file as soon as log collection is done.
1490 # Leave the job id to this marker for gs_offloader to consume.
1491 marker_file = os.path.join(crashlogs_dir, constants.CRASHLOGS_MARKER)
1492 with open(marker_file, 'a') as f:
1493 f.write('%s\n' % job_id)
1494
1495 # Remove need_crash_logs attribute
1496 logging.debug('%s: Remove attribute need_crash_logs from host %s',
1497 self._CRASHLOGS_PREFIX, self.hostname)
1498 self._AFE.set_host_attribute(constants.CRASHLOGS_HOST_ATTRIBUTE,
1499 None, hostname=self.hostname)
1500
1501 if flag_prior_logs:
1502 logging.debug('%s: Remove %s from host %s', self._CRASHLOGS_PREFIX,
1503 client_constants.PRIOR_LOGS_DIR, self.hostname)
1504 self.run('rm -rf %s; sync' % client_constants.PRIOR_LOGS_DIR)
1505 # Wait for a few seconds to make sure the prior command is
1506 # done deep through storage.
1507 time.sleep(self._SAFE_WAIT_SECS)
1508
1509
1510 def _collect_prior_logs(self, crashlogs_dir):
1511 """Grab prior logs that were stashed before re-installing a host.
1512
1513 @param crashlogs_dir: Directory path where crash-logs are stored.
1514 """
1515 logging.debug('%s: Found %s, collecting them...',
1516 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR)
1517 try:
1518 self.collect_logs(client_constants.PRIOR_LOGS_DIR,
1519 crashlogs_dir, False)
1520 logging.debug('%s: %s is collected',
1521 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR)
1522 except Exception as e:
1523 logging.error('%s: Failed to collect %s: %s',
1524 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR,
1525 e)
1526
1527
1528 def _collect_system_logs(self, crashlogs_dir):
1529 """Grab normal system logs from a host.
1530
1531 @param crashlogs_dir: Directory path where crash-logs are stored.
1532 """
1533 logging.debug('%s: Found %s, collecting system logs...',
1534 self._CRASHLOGS_PREFIX, self._LAB_MACHINE_FILE)
1535 sources = server_utils.parse_simple_config(self._LOGS_TO_COLLECT_FILE)
1536 for src in sources:
1537 try:
1538 if self.path_exists(src):
1539 logging.debug('%s: Collecting %s...',
1540 self._CRASHLOGS_PREFIX, src)
1541 dest = server_utils.concat_path_except_last(
1542 crashlogs_dir, src)
1543 self.collect_logs(src, dest, False)
1544 logging.debug('%s: %s is collected',
1545 self._CRASHLOGS_PREFIX, src)
1546 except Exception as e:
1547 logging.error('%s: Failed to collect %s: %s',
1548 self._CRASHLOGS_PREFIX, src, e)
1549
1550
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001551 def close(self):
Fang Deng0ca40e22013-08-27 17:47:44 -07001552 super(CrosHost, self).close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001553
1554
Dan Shi49ca0932014-11-14 11:22:27 -08001555 def get_power_supply_info(self):
1556 """Get the output of power_supply_info.
1557
1558 power_supply_info outputs the info of each power supply, e.g.,
1559 Device: Line Power
1560 online: no
1561 type: Mains
1562 voltage (V): 0
1563 current (A): 0
1564 Device: Battery
1565 state: Discharging
1566 percentage: 95.9276
1567 technology: Li-ion
1568
1569 Above output shows two devices, Line Power and Battery, with details of
1570 each device listed. This function parses the output into a dictionary,
1571 with key being the device name, and value being a dictionary of details
1572 of the device info.
1573
1574 @return: The dictionary of power_supply_info, e.g.,
1575 {'Line Power': {'online': 'yes', 'type': 'main'},
1576 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001577 @raise error.AutoservRunError if power_supply_info tool is not found in
1578 the DUT. Caller should handle this error to avoid false failure
1579 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001580 """
1581 result = self.run('power_supply_info').stdout.strip()
1582 info = {}
1583 device_name = None
1584 device_info = {}
1585 for line in result.split('\n'):
1586 pair = [v.strip() for v in line.split(':')]
1587 if len(pair) != 2:
1588 continue
1589 if pair[0] == 'Device':
1590 if device_name:
1591 info[device_name] = device_info
1592 device_name = pair[1]
1593 device_info = {}
1594 else:
1595 device_info[pair[0]] = pair[1]
1596 if device_name and not device_name in info:
1597 info[device_name] = device_info
1598 return info
1599
1600
1601 def get_battery_percentage(self):
1602 """Get the battery percentage.
1603
1604 @return: The percentage of battery level, value range from 0-100. Return
1605 None if the battery info cannot be retrieved.
1606 """
1607 try:
1608 info = self.get_power_supply_info()
1609 logging.info(info)
1610 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001611 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001612 return None
1613
1614
1615 def is_ac_connected(self):
1616 """Check if the dut has power adapter connected and charging.
1617
1618 @return: True if power adapter is connected and charging.
1619 """
1620 try:
1621 info = self.get_power_supply_info()
1622 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001623 except (KeyError, error.AutoservRunError):
1624 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001625
1626
Simran Basi5e6339a2013-03-21 11:34:32 -07001627 def _cleanup_poweron(self):
1628 """Special cleanup method to make sure hosts always get power back."""
1629 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1630 hosts = afe.get_hosts(hostname=self.hostname)
1631 if not hosts or not (self._RPM_OUTLET_CHANGED in
1632 hosts[0].attributes):
1633 return
1634 logging.debug('This host has recently interacted with the RPM'
1635 ' Infrastructure. Ensuring power is on.')
1636 try:
1637 self.power_on()
Dan Shi7dca56e2014-11-11 17:07:56 -08001638 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1639 hostname=self.hostname)
Simran Basi5e6339a2013-03-21 11:34:32 -07001640 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001641 logging.error('Failed to turn Power On for this host after '
1642 'cleanup through the RPM Infrastructure.')
Gabe Blackb72f4fb2015-01-20 16:47:13 -08001643 autotest_es.post(
Dan Shi7dca56e2014-11-11 17:07:56 -08001644 type_str='RPM_poweron_failure',
1645 metadata={'hostname': self.hostname})
Dan Shi49ca0932014-11-14 11:22:27 -08001646
1647 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -08001648 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -08001649 raise
1650 elif self.is_ac_connected():
1651 logging.info('The device has power adapter connected and '
1652 'charging. No need to try to turn RPM on '
1653 'again.')
1654 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1655 hostname=self.hostname)
1656 logging.info('Battery level is now at %s%%. The device may '
1657 'still have enough power to run test, so no '
1658 'exception will be raised.', battery_percentage)
1659
Simran Basi5e6339a2013-03-21 11:34:32 -07001660
beepsc87ff602013-07-31 21:53:00 -07001661 def _is_factory_image(self):
1662 """Checks if the image on the DUT is a factory image.
1663
1664 @return: True if the image on the DUT is a factory image.
1665 False otherwise.
1666 """
1667 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1668 return result.exit_status == 0
1669
1670
1671 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001672 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001673
1674 @raises: FactoryImageCheckerException for factory images, since
1675 we cannot attempt to restart ui on them.
1676 error.AutoservRunError for any other type of error that
1677 occurs while restarting ui.
1678 """
1679 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001680 raise FactoryImageCheckerException('Cannot restart ui on factory '
1681 'images')
beepsc87ff602013-07-31 21:53:00 -07001682
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001683 # TODO(jrbarnette): The command to stop/start the ui job
1684 # should live inside cros_ui, too. However that would seem
1685 # to imply interface changes to the existing start()/restart()
1686 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -07001687 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001688 self.run('stop ui; start ui')
1689 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001690
1691
Dan Shi549fb822015-03-24 18:01:11 -07001692 def get_release_version(self):
1693 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1694
1695 @returns The version string in lsb-release, under attribute
1696 CHROMEOS_RELEASE_VERSION.
1697 """
1698 lsb_release_content = self.run(
1699 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1700 return lsbrelease_utils.get_chromeos_release_version(
1701 lsb_release_content=lsb_release_content)
1702
1703
1704 def verify_cros_version_label(self):
1705 """ Make sure host's cros-version label match the actual image in dut.
1706
1707 Remove any cros-version: label that doesn't match that installed in
1708 the dut.
1709
1710 @param raise_error: Set to True to raise exception if any mismatch found
1711
1712 @raise error.AutoservError: If any mismatch between cros-version label
1713 and the build installed in dut is found.
1714 """
1715 labels = self._AFE.get_labels(
1716 name__startswith=ds_constants.VERSION_PREFIX,
1717 host__hostname=self.hostname)
1718 mismatch_found = False
1719 if labels:
1720 # Get CHROMEOS_RELEASE_VERSION from lsb-release, e.g., 6908.0.0.
1721 # Note that it's different from cros-version label, which has
1722 # builder and branch info, e.g.,
1723 # cros-version:peppy-release/R43-6908.0.0
1724 release_version = self.get_release_version()
1725 host_list = [self.hostname]
1726 for label in labels:
1727 # Remove any cros-version label that does not match
1728 # release_version.
1729 build_version = label.name[len(ds_constants.VERSION_PREFIX):]
1730 if not utils.version_match(build_version, release_version):
1731 logging.warn('cros-version label "%s" does not match '
1732 'release version %s. Removing the label.',
1733 label.name, release_version)
1734 label.remove_hosts(hosts=host_list)
1735 mismatch_found = True
1736 if mismatch_found:
Dan Shi1057bae2015-03-30 11:35:09 -07001737 autotest_es.post(use_http=True,
1738 type_str='cros_version_label_mismatch',
1739 metadata={'hostname': self.hostname})
Dan Shi549fb822015-03-24 18:01:11 -07001740 raise error.AutoservError('The host has wrong cros-version label.')
1741
1742
Darren Krahn0bd18e82015-10-28 23:30:46 +00001743 def verify_tpm_status(self):
1744 """ Verify the host's TPM is in a good state.
1745
1746 @raise error.AutoservError: If state is not good.
1747 """
1748 # This cryptohome command emits status information in JSON format. It
1749 # looks something like this:
1750 # {
1751 # "installattrs": {
1752 # "first_install": false,
1753 # "initialized": true,
1754 # "invalid": false,
1755 # "lockbox_index": 536870916,
1756 # "lockbox_nvram_version": 2,
1757 # "secure": true,
1758 # "size": 0,
1759 # "version": 1
1760 # },
1761 # "mounts": [ {
1762 # "enterprise": false,
1763 # "keysets": [ {
1764 # "current": true,
1765 # "index": 0,
1766 # "last_activity": 1330111359,
1767 # "ok": true,
1768 # "scrypt": true,
1769 # "tpm": false
1770 # } ],
1771 # "mounted": true,
1772 # "owner": "dbb3dd34edb181245130e136be51fa08478d3909"
1773 # } ],
1774 # "tpm": {
1775 # "being_owned": false,
1776 # "can_connect": true,
1777 # "can_decrypt": false,
1778 # "can_encrypt": false,
1779 # "can_load_srk": true,
1780 # "can_load_srk_pubkey": true,
1781 # "enabled": true,
1782 # "has_context": true,
1783 # "has_cryptohome_key": false,
1784 # "has_key_handle": false,
1785 # "last_error": 0,
1786 # "owned": true
1787 # }
1788 # }
1789 output = self.run('cryptohome --action=status').stdout.strip()
1790 try:
1791 status = json.loads(output)
1792 except ValueError:
1793 logging.error('TPM_VERIFY: Cryptohome did not return valid status.')
1794 return
1795 try:
1796 tpm = status['tpm']
1797 if (not tpm['enabled'] or not tpm['can_connect'] or
1798 (tpm['owned'] and not tpm['can_load_srk']) or
1799 (tpm['can_load_srk'] and not tpm['can_load_srk_pubkey'])):
1800 logging.error('TPM_VERIFY: The host TPM is in a bad state.')
1801 raise error.AutoservError('The host TPM is in a bad state.')
1802 else:
1803 logging.debug('TPM_VERIFY: The host TPM is in a good state.')
1804 except KeyError:
1805 logging.error('TPM_VERIFY: Cryptohome did not return valid status.')
1806
1807
beepsc87ff602013-07-31 21:53:00 -07001808 def cleanup(self):
MK Ryu35d661e2014-09-25 17:44:10 -07001809 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001810 try:
beepsc87ff602013-07-31 21:53:00 -07001811 self._restart_ui()
1812 except (error.AutotestRunError, error.AutoservRunError,
1813 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001814 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001815 # Since restarting the UI fails fall back to normal Autotest
1816 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001817 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001818 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001819 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001820 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001821 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001822
1823
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001824 def reboot(self, **dargs):
1825 """
1826 This function reboots the site host. The more generic
1827 RemoteHost.reboot() performs sync and sleeps for 5
1828 seconds. This is not necessary for Chrome OS devices as the
1829 sync should be finished in a short time during the reboot
1830 command.
1831 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001832 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001833 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001834 dargs['reboot_cmd'] = ('sleep 1; '
1835 'reboot & sleep %d; '
1836 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001837 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001838 if 'fastsync' not in dargs:
1839 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001840
Charlie Mooneya8e6dab2014-05-29 14:37:55 -07001841 # For purposes of logging reboot times:
1842 # Get the board name i.e. 'daisy_spring'
Michael Liangca4f5a62014-07-10 15:45:13 -07001843 board_fullname = self.get_board()
1844
1845 # Strip the prefix and add it to dargs.
1846 dargs['board'] = board_fullname[board_fullname.find(':')+1:]
Fang Deng0ca40e22013-08-27 17:47:44 -07001847 super(CrosHost, self).reboot(**dargs)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001848
1849
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001850 def suspend(self, **dargs):
1851 """
1852 This function suspends the site host.
1853 """
1854 suspend_time = dargs.get('suspend_time', 60)
1855 dargs['timeout'] = suspend_time
1856 if 'suspend_cmd' not in dargs:
J. Richard Barnette9af19632015-09-25 12:18:03 -07001857 dargs['suspend_cmd'] = ' && '.join([
1858 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001859 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
J. Richard Barnette9af19632015-09-25 12:18:03 -07001860 'powerd_dbus_suspend --delay=0'])
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001861 super(CrosHost, self).suspend(**dargs)
1862
1863
Simran Basiec564392014-08-25 16:48:09 -07001864 def upstart_status(self, service_name):
1865 """Check the status of an upstart init script.
1866
1867 @param service_name: Service to look up.
1868
1869 @returns True if the service is running, False otherwise.
1870 """
1871 return self.run('status %s | grep start/running' %
1872 service_name).stdout.strip() != ''
1873
1874
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001875 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001876 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001877
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001878 Tests for the following conditions:
1879 1. All conditions tested by the parent version of this
1880 function.
1881 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001882 3. Sufficient space in /mnt/stateful_partition/encrypted.
1883 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001884
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001885 """
MK Ryu35d661e2014-09-25 17:44:10 -07001886 # Check if a job was crashed on this host.
1887 # If yes, avoid verification until crash-logs are collected.
1888 if self._need_crash_logs():
1889 raise error.AutoservCrashLogCollectRequired(
1890 'Need to collect crash-logs before verification')
1891
Fang Deng0ca40e22013-08-27 17:47:44 -07001892 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001893 default_kilo_inodes_required = CONFIG.get_config_value(
1894 'SERVER', 'kilo_inodes_required', type=int, default=100)
1895 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1896 kilo_inodes_required = CONFIG.get_config_value(
1897 'SERVER', 'kilo_inodes_required_%s' % board,
1898 type=int, default=default_kilo_inodes_required)
1899 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001900 self.check_diskspace(
1901 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001902 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001903 'SERVER', 'gb_diskspace_required', type=float,
1904 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001905 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1906 # Not all targets build with encrypted stateful support.
1907 if self.path_exists(encrypted_stateful_path):
1908 self.check_diskspace(
1909 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001910 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001911 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1912 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001913
Simran Basiec564392014-08-25 16:48:09 -07001914 if not self.upstart_status('system-services'):
Prashanth B5d0a0512014-04-25 12:26:08 -07001915 raise error.AutoservError('Chrome failed to reach login. '
1916 'System services not running.')
1917
beepsc87ff602013-07-31 21:53:00 -07001918 # Factory images don't run update engine,
1919 # goofy controls dbus on these DUTs.
1920 if not self._is_factory_image():
1921 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -05001922 # Makes sure python is present, loads and can use built in functions.
1923 # We have seen cases where importing cPickle fails with undefined
1924 # symbols in cPickle.so.
1925 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001926
Dan Shi549fb822015-03-24 18:01:11 -07001927 self.verify_cros_version_label()
1928
Darren Krahn0bd18e82015-10-28 23:30:46 +00001929 self.verify_tpm_status()
1930
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001931
Dan Shi49ca0932014-11-14 11:22:27 -08001932 def verify_hardware(self):
1933 """Verify hardware system of a Chrome OS system.
1934
1935 Check following hardware conditions:
1936 1. Battery level.
1937 2. Is power adapter connected.
1938 """
1939 logging.info('Battery percentage: %s', self.get_battery_percentage())
Dan Shie9b765d2014-12-29 16:59:49 -08001940 if self.is_ac_connected() is None:
1941 logging.info('Can not determine if the device has power adapter '
1942 'connected.')
1943 else:
1944 logging.info('Device %s power adapter connected and charging.',
1945 'has' if self.is_ac_connected() else 'does not have')
Dan Shi49ca0932014-11-14 11:22:27 -08001946
1947
Fang Deng96667ca2013-08-01 17:46:18 -07001948 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
1949 connect_timeout=None, alive_interval=None):
1950 """Override default make_ssh_command to use options tuned for Chrome OS.
1951
1952 Tuning changes:
1953 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1954 connection failure. Consistency with remote_access.sh.
1955
Samuel Tan2ce155b2015-06-23 18:24:38 -07001956 - ServerAliveInterval=900; which causes SSH to ping connection every
1957 900 seconds. In conjunction with ServerAliveCountMax ensures
1958 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001959 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001960 the test completed successfully. Later increased from 180 seconds to
1961 900 seconds to account for tests where the DUT is suspended for
1962 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001963
1964 - ServerAliveCountMax=3; consistency with remote_access.sh.
1965
1966 - ConnectAttempts=4; reduce flakiness in connection errors;
1967 consistency with remote_access.sh.
1968
1969 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1970 Host keys change with every new installation, don't waste
1971 memory/space saving them.
1972
1973 - SSH protocol forced to 2; needed for ServerAliveInterval.
1974
1975 @param user User name to use for the ssh connection.
1976 @param port Port on the target host to use for ssh connection.
1977 @param opts Additional options to the ssh command.
1978 @param hosts_file Ignored.
1979 @param connect_timeout Ignored.
1980 @param alive_interval Ignored.
1981 """
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001982 base_command = ('/usr/bin/ssh -a -x %s %s %s'
1983 ' -o StrictHostKeyChecking=no'
Fang Deng96667ca2013-08-01 17:46:18 -07001984 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Samuel Tan2ce155b2015-06-23 18:24:38 -07001985 ' -o ConnectTimeout=30 -o ServerAliveInterval=900'
Fang Deng96667ca2013-08-01 17:46:18 -07001986 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
1987 ' -o Protocol=2 -l %s -p %d')
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001988 return base_command % (self._ssh_verbosity_flag, self._ssh_options,
1989 opts, user, port)
Jason Abeleb6f924f2013-11-13 16:01:54 -08001990 def syslog(self, message, tag='autotest'):
1991 """Logs a message to syslog on host.
1992
1993 @param message String message to log into syslog
1994 @param tag String tag prefix for syslog
1995
1996 """
1997 self.run('logger -t "%s" "%s"' % (tag, message))
1998
1999
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002000 def _ping_check_status(self, status):
2001 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002002
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002003 @param status Check the ping status against this value.
2004 @return True iff `status` and the result of ping are the same
2005 (i.e. both True or both False).
2006
2007 """
2008 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
2009 return not (status ^ (ping_val == 0))
2010
2011 def _ping_wait_for_status(self, status, timeout):
2012 """Wait for the host to have a given status (UP or DOWN).
2013
2014 Status is checked by polling. Polling will not last longer
2015 than the number of seconds in `timeout`. The polling
2016 interval will be long enough that only approximately
2017 _PING_WAIT_COUNT polling cycles will be executed, subject
2018 to a maximum interval of about one minute.
2019
2020 @param status Waiting will stop immediately if `ping` of the
2021 host returns this status.
2022 @param timeout Poll for at most this many seconds.
2023 @return True iff the host status from `ping` matched the
2024 requested status at the time of return.
2025
2026 """
2027 # _ping_check_status() takes about 1 second, hence the
2028 # "- 1" in the formula below.
2029 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
2030 end_time = time.time() + timeout
2031 while time.time() <= end_time:
2032 if self._ping_check_status(status):
2033 return True
2034 if poll_interval > 0:
2035 time.sleep(poll_interval)
2036
2037 # The last thing we did was sleep(poll_interval), so it may
2038 # have been too long since the last `ping`. Check one more
2039 # time, just to be sure.
2040 return self._ping_check_status(status)
2041
2042 def ping_wait_up(self, timeout):
2043 """Wait for the host to respond to `ping`.
2044
2045 N.B. This method is not a reliable substitute for
2046 `wait_up()`, because a host that responds to ping will not
2047 necessarily respond to ssh. This method should only be used
2048 if the target DUT can be considered functional even if it
2049 can't be reached via ssh.
2050
2051 @param timeout Minimum time to allow before declaring the
2052 host to be non-responsive.
2053 @return True iff the host answered to ping before the timeout.
2054
2055 """
2056 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002057
Andrew Bresticker678c0c72013-01-22 10:44:09 -08002058 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002059 """Wait until the host no longer responds to `ping`.
2060
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002061 This function can be used as a slightly faster version of
2062 `wait_down()`, by avoiding potentially long ssh timeouts.
2063
2064 @param timeout Minimum time to allow for the host to become
2065 non-responsive.
2066 @return True iff the host quit answering ping before the
2067 timeout.
2068
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002069 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002070 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002071
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002072 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002073 """Wait for the client to enter low-power sleep mode.
2074
2075 The test for "is asleep" can't distinguish a system that is
2076 powered off; to confirm that the unit was asleep, it is
2077 necessary to force resume, and then call
2078 `test_wait_for_resume()`.
2079
2080 This function is expected to be called from a test as part
2081 of a sequence like the following:
2082
2083 ~~~~~~~~
2084 boot_id = host.get_boot_id()
2085 # trigger sleep on the host
2086 host.test_wait_for_sleep()
2087 # trigger resume on the host
2088 host.test_wait_for_resume(boot_id)
2089 ~~~~~~~~
2090
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002091 @param sleep_timeout time limit in seconds to allow the host sleep.
2092
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002093 @exception TestFail The host did not go to sleep within
2094 the allowed time.
2095 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002096 if sleep_timeout is None:
2097 sleep_timeout = self.SLEEP_TIMEOUT
2098
2099 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002100 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002101 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002102
2103
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002104 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002105 """Wait for the client to resume from low-power sleep mode.
2106
2107 The `old_boot_id` parameter should be the value from
2108 `get_boot_id()` obtained prior to entering sleep mode. A
2109 `TestFail` exception is raised if the boot id changes.
2110
2111 See @ref test_wait_for_sleep for more on this function's
2112 usage.
2113
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002114 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002115 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002116 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002117
2118 @exception TestFail The host did not respond within the
2119 allowed time.
2120 @exception TestFail The host responded, but the boot id test
2121 indicated a reboot rather than a sleep
2122 cycle.
2123 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002124 if resume_timeout is None:
2125 resume_timeout = self.RESUME_TIMEOUT
2126
2127 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002128 raise error.TestFail(
2129 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002130 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002131 else:
2132 new_boot_id = self.get_boot_id()
2133 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002134 logging.error('client rebooted (old boot %s, new boot %s)',
2135 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002136 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002137 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002138
2139
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002140 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002141 """Wait for the client to shut down.
2142
2143 The test for "has shut down" can't distinguish a system that
2144 is merely asleep; to confirm that the unit was down, it is
2145 necessary to force boot, and then call test_wait_for_boot().
2146
2147 This function is expected to be called from a test as part
2148 of a sequence like the following:
2149
2150 ~~~~~~~~
2151 boot_id = host.get_boot_id()
2152 # trigger shutdown on the host
2153 host.test_wait_for_shutdown()
2154 # trigger boot on the host
2155 host.test_wait_for_boot(boot_id)
2156 ~~~~~~~~
2157
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002158 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002159 @exception TestFail The host did not shut down within the
2160 allowed time.
2161 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002162 if shutdown_timeout is None:
2163 shutdown_timeout = self.SHUTDOWN_TIMEOUT
2164
2165 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002166 raise error.TestFail(
2167 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002168 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002169
2170
2171 def test_wait_for_boot(self, old_boot_id=None):
2172 """Wait for the client to boot from cold power.
2173
2174 The `old_boot_id` parameter should be the value from
2175 `get_boot_id()` obtained prior to shutting down. A
2176 `TestFail` exception is raised if the boot id does not
2177 change. The boot id test is omitted if `old_boot_id` is not
2178 specified.
2179
2180 See @ref test_wait_for_shutdown for more on this function's
2181 usage.
2182
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002183 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002184 shut down.
2185
2186 @exception TestFail The host did not respond within the
2187 allowed time.
2188 @exception TestFail The host responded, but the boot id test
2189 indicated that there was no reboot.
2190 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002191 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002192 raise error.TestFail(
2193 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002194 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002195 elif old_boot_id:
2196 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002197 logging.error('client not rebooted (boot %s)',
2198 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002199 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002200 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07002201
2202
2203 @staticmethod
2204 def check_for_rpm_support(hostname):
2205 """For a given hostname, return whether or not it is powered by an RPM.
2206
Simran Basi1df55112013-09-06 11:25:09 -07002207 @param hostname: hostname to check for rpm support.
2208
Simran Basid5e5e272012-09-24 15:23:59 -07002209 @return None if this host does not follows the defined naming format
2210 for RPM powered DUT's in the lab. If it does follow the format,
2211 it returns a regular expression MatchObject instead.
2212 """
Fang Dengbaff9082015-01-06 13:46:15 -08002213 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002214
2215
2216 def has_power(self):
2217 """For this host, return whether or not it is powered by an RPM.
2218
2219 @return True if this host is in the CROS lab and follows the defined
2220 naming format.
2221 """
Fang Deng0ca40e22013-08-27 17:47:44 -07002222 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002223
2224
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002225 def _set_power(self, state, power_method):
2226 """Sets the power to the host via RPM, Servo or manual.
2227
2228 @param state Specifies which power state to set to DUT
2229 @param power_method Specifies which method of power control to
2230 use. By default "RPM" will be used. Valid values
2231 are the strings "RPM", "manual", "servoj10".
2232
2233 """
2234 ACCEPTABLE_STATES = ['ON', 'OFF']
2235
2236 if state.upper() not in ACCEPTABLE_STATES:
2237 raise error.TestError('State must be one of: %s.'
2238 % (ACCEPTABLE_STATES,))
2239
2240 if power_method == self.POWER_CONTROL_SERVO:
2241 logging.info('Setting servo port J10 to %s', state)
2242 self.servo.set('prtctl3_pwren', state.lower())
2243 time.sleep(self._USB_POWER_TIMEOUT)
2244 elif power_method == self.POWER_CONTROL_MANUAL:
2245 logging.info('You have %d seconds to set the AC power to %s.',
2246 self._POWER_CYCLE_TIMEOUT, state)
2247 time.sleep(self._POWER_CYCLE_TIMEOUT)
2248 else:
2249 if not self.has_power():
2250 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07002251 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
2252 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
2253 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07002254 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07002255
2256
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002257 def power_off(self, power_method=POWER_CONTROL_RPM):
2258 """Turn off power to this host via RPM, Servo or manual.
2259
2260 @param power_method Specifies which method of power control to
2261 use. By default "RPM" will be used. Valid values
2262 are the strings "RPM", "manual", "servoj10".
2263
2264 """
2265 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07002266
2267
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002268 def power_on(self, power_method=POWER_CONTROL_RPM):
2269 """Turn on power to this host via RPM, Servo or manual.
2270
2271 @param power_method Specifies which method of power control to
2272 use. By default "RPM" will be used. Valid values
2273 are the strings "RPM", "manual", "servoj10".
2274
2275 """
2276 self._set_power('ON', power_method)
2277
2278
2279 def power_cycle(self, power_method=POWER_CONTROL_RPM):
2280 """Cycle power to this host by turning it OFF, then ON.
2281
2282 @param power_method Specifies which method of power control to
2283 use. By default "RPM" will be used. Valid values
2284 are the strings "RPM", "manual", "servoj10".
2285
2286 """
2287 if power_method in (self.POWER_CONTROL_SERVO,
2288 self.POWER_CONTROL_MANUAL):
2289 self.power_off(power_method=power_method)
2290 time.sleep(self._POWER_CYCLE_TIMEOUT)
2291 self.power_on(power_method=power_method)
2292 else:
2293 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002294
2295
2296 def get_platform(self):
2297 """Determine the correct platform label for this host.
2298
2299 @returns a string representing this host's platform.
2300 """
2301 crossystem = utils.Crossystem(self)
2302 crossystem.init()
2303 # Extract fwid value and use the leading part as the platform id.
2304 # fwid generally follow the format of {platform}.{firmware version}
2305 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
2306 platform = crossystem.fwid().split('.')[0].lower()
2307 # Newer platforms start with 'Google_' while the older ones do not.
2308 return platform.replace('google_', '')
2309
2310
Hung-ying Tyanb1328032014-04-01 14:18:54 +08002311 def get_architecture(self):
2312 """Determine the correct architecture label for this host.
2313
2314 @returns a string representing this host's architecture.
2315 """
2316 crossystem = utils.Crossystem(self)
2317 crossystem.init()
2318 return crossystem.arch()
2319
2320
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002321 def get_chrome_version(self):
2322 """Gets the Chrome version number and milestone as strings.
2323
2324 Invokes "chrome --version" to get the version number and milestone.
2325
2326 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
2327 current Chrome version number as a string (in the form "W.X.Y.Z")
2328 and "milestone" is the first component of the version number
2329 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
2330 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
2331 of "chrome --version" and the milestone will be the empty string.
2332
2333 """
MK Ryu35d661e2014-09-25 17:44:10 -07002334 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002335 return utils.parse_chrome_version(version_string)
2336
Aviv Keshet74c89a92013-02-04 15:18:30 -08002337 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07002338 def get_board(self):
2339 """Determine the correct board label for this host.
2340
2341 @returns a string representing this host's board.
2342 """
2343 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
2344 run_method=self.run)
2345 board = release_info['CHROMEOS_RELEASE_BOARD']
2346 # Devices in the lab generally have the correct board name but our own
2347 # development devices have {board_name}-signed-{key_type}. The board
2348 # name may also begin with 'x86-' which we need to keep.
Simran Basi833814b2013-01-29 13:13:43 -08002349 board_format_string = ds_constants.BOARD_PREFIX + '%s'
Simran Basic6f1f7a2012-10-16 10:47:46 -07002350 if 'x86' not in board:
Simran Basi833814b2013-01-29 13:13:43 -08002351 return board_format_string % board.split('-')[0]
2352 return board_format_string % '-'.join(board.split('-')[0:2])
Simran Basic6f1f7a2012-10-16 10:47:46 -07002353
2354
Aviv Keshet74c89a92013-02-04 15:18:30 -08002355 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002356 def has_lightsensor(self):
2357 """Determine the correct board label for this host.
2358
2359 @returns the string 'lightsensor' if this host has a lightsensor or
2360 None if it does not.
2361 """
2362 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08002363 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07002364 try:
2365 # Run the search cmd following the symlinks. Stderr_tee is set to
2366 # None as there can be a symlink loop, but the command will still
2367 # execute correctly with a few messages printed to stderr.
2368 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
2369 return 'lightsensor'
2370 except error.AutoservRunError:
2371 # egrep exited with a return code of 1 meaning none of the possible
2372 # lightsensor files existed.
2373 return None
2374
2375
Aviv Keshet74c89a92013-02-04 15:18:30 -08002376 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002377 def has_bluetooth(self):
2378 """Determine the correct board label for this host.
2379
2380 @returns the string 'bluetooth' if this host has bluetooth or
2381 None if it does not.
2382 """
2383 try:
2384 self.run('test -d /sys/class/bluetooth/hci0')
2385 # test exited with a return code of 0.
2386 return 'bluetooth'
2387 except error.AutoservRunError:
2388 # test exited with a return code 1 meaning the directory did not
2389 # exist.
2390 return None
2391
2392
Bill Richardson4f595f52014-02-13 16:20:26 -08002393 @label_decorator('ec')
2394 def get_ec(self):
2395 """
2396 Determine the type of EC on this host.
2397
2398 @returns a string representing this host's embedded controller type.
2399 At present, it only returns "ec:cros", for Chrome OS ECs. Other types
2400 of EC (or none) don't return any strings, since no tests depend on
2401 those.
2402 """
2403 cmd = 'mosys ec info'
2404 # The output should look like these, so that the last field should
2405 # match our EC version scheme:
2406 #
2407 # stm | stm32f100 | snow_v1.3.139-375eb9f
2408 # ti | Unknown-10de | peppy_v1.5.114-5d52788
2409 #
2410 # Non-Chrome OS ECs will look like these:
2411 #
2412 # ENE | KB932 | 00BE107A00
2413 # ite | it8518 | 3.08
2414 #
2415 # And some systems don't have ECs at all (Lumpy, for example).
2416 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
2417
2418 ecinfo = self.run(command=cmd, ignore_status=True)
2419 if ecinfo.exit_status == 0:
2420 res = re.search(regexp, ecinfo.stdout)
2421 if res:
2422 logging.info("EC version is %s", res.groups()[0])
2423 return 'ec:cros'
2424 logging.info("%s got: %s", cmd, ecinfo.stdout)
2425 # Has an EC, but it's not a Chrome OS EC
2426 return None
2427 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
2428 # No EC present
2429 return None
2430
2431
Alec Berg31b932b2014-04-04 16:09:11 -07002432 @label_decorator('accels')
2433 def get_accels(self):
2434 """
2435 Determine the type of accelerometers on this host.
2436
2437 @returns a string representing this host's accelerometer type.
2438 At present, it only returns "accel:cros-ec", for accelerometers
2439 attached to a Chrome OS EC, or none, if no accelerometers.
2440 """
2441 # Check to make sure we have ectool
2442 rv = self.run('which ectool', ignore_status=True)
2443 if rv.exit_status:
2444 logging.info("No ectool cmd found, assuming no EC accelerometers")
2445 return None
2446
2447 # Check that the EC supports the motionsense command
2448 rv = self.run('ectool motionsense', ignore_status=True)
2449 if rv.exit_status:
2450 logging.info("EC does not support motionsense command "
2451 "assuming no EC accelerometers")
2452 return None
2453
2454 # Check that EC motion sensors are active
2455 active = self.run('ectool motionsense active').stdout.split('\n')
2456 if active[0] == "0":
2457 logging.info("Motion sense inactive, assuming no EC accelerometers")
2458 return None
2459
2460 logging.info("EC accelerometers found")
2461 return 'accel:cros-ec'
2462
2463
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002464 @label_decorator('chameleon')
2465 def has_chameleon(self):
2466 """Determine if a Chameleon connected to this host.
2467
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08002468 @returns a list containing two strings ('chameleon' and
2469 'chameleon:' + label, e.g. 'chameleon:hdmi') if this host
2470 has a Chameleon or None if it has not.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002471 """
2472 if self._chameleon_host:
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08002473 return ['chameleon', 'chameleon:' + self.chameleon.get_label()]
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002474 else:
2475 return None
2476
2477
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +08002478 @label_decorator('audio_loopback_dongle')
2479 def has_loopback_dongle(self):
2480 """Determine if an audio loopback dongle is plugged to this host.
2481
2482 @returns 'audio_loopback_dongle' when there is an audio loopback dongle
2483 plugged to this host.
2484 None when there is no audio loopback dongle
2485 plugged to this host.
2486 """
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +08002487 nodes_info = self.run(command=cras_utils.get_cras_nodes_cmd(),
2488 ignore_status=True).stdout
2489 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
2490 cras_utils.node_type_is_plugged('MIC', nodes_info)):
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +08002491 return 'audio_loopback_dongle'
2492 else:
2493 return None
2494
2495
Derek Basehorec71ff622014-07-07 15:18:40 -07002496 @label_decorator('power_supply')
2497 def get_power_supply(self):
2498 """
2499 Determine what type of power supply the host has
2500
2501 @returns a string representing this host's power supply.
2502 'power:battery' when the device has a battery intended for
2503 extended use
2504 'power:AC_primary' when the device has a battery not intended
2505 for extended use (for moving the machine, etc)
2506 'power:AC_only' when the device has no battery at all.
2507 """
2508 psu = self.run(command='mosys psu type', ignore_status=True)
2509 if psu.exit_status:
2510 # The psu command for mosys is not included for all platforms. The
2511 # assumption is that the device will have a battery if the command
2512 # is not found.
2513 return 'power:battery'
2514
2515 psu_str = psu.stdout.strip()
2516 if psu_str == 'unknown':
2517 return None
2518
2519 return 'power:%s' % psu_str
2520
2521
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002522 @label_decorator('storage')
2523 def get_storage(self):
2524 """
2525 Determine the type of boot device for this host.
2526
2527 Determine if the internal device is SCSI or dw_mmc device.
2528 Then check that it is SSD or HDD or eMMC or something else.
2529
2530 @returns a string representing this host's internal device type.
2531 'storage:ssd' when internal device is solid state drive
2532 'storage:hdd' when internal device is hard disk drive
2533 'storage:mmc' when internal device is mmc drive
2534 None When internal device is something else or
2535 when we are unable to determine the type
2536 """
2537 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
2538 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2539 '. /usr/share/misc/chromeos-common.sh;',
2540 'load_base_vars;',
2541 'get_fixed_dst_drive'])
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002542 rootdev = self.run(command=rootdev_cmd, ignore_status=True)
2543 if rootdev.exit_status:
2544 logging.info("Fail to run %s", rootdev_cmd)
2545 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002546 rootdev_str = rootdev.stdout.strip()
2547
2548 if not rootdev_str:
2549 return None
2550
2551 rootdev_base = os.path.basename(rootdev_str)
2552
2553 mmc_pattern = '/dev/mmcblk[0-9]'
2554 if re.match(mmc_pattern, rootdev_str):
2555 # Use type to determine if the internal device is eMMC or somthing
2556 # else. We can assume that MMC is always an internal device.
2557 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002558 type = self.run(command=type_cmd, ignore_status=True)
2559 if type.exit_status:
2560 logging.info("Fail to run %s", type_cmd)
2561 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002562 type_str = type.stdout.strip()
2563
2564 if type_str == 'MMC':
2565 return 'storage:mmc'
2566
2567 scsi_pattern = '/dev/sd[a-z]+'
2568 if re.match(scsi_pattern, rootdev.stdout):
2569 # Read symlink for /sys/block/sd* to determine if the internal
2570 # device is connected via ata or usb.
2571 link_cmd = 'readlink /sys/block/%s' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002572 link = self.run(command=link_cmd, ignore_status=True)
2573 if link.exit_status:
2574 logging.info("Fail to run %s", link_cmd)
2575 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002576 link_str = link.stdout.strip()
2577 if 'usb' in link_str:
2578 return None
2579
2580 # Read rotation to determine if the internal device is ssd or hdd.
2581 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
2582 % rootdev_base)
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002583 rotate = self.run(command=rotate_cmd, ignore_status=True)
2584 if rotate.exit_status:
2585 logging.info("Fail to run %s", rotate_cmd)
2586 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002587 rotate_str = rotate.stdout.strip()
2588
2589 rotate_dict = {'0':'storage:ssd', '1':'storage:hdd'}
2590 return rotate_dict.get(rotate_str)
2591
2592 # All other internal device / error case will always fall here
2593 return None
2594
2595
Dan Shi4e9a2aa2014-03-24 14:28:42 -07002596 @label_decorator('servo')
2597 def get_servo(self):
2598 """Determine if the host has a servo attached.
2599
2600 If the host has a working servo attached, it should have a servo label.
2601
2602 @return: string 'servo' if the host has servo attached. Otherwise,
2603 returns None.
2604 """
2605 return 'servo' if self._servo_host else None
2606
2607
Dan Shi5beba472014-05-28 22:46:07 -07002608 @label_decorator('video_labels')
2609 def get_video_labels(self):
2610 """Run /usr/local/bin/avtest_label_detect to get a list of video labels.
2611
2612 Sample output of avtest_label_detect:
2613 Detected label: hw_video_acc_vp8
2614 Detected label: webcam
2615
2616 @return: A list of labels detected by tool avtest_label_detect.
2617 """
2618 try:
2619 result = self.run('/usr/local/bin/avtest_label_detect').stdout
2620 return re.findall('^Detected label: (\w+)$', result, re.M)
2621 except error.AutoservRunError:
2622 # The tool is not installed.
2623 return []
2624
2625
mussa584b4462014-06-20 15:13:28 -07002626 @label_decorator('video_glitch_detection')
2627 def is_video_glitch_detection_supported(self):
2628 """ Determine if a board under test is supported for video glitch
2629 detection tests.
2630
2631 @return: 'video_glitch_detection' if board is supported, None otherwise.
2632 """
Mussa5b589052015-10-26 17:55:26 -07002633 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
mussa584b4462014-06-20 15:13:28 -07002634
Mussa5b589052015-10-26 17:55:26 -07002635 if board in video_test_constants.SUPPORTED_BOARDS:
2636 return 'video_glitch_detection'
mussa584b4462014-06-20 15:13:28 -07002637
Mussa5b589052015-10-26 17:55:26 -07002638 return None
mussa584b4462014-06-20 15:13:28 -07002639
mussa584b4462014-06-20 15:13:28 -07002640
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002641 @label_decorator('touch_labels')
2642 def get_touch(self):
2643 """
2644 Determine whether board under test has a touchpad or touchscreen.
2645
2646 @return: A list of some combination of 'touchscreen' and 'touchpad',
2647 depending on what is present on the device.
Katherine Threlkeldab83d392015-06-18 16:45:57 -07002648
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002649 """
2650 labels = []
Katherine Threlkeldab83d392015-06-18 16:45:57 -07002651 looking_for = ['touchpad', 'touchscreen']
2652 player = input_playback.InputPlayback()
2653 input_events = self.run('ls /dev/input/event*').stdout.strip().split()
2654 filename = '/tmp/touch_labels'
2655 for event in input_events:
2656 self.run('evtest %s > %s' % (event, filename), timeout=1,
2657 ignore_timeout=True)
2658 properties = self.run('cat %s' % filename).stdout
2659 input_type = player._determine_input_type(properties)
2660 if input_type in looking_for:
2661 labels.append(input_type)
2662 looking_for.remove(input_type)
2663 if len(looking_for) == 0:
2664 break
2665 self.run('rm %s' % filename)
2666
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002667 return labels
2668
Hung-ying Tyana39b0542015-06-30 10:36:42 +08002669
2670 @label_decorator('internal_display')
2671 def has_internal_display(self):
2672 """Determine if the device under test is equipped with an internal
2673 display.
2674
2675 @return: 'internal_display' if one is present; None otherwise.
2676 """
2677 from autotest_lib.client.cros.graphics import graphics_utils
2678 from autotest_lib.client.common_lib import utils as common_utils
2679
2680 def __system_output(cmd):
2681 return self.run(cmd).stdout
2682
2683 def __read_file(remote_path):
2684 return self.run('cat %s' % remote_path).stdout
2685
2686 # Hijack the necessary client functions so that we can take advantage
2687 # of the client lib here.
2688 # FIXME: find a less hacky way than this
2689 original_system_output = utils.system_output
2690 original_read_file = common_utils.read_file
2691 utils.system_output = __system_output
2692 common_utils.read_file = __read_file
2693 try:
2694 return ('internal_display' if graphics_utils.has_internal_display()
2695 else None)
2696 finally:
2697 utils.system_output = original_system_output
2698 common_utils.read_file = original_read_file
2699
2700
Eric Carusoee673ac2015-08-05 17:03:04 -07002701 @label_decorator('lucidsleep')
2702 def has_lucid_sleep_support(self):
2703 """Determine if the device under test has support for lucid sleep.
2704
2705 @return 'lucidsleep' if this board supports lucid sleep; None otherwise
2706 """
2707 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2708 return 'lucidsleep' if board in LUCID_SLEEP_BOARDS else None
2709
2710
Dan Shi85276d42014-04-08 22:11:45 -07002711 def is_boot_from_usb(self):
2712 """Check if DUT is boot from USB.
2713
2714 @return: True if DUT is boot from usb.
2715 """
2716 device = self.run('rootdev -s -d').stdout.strip()
2717 removable = int(self.run('cat /sys/block/%s/removable' %
2718 os.path.basename(device)).stdout.strip())
2719 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002720
2721
2722 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002723 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002724
2725 @param key: meminfo requested
2726
2727 @return the memory value as a string
2728
2729 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002730 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2731 logging.debug('%s', meminfo)
2732 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002733
2734
2735 def get_board_type(self):
2736 """
2737 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002738 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2739
2740 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002741 """
Danny Chan471a8d12015-08-18 14:57:41 -07002742 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2743 ignore_status=True).stdout
2744 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002745 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002746 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002747
2748
2749 def get_os_type(self):
2750 return 'cros'
Simran Basia5522a32015-10-06 11:01:24 -07002751
2752
2753 def enable_adb_testing(self):
2754 """Mark this host as an adb tester."""
Dan Shia2872172015-10-31 01:16:51 -07002755 self.run('touch %s' % constants.ANDROID_TESTER_FILEFLAG)