blob: 244a1fc4f408d5437be7236bcac76c6e2eea08a6 [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
mussa584b4462014-06-20 15:13:28 -07005import ConfigParser
Aviv Keshet74c89a92013-02-04 15:18:30 -08006import functools
Christopher Wiley0ed712b2013-04-09 15:25:12 -07007import httplib
J. Richard Barnette1d78b012012-05-15 13:56:30 -07008import logging
Dan Shi0f466e82013-02-22 15:44:58 -08009import os
Simran Basid5e5e272012-09-24 15:23:59 -070010import re
Christopher Wileyd78249a2013-03-01 13:05:31 -080011import socket
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070012import time
J. Richard Barnette1d78b012012-05-15 13:56:30 -070013import xmlrpclib
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070014
mussa584b4462014-06-20 15:13:28 -070015import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070016from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070017from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080018from autotest_lib.client.common_lib import error
19from autotest_lib.client.common_lib import global_config
Dan Shi549fb822015-03-24 18:01:11 -070020from autotest_lib.client.common_lib import lsbrelease_utils
J. Richard Barnette45e93de2012-04-11 17:24:15 -070021from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette03a0c132012-11-05 12:40:35 -080022from autotest_lib.client.common_lib.cros import dev_server
Christopher Wileyd78249a2013-03-01 13:05:31 -080023from autotest_lib.client.common_lib.cros import retry
Gabe Blackb72f4fb2015-01-20 16:47:13 -080024from autotest_lib.client.common_lib.cros.graphite import autotest_es
Gabe Black1e1c41b2015-02-04 23:55:15 -080025from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000026from autotest_lib.client.cros import constants as client_constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080027from autotest_lib.client.cros import cros_ui
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +080028from autotest_lib.client.cros.audio import cras_utils
Katherine Threlkeldab83d392015-06-18 16:45:57 -070029from autotest_lib.client.cros.input_playback import input_playback
MK Ryu35d661e2014-09-25 17:44:10 -070030from autotest_lib.server import autoserv_parser
31from autotest_lib.server import autotest
32from autotest_lib.server import constants
33from autotest_lib.server import crashcollect
Dan Shia1ecd5c2013-06-06 11:21:31 -070034from autotest_lib.server import utils as server_utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070035from autotest_lib.server.cros import provision
Scott Zawalski89c44dd2013-02-26 09:28:02 -050036from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070037from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
Dan Shi9cb0eec2014-06-03 09:04:50 -070038from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
Fang Deng96667ca2013-08-01 17:46:18 -070039from autotest_lib.server.hosts import abstract_ssh
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +080040from autotest_lib.server.hosts import chameleon_host
Fang Deng5d518f42013-08-02 14:04:32 -070041from autotest_lib.server.hosts import servo_host
Simran Basidcff4252012-11-20 16:13:20 -080042from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070043
44
beeps32a63082013-08-22 14:02:29 -070045try:
46 import jsonrpclib
47except ImportError:
48 jsonrpclib = None
Fang Deng96667ca2013-08-01 17:46:18 -070049
Fang Dengd1c2b732013-08-20 12:59:46 -070050
Dan Shib8540a52015-07-16 14:18:23 -070051CONFIG = global_config.global_config
52
Eric Carusoee673ac2015-08-05 17:03:04 -070053LUCID_SLEEP_BOARDS = ['samus', 'lulu']
54
Dan Shid07ee2e2015-09-24 14:49:25 -070055# A file to indicate provision failure and require Repair job to powerwash the
56# dut.
57PROVISION_FAILED = '/var/tmp/provision_failed'
58
beepsc87ff602013-07-31 21:53:00 -070059class FactoryImageCheckerException(error.AutoservError):
60 """Exception raised when an image is a factory image."""
61 pass
62
63
Fang Deng0ca40e22013-08-27 17:47:44 -070064class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070065 """Chromium OS specific subclass of Host."""
66
67 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -050068 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070069
Richard Barnette03a0c132012-11-05 12:40:35 -080070 # Timeout values (in seconds) associated with various Chrome OS
71 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070072 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080073 # In general, a good rule of thumb is that the timeout can be up
74 # to twice the typical measured value on the slowest platform.
75 # The times here have not necessarily been empirically tested to
76 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070077 #
78 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080079 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
80 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080081 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070082 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080083 # other things, this must account for the 30 second dev-mode
J. Richard Barnette417cc792015-10-01 09:56:36 -070084 # screen delay, time to start the network on the DUT, and the
85 # ssh timeout of 120 seconds.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070086 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080087 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080088 # network.
beepsf079cfb2013-09-18 17:49:51 -070089 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080090 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
91 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070092
93 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080094 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +080095 SHUTDOWN_TIMEOUT = 10
J. Richard Barnette417cc792015-10-01 09:56:36 -070096 BOOT_TIMEOUT = 150
J. Richard Barnette5bab5f52015-08-03 13:14:38 -070097 USB_BOOT_TIMEOUT = 300
J. Richard Barnette7817b052014-08-28 09:47:29 -070098 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -080099 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -0700100
Dan Shica503482015-03-30 17:23:25 -0700101 # Minimum OS version that supports server side packaging. Older builds may
102 # not have server side package built or with Autotest code change to support
103 # server-side packaging.
Dan Shib8540a52015-07-16 14:18:23 -0700104 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value(
Dan Shiced09e42015-04-17 16:09:34 -0700105 'AUTOSERV', 'min_version_support_ssp', type=int)
Dan Shica503482015-03-30 17:23:25 -0700106
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800107 # REBOOT_TIMEOUT: How long to wait for a reboot.
108 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700109 # We have a long timeout to ensure we don't flakily fail due to other
110 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700111 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
112 # return from reboot' bug is solved.
113 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700114
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800115 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
116 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
117 _USB_POWER_TIMEOUT = 5
118 _POWER_CYCLE_TIMEOUT = 10
119
beeps32a63082013-08-22 14:02:29 -0700120 _RPC_PROXY_URL = 'http://localhost:%d'
Christopher Wileydd181852013-10-10 19:56:58 -0700121 _RPC_SHUTDOWN_POLLING_PERIOD_SECONDS = 2
Peter Qiu4410db72014-06-05 10:32:41 -0700122 # Set shutdown timeout to account for the time for restarting the UI.
123 _RPC_SHUTDOWN_TIMEOUT_SECONDS = cros_ui.RESTART_UI_TIMEOUT
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800124
Dan Shib8540a52015-07-16 14:18:23 -0700125 _RPM_RECOVERY_BOARDS = CONFIG.get_config_value('CROS',
Richard Barnette82c35912012-11-20 10:09:10 -0800126 'rpm_recovery_boards', type=str).split(',')
127
128 _MAX_POWER_CYCLE_ATTEMPTS = 6
129 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Fang Dengdeba14f2014-11-14 11:54:09 -0800130 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
131 '-host(\d+)')
Katherine Threlkeldab83d392015-06-18 16:45:57 -0700132 _LIGHTSENSOR_FILES = [ "in_illuminance0_input",
133 "in_illuminance_input",
134 "in_illuminance0_raw",
135 "in_illuminance_raw",
136 "illuminance0_input"]
Richard Barnette82c35912012-11-20 10:09:10 -0800137 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
138 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800139 _DETECTABLE_LABELS = []
Kevin Cheng3a4a57a2015-09-30 12:09:50 -0700140 label_decorator = functools.partial(server_utils.add_label_detector,
141 _LABEL_FUNCTIONS,
Aviv Keshet74c89a92013-02-04 15:18:30 -0800142 _DETECTABLE_LABELS)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700143
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800144 # Constants used in ping_wait_up() and ping_wait_down().
145 #
146 # _PING_WAIT_COUNT is the approximate number of polling
147 # cycles to use when waiting for a host state change.
148 #
149 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
150 # for arguments to the internal _ping_wait_for_status()
151 # method.
152 _PING_WAIT_COUNT = 40
153 _PING_STATUS_DOWN = False
154 _PING_STATUS_UP = True
155
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800156 # Allowed values for the power_method argument.
157
158 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
159 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
160 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
161 POWER_CONTROL_RPM = 'RPM'
162 POWER_CONTROL_SERVO = 'servoj10'
163 POWER_CONTROL_MANUAL = 'manual'
164
165 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
166 POWER_CONTROL_SERVO,
167 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800168
Simran Basi5e6339a2013-03-21 11:34:32 -0700169 _RPM_OUTLET_CHANGED = 'outlet_changed'
170
Dan Shi9cb0eec2014-06-03 09:04:50 -0700171 # URL pattern to download firmware image.
Dan Shib8540a52015-07-16 14:18:23 -0700172 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value(
Dan Shi9cb0eec2014-06-03 09:04:50 -0700173 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700174
MK Ryu35d661e2014-09-25 17:44:10 -0700175 # File that has a list of directories to be collected
176 _LOGS_TO_COLLECT_FILE = os.path.join(
177 common.client_dir, 'common_lib', 'logs_to_collect')
178
179 # Prefix of logging message w.r.t. crash collection
180 _CRASHLOGS_PREFIX = 'collect_crashlogs'
181
182 # Time duration waiting for host up/down check
183 _CHECK_HOST_UP_TIMEOUT_SECS = 15
184
185 # A command that interacts with kernel and hardware (e.g., rm, mkdir, etc)
186 # might not be completely done deep through the hardware when the machine
187 # is powered down right after the command returns.
188 # We should wait for a few seconds to make them done. Finger crossed.
189 _SAFE_WAIT_SECS = 10
190
191
J. Richard Barnette964fba02012-10-24 17:34:29 -0700192 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800193 def check_host(host, timeout=10):
194 """
195 Check if the given host is a chrome-os host.
196
197 @param host: An ssh host representing a device.
198 @param timeout: The timeout for the run command.
199
200 @return: True if the host device is chromeos.
201
beeps46dadc92013-11-07 14:07:10 -0800202 """
203 try:
Simran Basi933c8af2015-04-29 14:05:07 -0700204 result = host.run(
205 'grep -q CHROMEOS /etc/lsb-release && '
206 '! test -f /mnt/stateful_partition/.android_tester && '
207 '! grep -q moblab /etc/lsb-release',
208 ignore_status=True, timeout=timeout)
beeps46dadc92013-11-07 14:07:10 -0800209 except (error.AutoservRunError, error.AutoservSSHTimeout):
210 return False
211 return result.exit_status == 0
212
213
214 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800215 def _extract_arguments(args_dict, key_subset):
216 """Extract options from `args_dict` and return a subset result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800217
218 Take the provided dictionary of argument options and return
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800219 a subset that represent standard arguments needed to construct
220 a test-assistant object (chameleon or servo) for a host. The
221 intent is to provide standard argument processing from
Christopher Wiley644ef3e2015-05-15 13:14:14 -0700222 CrosHost for tests that require a test-assistant board
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800223 to operate.
224
225 @param args_dict Dictionary from which to extract the arguments.
226 @param key_subset Tuple of keys to extract from the args_dict, e.g.
227 ('servo_host', 'servo_port').
228 """
229 result = {}
230 for arg in key_subset:
231 if arg in args_dict:
232 result[arg] = args_dict[arg]
233 return result
234
235
236 @staticmethod
237 def get_chameleon_arguments(args_dict):
238 """Extract chameleon options from `args_dict` and return the result.
239
240 Recommended usage:
241 ~~~~~~~~
242 args_dict = utils.args_to_dict(args)
243 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
244 host = hosts.create_host(machine, chameleon_args=chameleon_args)
245 ~~~~~~~~
246
247 @param args_dict Dictionary from which to extract the chameleon
248 arguments.
249 """
250 return CrosHost._extract_arguments(
251 args_dict, ('chameleon_host', 'chameleon_port'))
252
253
254 @staticmethod
255 def get_servo_arguments(args_dict):
256 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800257
258 Recommended usage:
259 ~~~~~~~~
260 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700261 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800262 host = hosts.create_host(machine, servo_args=servo_args)
263 ~~~~~~~~
264
265 @param args_dict Dictionary from which to extract the servo
266 arguments.
267 """
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800268 return CrosHost._extract_arguments(
269 args_dict, ('servo_host', 'servo_port'))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700270
J. Richard Barnette964fba02012-10-24 17:34:29 -0700271
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800272 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
Fang Denge545abb2014-12-30 18:43:47 -0800273 try_lab_servo=False, ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700274 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800275 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700276
Fang Denge545abb2014-12-30 18:43:47 -0800277 This method will attempt to create the test-assistant object
278 (chameleon/servo) when it is needed by the test. Check
279 the docstring of chameleon_host.create_chameleon_host and
280 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700281
Fang Denge545abb2014-12-30 18:43:47 -0800282 @param hostname: Hostname of the dut.
283 @param chameleon_args: A dictionary that contains args for creating
284 a ChameleonHost. See chameleon_host for details.
285 @param servo_args: A dictionary that contains args for creating
286 a ServoHost object. See servo_host for details.
287 @param try_lab_servo: Boolean, False indicates that ServoHost should
288 not be created for a device in Cros test lab.
289 See servo_host for details.
290 @param ssh_verbosity_flag: String, to pass to the ssh command to control
291 verbosity.
292 @param ssh_options: String, other ssh options to pass to the ssh
293 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700294 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700295 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700296 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700297 # self.env is a dictionary of environment variable settings
298 # to be exported for commands run on the host.
299 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
300 # errors that might happen.
301 self.env['LIBC_FATAL_STDERR_'] = '1'
beeps32a63082013-08-22 14:02:29 -0700302 self._rpc_proxy_map = {}
Fang Dengd1c2b732013-08-20 12:59:46 -0700303 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700304 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700305 # TODO(fdeng): We need to simplify the
306 # process of servo and servo_host initialization.
307 # crbug.com/298432
Fang Denge545abb2014-12-30 18:43:47 -0800308 self._servo_host = servo_host.create_servo_host(
309 dut=self.hostname, servo_args=servo_args,
310 try_lab_servo=try_lab_servo)
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800311 # TODO(waihong): Do the simplication on Chameleon too.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800312 self._chameleon_host = chameleon_host.create_chameleon_host(
313 dut=self.hostname, chameleon_args=chameleon_args)
314
Dan Shi4d478522014-02-14 13:46:32 -0800315 if self._servo_host is not None:
316 self.servo = self._servo_host.get_servo()
317 else:
318 self.servo = None
319
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800320 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800321 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800322 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800323 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700324
325
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500326 def get_repair_image_name(self):
327 """Generate a image_name from variables in the global config.
328
329 @returns a str of $board-version/$BUILD.
330
331 """
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500332 board = self._get_board_from_afe()
333 if board is None:
334 raise error.AutoservError('DUT has no board attribute, '
335 'cannot be repaired.')
Dan Shi6964fa52014-12-18 11:04:27 -0800336 stable_version = self._AFE.run('get_stable_version', board=board)
Dan Shib8540a52015-07-16 14:18:23 -0700337 build_pattern = CONFIG.get_config_value(
Dan Shi6964fa52014-12-18 11:04:27 -0800338 'CROS', 'stable_build_pattern')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500339 return build_pattern % (board, stable_version)
340
341
Scott Zawalski62bacae2013-03-05 10:40:32 -0500342 def _host_in_AFE(self):
343 """Check if the host is an object the AFE knows.
344
345 @returns the host object.
346 """
347 return self._AFE.get_hosts(hostname=self.hostname)
348
349
Chris Sosab76e0ee2013-05-22 16:55:41 -0700350 def lookup_job_repo_url(self):
351 """Looks up the job_repo_url for the host.
352
353 @returns job_repo_url from AFE or None if not found.
354
355 @raises KeyError if the host does not have a job_repo_url
356 """
Chris Sosab76e0ee2013-05-22 16:55:41 -0700357 hosts = self._AFE.get_hosts(hostname=self.hostname)
beepsb5efc532013-06-04 11:29:34 -0700358 if hosts and ds_constants.JOB_REPO_URL in hosts[0].attributes:
359 return hosts[0].attributes[ds_constants.JOB_REPO_URL]
J. Richard Barnette85d0aac2015-08-20 10:34:39 -0700360 else:
361 return None
Chris Sosab76e0ee2013-05-22 16:55:41 -0700362
363
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500364 def clear_cros_version_labels_and_job_repo_url(self):
365 """Clear cros_version labels and host attribute job_repo_url."""
Scott Zawalski62bacae2013-03-05 10:40:32 -0500366 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400367 return
368
Scott Zawalski62bacae2013-03-05 10:40:32 -0500369 host_list = [self.hostname]
370 labels = self._AFE.get_labels(
371 name__startswith=ds_constants.VERSION_PREFIX,
372 host__hostname=self.hostname)
Dan Shi0f466e82013-02-22 15:44:58 -0800373
Scott Zawalski62bacae2013-03-05 10:40:32 -0500374 for label in labels:
375 label.remove_hosts(hosts=host_list)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500376
beepscb6f1e22013-06-28 19:14:10 -0700377 self.update_job_repo_url(None, None)
378
379
380 def update_job_repo_url(self, devserver_url, image_name):
381 """
382 Updates the job_repo_url host attribute and asserts it's value.
383
384 @param devserver_url: The devserver to use in the job_repo_url.
385 @param image_name: The name of the image to use in the job_repo_url.
386
387 @raises AutoservError: If we failed to update the job_repo_url.
388 """
389 repo_url = None
390 if devserver_url and image_name:
391 repo_url = tools.get_package_url(devserver_url, image_name)
392 self._AFE.set_host_attribute(ds_constants.JOB_REPO_URL, repo_url,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500393 hostname=self.hostname)
beepscb6f1e22013-06-28 19:14:10 -0700394 if self.lookup_job_repo_url() != repo_url:
395 raise error.AutoservError('Failed to update job_repo_url with %s, '
396 'host %s' % (repo_url, self.hostname))
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500397
398
Dan Shie9309262013-06-19 22:50:21 -0700399 def add_cros_version_labels_and_job_repo_url(self, image_name):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400400 """Add cros_version labels and host attribute job_repo_url.
401
402 @param image_name: The name of the image e.g.
403 lumpy-release/R27-3837.0.0
Dan Shi7458bf62013-06-10 12:50:16 -0700404
Scott Zawalskieadbf702013-03-14 09:23:06 -0400405 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500406 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400407 return
Scott Zawalski62bacae2013-03-05 10:40:32 -0500408
Scott Zawalskieadbf702013-03-14 09:23:06 -0400409 cros_label = '%s%s' % (ds_constants.VERSION_PREFIX, image_name)
Dan Shie9309262013-06-19 22:50:21 -0700410 devserver_url = dev_server.ImageServer.resolve(image_name).url()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500411
MK Ryufb5e3a82015-07-01 12:21:20 -0700412 self._AFE.run('label_add_hosts', id=cros_label, hosts=[self.hostname])
beepscb6f1e22013-06-28 19:14:10 -0700413 self.update_job_repo_url(devserver_url, image_name)
414
415
beepsdae65fd2013-07-26 16:24:41 -0700416 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700417 """
418 Make sure job_repo_url of this host is valid.
419
joychen03eaad92013-06-26 09:55:21 -0700420 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700421 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
422 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
423 download and extract it. If the devserver embedded in the url is
424 unresponsive, update the job_repo_url of the host after staging it on
425 another devserver.
426
427 @param job_repo_url: A url pointing to the devserver where the autotest
428 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700429 @param tag: The tag from the server job, in the format
430 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700431
432 @raises DevServerException: If we could not resolve a devserver.
433 @raises AutoservError: If we're unable to save the new job_repo_url as
434 a result of choosing a new devserver because the old one failed to
435 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700436 @raises urllib2.URLError: If the devserver embedded in job_repo_url
437 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700438 """
439 job_repo_url = self.lookup_job_repo_url()
440 if not job_repo_url:
441 logging.warning('No job repo url set on host %s', self.hostname)
442 return
443
444 logging.info('Verifying job repo url %s', job_repo_url)
445 devserver_url, image_name = tools.get_devserver_build_from_package_url(
446 job_repo_url)
447
beeps0c865032013-07-30 11:37:06 -0700448 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700449
450 logging.info('Staging autotest artifacts for %s on devserver %s',
451 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700452
453 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700454 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700455 stage_time = time.time() - start_time
456
457 # Record how much of the verification time comes from a devserver
458 # restage. If we're doing things right we should not see multiple
459 # devservers for a given board/build/branch path.
460 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800461 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700462 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800463 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700464 pass
465 else:
beeps0c865032013-07-30 11:37:06 -0700466 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700467 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700468 stats_key = {
469 'board': board,
470 'build_type': build_type,
471 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700472 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700473 }
Gabe Black1e1c41b2015-02-04 23:55:15 -0800474 autotest_stats.Gauge('verify_job_repo_url').send(
beeps687243d2013-07-18 15:29:27 -0700475 '%(board)s.%(build_type)s.%(branch)s.%(devserver)s' % stats_key,
476 stage_time)
beepscb6f1e22013-06-28 19:14:10 -0700477
Scott Zawalskieadbf702013-03-14 09:23:06 -0400478
Dan Shicf4d2032015-03-12 15:04:21 -0700479 def stage_server_side_package(self, image=None):
480 """Stage autotest server-side package on devserver.
481
482 @param image: Full path of an OS image to install or a build name.
483
484 @return: A url to the autotest server-side package.
485 """
486 if image:
487 image_name = tools.get_build_from_image(image)
488 if not image_name:
489 raise error.AutoservError(
490 'Failed to parse build name from %s' % image)
491 ds = dev_server.ImageServer.resolve(image_name)
492 else:
493 job_repo_url = self.lookup_job_repo_url()
494 if job_repo_url:
495 devserver_url, image_name = (
496 tools.get_devserver_build_from_package_url(job_repo_url))
497 ds = dev_server.ImageServer(devserver_url)
498 else:
499 labels = self._AFE.get_labels(
500 name__startswith=ds_constants.VERSION_PREFIX,
501 host__hostname=self.hostname)
502 if not labels:
503 raise error.AutoservError(
504 'Failed to stage server-side package. The host has '
505 'no job_report_url attribute or version label.')
506 image_name = labels[0].name[len(ds_constants.VERSION_PREFIX):]
507 ds = dev_server.ImageServer.resolve(image_name)
Dan Shica503482015-03-30 17:23:25 -0700508
509 # Get the OS version of the build, for any build older than
510 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
511 match = re.match('.*/R\d+-(\d+)\.', image_name)
512 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
513 logging.warn('Build %s is older than %s. Server side packaging is '
514 'disabled.', image_name, self.MIN_VERSION_SUPPORT_SSP)
515 return None
516
Dan Shicf4d2032015-03-12 15:04:21 -0700517 ds.stage_artifacts(image_name, ['autotest_server_package'])
518 return '%s/static/%s/%s' % (ds.url(), image_name,
519 'autotest_server_package.tar.bz2')
520
521
Dan Shi0f466e82013-02-22 15:44:58 -0800522 def _try_stateful_update(self, update_url, force_update, updater):
523 """Try to use stateful update to initialize DUT.
524
525 When DUT is already running the same version that machine_install
526 tries to install, stateful update is a much faster way to clean up
527 the DUT for testing, compared to a full reimage. It is implemeted
528 by calling autoupdater.run_update, but skipping updating root, as
529 updating the kernel is time consuming and not necessary.
530
531 @param update_url: url of the image.
532 @param force_update: Set to True to update the image even if the DUT
533 is running the same version.
534 @param updater: ChromiumOSUpdater instance used to update the DUT.
535 @returns: True if the DUT was updated with stateful update.
536
537 """
J. Richard Barnette3f731032014-04-07 17:42:59 -0700538 # TODO(jrbarnette): Yes, I hate this re.match() test case.
539 # It's better than the alternative: see crbug.com/360944.
540 image_name = autoupdater.url_to_image_name(update_url)
541 release_pattern = r'^.*-release/R[0-9]+-[0-9]+\.[0-9]+\.0$'
542 if not re.match(release_pattern, image_name):
543 return False
Dan Shi0f466e82013-02-22 15:44:58 -0800544 if not updater.check_version():
545 return False
546 if not force_update:
547 logging.info('Canceling stateful update because the new and '
548 'old versions are the same.')
549 return False
550 # Following folders should be rebuilt after stateful update.
551 # A test file is used to confirm each folder gets rebuilt after
552 # the stateful update.
553 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
554 test_file = '.test_file_to_be_deleted'
555 for folder in folders_to_check:
556 touch_path = os.path.join(folder, test_file)
557 self.run('touch %s' % touch_path)
558
Chris Sosae92399e2015-04-24 11:32:59 -0700559 updater.run_update(update_root=False)
Dan Shi0f466e82013-02-22 15:44:58 -0800560
561 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700562 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800563 check_file_cmd = 'test -f %s; echo $?'
564 for folder in folders_to_check:
565 test_file_path = os.path.join(folder, test_file)
566 result = self.run(check_file_cmd % test_file_path,
567 ignore_status=True)
568 if result.exit_status == 1:
569 return False
570 return True
571
572
J. Richard Barnette7275b612013-06-04 18:13:11 -0700573 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800574 """After the DUT is updated, confirm machine_install succeeded.
575
576 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700577 @param expected_kernel: kernel expected to be active after reboot,
578 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800579
580 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700581 # Touch the lab machine file to leave a marker that
582 # distinguishes this image from other test images.
583 # Afterwards, we must re-run the autoreboot script because
584 # it depends on the _LAB_MACHINE_FILE.
Dan Shi0f466e82013-02-22 15:44:58 -0800585 self.run('touch %s' % self._LAB_MACHINE_FILE)
Dan Shi0f466e82013-02-22 15:44:58 -0800586 self.run('start autoreboot')
Chris Sosa65425082013-10-16 13:26:22 -0700587 updater.verify_boot_expectations(
588 expected_kernel, rollback_message=
589 'Build %s failed to boot on %s; system rolled back to previous'
590 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700591 # Check that we've got the build we meant to install.
592 if not updater.check_version_to_confirm_install():
593 raise autoupdater.ChromiumOSError(
594 'Failed to update %s to build %s; found build '
595 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700596 updater.update_version,
Dan Shi0942b1d2015-03-31 11:07:00 -0700597 self.get_release_version()))
Dan Shi0f466e82013-02-22 15:44:58 -0800598
Chris Sosae92399e2015-04-24 11:32:59 -0700599 logging.debug('Cleaning up old autotest directories.')
600 try:
601 installed_autodir = autotest.Autotest.get_installed_autodir(self)
602 self.run('rm -rf ' + installed_autodir)
603 except autotest.AutodirNotFoundError:
604 logging.debug('No autotest installed directory found.')
605
Dan Shi0f466e82013-02-22 15:44:58 -0800606
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700607 def _stage_image_for_update(self, image_name=None):
Chris Sosae92399e2015-04-24 11:32:59 -0700608 """Stage a build on a devserver and return the update_url and devserver.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400609
610 @param image_name: a name like lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700611 @returns a tuple with an update URL like:
Scott Zawalskieadbf702013-03-14 09:23:06 -0400612 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700613 and the devserver instance.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400614 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700615 if not image_name:
616 image_name = self.get_repair_image_name()
Chris Sosae92399e2015-04-24 11:32:59 -0700617
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700618 logging.info('Staging build for AU: %s', image_name)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400619 devserver = dev_server.ImageServer.resolve(image_name)
620 devserver.trigger_download(image_name, synchronous=False)
Chris Sosae92399e2015-04-24 11:32:59 -0700621 return (tools.image_url_pattern() % (devserver.url(), image_name),
622 devserver)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400623
624
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700625 def stage_image_for_servo(self, image_name=None):
626 """Stage a build on a devserver and return the update_url.
627
628 @param image_name: a name like lumpy-release/R27-3837.0.0
629 @returns an update URL like:
630 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
631 """
632 if not image_name:
633 image_name = self.get_repair_image_name()
634 logging.info('Staging build for servo install: %s', image_name)
635 devserver = dev_server.ImageServer.resolve(image_name)
636 devserver.stage_artifacts(image_name, ['test_image'])
637 return devserver.get_test_image_url(image_name)
638
639
beepse539be02013-07-31 21:57:39 -0700640 def stage_factory_image_for_servo(self, image_name):
641 """Stage a build on a devserver and return the update_url.
642
643 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700644
beepse539be02013-07-31 21:57:39 -0700645 @return: An update URL, eg:
646 http://<devserver>/static/canary-channel/\
647 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700648
649 @raises: ValueError if the factory artifact name is missing from
650 the config.
651
beepse539be02013-07-31 21:57:39 -0700652 """
653 if not image_name:
654 logging.error('Need an image_name to stage a factory image.')
655 return
656
Dan Shib8540a52015-07-16 14:18:23 -0700657 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700658 'CROS', 'factory_artifact', type=str, default='')
659 if not factory_artifact:
660 raise ValueError('Cannot retrieve the factory artifact name from '
661 'autotest config, and hence cannot stage factory '
662 'artifacts.')
663
beepse539be02013-07-31 21:57:39 -0700664 logging.info('Staging build for servo install: %s', image_name)
665 devserver = dev_server.ImageServer.resolve(image_name)
666 devserver.stage_artifacts(
667 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700668 [factory_artifact],
669 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700670
671 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
672
673
Chris Sosaa3ac2152012-05-23 22:23:13 -0700674 def machine_install(self, update_url=None, force_update=False,
Richard Barnette0b023a72015-04-24 16:07:30 +0000675 local_devserver=False, repair=False,
676 force_full_update=False):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500677 """Install the DUT.
678
Dan Shi0f466e82013-02-22 15:44:58 -0800679 Use stateful update if the DUT is already running the same build.
680 Stateful update does not update kernel and tends to run much faster
681 than a full reimage. If the DUT is running a different build, or it
682 failed to do a stateful update, full update, including kernel update,
683 will be applied to the DUT.
684
Scott Zawalskieadbf702013-03-14 09:23:06 -0400685 Once a host enters machine_install its cros_version label will be
686 removed as well as its host attribute job_repo_url (used for
687 package install).
688
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500689 @param update_url: The url to use for the update
690 pattern: http://$devserver:###/update/$build
691 If update_url is None and repair is True we will install the
Dan Shi6964fa52014-12-18 11:04:27 -0800692 stable image listed in afe_stable_versions table. If the table
693 is not setup, global_config value under CROS.stable_cros_version
694 will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500695 @param force_update: Force an update even if the version installed
696 is the same. Default:False
Christopher Wiley6a4ff932015-05-15 14:00:47 -0700697 @param local_devserver: Used by test_that to allow people to
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500698 use their local devserver. Default: False
Chris Sosae92399e2015-04-24 11:32:59 -0700699 @param repair: Forces update to repair image. Implies force_update.
Fang Deng3d3b9272014-12-22 12:20:28 -0800700 @param force_full_update: If True, do not attempt to run stateful
701 update, force a full reimage. If False, try stateful update
702 first when the dut is already installed with the same version.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500703 @raises autoupdater.ChromiumOSError
704
705 """
Chris Sosae92399e2015-04-24 11:32:59 -0700706 devserver = None
Richard Barnette0b023a72015-04-24 16:07:30 +0000707 if repair:
Chris Sosae92399e2015-04-24 11:32:59 -0700708 update_url, devserver = self._stage_image_for_update()
Richard Barnette0b023a72015-04-24 16:07:30 +0000709 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800710
Chris Sosae92399e2015-04-24 11:32:59 -0700711 if not update_url and not self._parser.options.image:
712 raise error.AutoservError(
Dan Shid07ee2e2015-09-24 14:49:25 -0700713 'There is no update URL, nor a method to get one.')
Chris Sosae92399e2015-04-24 11:32:59 -0700714
715 if not update_url and self._parser.options.image:
716 # This is the base case where we have no given update URL i.e.
717 # dynamic suites logic etc. This is the most flexible case where we
718 # can serve an update from any of our fleet of devservers.
719 requested_build = self._parser.options.image
720 if not requested_build.startswith('http://'):
721 logging.debug('Update will be staged for this installation')
722 update_url, devserver = self._stage_image_for_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700723 requested_build)
Chris Sosae92399e2015-04-24 11:32:59 -0700724 else:
725 update_url = requested_build
726
727 logging.debug('Update URL is %s', update_url)
728
Scott Zawalskieadbf702013-03-14 09:23:06 -0400729 # Remove cros-version and job_repo_url host attribute from host.
730 self.clear_cros_version_labels_and_job_repo_url()
Chris Sosae92399e2015-04-24 11:32:59 -0700731
Dan Shid07ee2e2015-09-24 14:49:25 -0700732 # Create a file to indicate if provision fails. The file will be removed
733 # by stateful update or full install.
734 self.run('touch %s' % PROVISION_FAILED)
735
Chris Sosae92399e2015-04-24 11:32:59 -0700736 update_complete = False
737 updater = autoupdater.ChromiumOSUpdater(
738 update_url, host=self, local_devserver=local_devserver)
Fang Deng3d3b9272014-12-22 12:20:28 -0800739 if not force_full_update:
740 try:
Chris Sosae92399e2015-04-24 11:32:59 -0700741 # If the DUT is already running the same build, try stateful
742 # update first as it's much quicker than a full re-image.
743 update_complete = self._try_stateful_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700744 update_url, force_update, updater)
Fang Deng3d3b9272014-12-22 12:20:28 -0800745 except Exception as e:
746 logging.exception(e)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700747
Dan Shi0f466e82013-02-22 15:44:58 -0800748 inactive_kernel = None
Chris Sosae92399e2015-04-24 11:32:59 -0700749 if update_complete or (not force_update and updater.check_version()):
750 logging.info('Install complete without full update')
751 else:
752 logging.info('DUT requires full update.')
753 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
754 num_of_attempts = provision.FLAKY_DEVSERVER_ATTEMPTS
Chris Sosab7612bc2013-03-21 10:32:37 -0700755
Chris Sosae92399e2015-04-24 11:32:59 -0700756 while num_of_attempts > 0:
757 num_of_attempts -= 1
758 try:
759 updater.run_update()
760 except Exception:
761 logging.warn('Autoupdate did not complete.')
762 # Do additional check for the devserver health. Ideally,
763 # the autoupdater.py could raise an exception when it
764 # detected network flake but that would require
765 # instrumenting the update engine and parsing it log.
766 if (num_of_attempts <= 0 or
767 devserver is None or
768 dev_server.DevServer.devserver_healthy(
769 devserver.url())):
Dan Shid07ee2e2015-09-24 14:49:25 -0700770 raise
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700771
Chris Sosae92399e2015-04-24 11:32:59 -0700772 logging.warn('Devserver looks unhealthy. Trying another')
773 update_url, devserver = self._stage_image_for_update(
774 requested_build)
775 logging.debug('New Update URL is %s', update_url)
776 updater = autoupdater.ChromiumOSUpdater(
777 update_url, host=self,
778 local_devserver=local_devserver)
779 else:
780 break
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700781
Chris Sosae92399e2015-04-24 11:32:59 -0700782 # Give it some time in case of IO issues.
783 time.sleep(10)
Dan Shi5699ac22014-12-19 10:55:49 -0800784
Chris Sosae92399e2015-04-24 11:32:59 -0700785 # Figure out active and inactive kernel.
786 active_kernel, inactive_kernel = updater.get_kernel_state()
Simran Basi13fa1ba2013-03-04 10:56:47 -0800787
Chris Sosae92399e2015-04-24 11:32:59 -0700788 # Ensure inactive kernel has higher priority than active.
789 if (updater.get_kernel_priority(inactive_kernel)
790 < updater.get_kernel_priority(active_kernel)):
791 raise autoupdater.ChromiumOSError(
792 'Update failed. The priority of the inactive kernel'
793 ' partition is less than that of the active kernel'
794 ' partition.')
795
796 # Updater has returned successfully; reboot the host.
797 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
798
799 self._post_update_processing(updater, inactive_kernel)
800 self.add_cros_version_labels_and_job_repo_url(
801 autoupdater.url_to_image_name(update_url))
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700802
803
Dan Shi9cb0eec2014-06-03 09:04:50 -0700804 def _clear_fw_version_labels(self):
805 """Clear firmware version labels from the machine."""
806 labels = self._AFE.get_labels(
Dan Shi0723bf52015-06-24 10:52:38 -0700807 name__startswith=provision.FW_RW_VERSION_PREFIX,
Dan Shi9cb0eec2014-06-03 09:04:50 -0700808 host__hostname=self.hostname)
809 for label in labels:
810 label.remove_hosts(hosts=[self.hostname])
811
812
813 def _add_fw_version_label(self, build):
814 """Add firmware version label to the machine.
815
816 @param build: Build of firmware.
817
818 """
819 fw_label = provision.fw_version_to_label(build)
MK Ryu73be9862015-07-06 12:25:00 -0700820 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Dan Shi9cb0eec2014-06-03 09:04:50 -0700821
822
823 def firmware_install(self, build=None):
824 """Install firmware to the DUT.
825
826 Use stateful update if the DUT is already running the same build.
827 Stateful update does not update kernel and tends to run much faster
828 than a full reimage. If the DUT is running a different build, or it
829 failed to do a stateful update, full update, including kernel update,
830 will be applied to the DUT.
831
832 Once a host enters firmware_install its fw_version label will be
833 removed. After the firmware is updated successfully, a new fw_version
834 label will be added to the host.
835
836 @param build: The build version to which we want to provision the
837 firmware of the machine,
838 e.g. 'link-firmware/R22-2695.1.144'.
839
840 TODO(dshi): After bug 381718 is fixed, update here with corresponding
841 exceptions that could be raised.
842
843 """
844 if not self.servo:
845 raise error.TestError('Host %s does not have servo.' %
846 self.hostname)
847
848 # TODO(fdeng): use host.get_board() after
849 # crbug.com/271834 is fixed.
850 board = self._get_board_from_afe()
851
Chris Sosae92399e2015-04-24 11:32:59 -0700852 # If build is not set, try to install firmware from stable CrOS.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700853 if not build:
854 build = self.get_repair_image_name()
855
856 config = FAFTConfig(board)
857 if config.use_u_boot:
858 ap_image = 'image-%s.bin' % board
859 else: # Depthcharge platform
860 ap_image = 'image.bin'
861 ec_image = 'ec.bin'
862 ds = dev_server.ImageServer.resolve(build)
863 ds.stage_artifacts(build, ['firmware'])
864
865 tmpd = autotemp.tempdir(unique_id='fwimage')
866 try:
867 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
868 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
869 server_utils.system('wget -O %s %s' % (local_tarball, fwurl),
870 timeout=60)
871 server_utils.system('tar xf %s -C %s %s %s' %
872 (local_tarball, tmpd.name, ap_image, ec_image),
873 timeout=60)
874 server_utils.system('tar xf %s --wildcards -C %s "dts/*"' %
875 (local_tarball, tmpd.name),
876 timeout=60, ignore_status=True)
877
878 self._clear_fw_version_labels()
879 logging.info('Will re-program EC now')
880 self.servo.program_ec(os.path.join(tmpd.name, ec_image))
881 logging.info('Will re-program BIOS now')
882 self.servo.program_bios(os.path.join(tmpd.name, ap_image))
883 self.servo.get_power_state_controller().reset()
884 time.sleep(self.servo.BOOT_DELAY)
Dan Shia5fef052015-05-18 23:28:47 -0700885 self._add_fw_version_label(build)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700886 finally:
887 tmpd.clean()
888
889
Dan Shi10e992b2013-08-30 11:02:59 -0700890 def show_update_engine_log(self):
891 """Output update engine log."""
MK Ryu35d661e2014-09-25 17:44:10 -0700892 logging.debug('Dumping %s', client_constants.UPDATE_ENGINE_LOG)
893 self.run('cat %s' % client_constants.UPDATE_ENGINE_LOG)
Dan Shi10e992b2013-08-30 11:02:59 -0700894
895
Richard Barnette82c35912012-11-20 10:09:10 -0800896 def _get_board_from_afe(self):
897 """Retrieve this host's board from its labels in the AFE.
898
899 Looks for a host label of the form "board:<board>", and
900 returns the "<board>" part of the label. `None` is returned
901 if there is not a single, unique label matching the pattern.
902
903 @returns board from label, or `None`.
904 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700905 return server_utils.get_board_from_afe(self.hostname, self._AFE)
Simran Basi833814b2013-01-29 13:13:43 -0800906
907
908 def get_build(self):
909 """Retrieve the current build for this Host from the AFE.
910
911 Looks through this host's labels in the AFE to determine its build.
912
913 @returns The current build or None if it could not find it or if there
914 were multiple build labels assigned to this host.
915 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700916 return server_utils.get_build_from_afe(self.hostname, self._AFE)
Richard Barnette82c35912012-11-20 10:09:10 -0800917
918
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500919 def _install_repair(self):
Chris Sosae92399e2015-04-24 11:32:59 -0700920 """Attempt to repair this host using the update-engine.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500921
922 If the host is up, try installing the DUT with a stable
Dan Shi6964fa52014-12-18 11:04:27 -0800923 "repair" version of Chrome OS as defined in afe_stable_versions table.
924 If the table is not setup, global_config value under
925 CROS.stable_cros_version will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500926
Scott Zawalski62bacae2013-03-05 10:40:32 -0500927 @raises AutoservRepairMethodNA if the DUT is not reachable.
928 @raises ChromiumOSError if the install failed for some reason.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500929
930 """
931 if not self.is_up():
Scott Zawalski62bacae2013-03-05 10:40:32 -0500932 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500933 logging.info('Attempting to reimage machine to repair image.')
934 try:
Richard Barnette0b023a72015-04-24 16:07:30 +0000935 self.machine_install(repair=True)
Fang Dengd0672f32013-03-18 17:18:09 -0700936 except autoupdater.ChromiumOSError as e:
937 logging.exception(e)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500938 logging.info('Repair via install failed.')
Scott Zawalski62bacae2013-03-05 10:40:32 -0500939 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500940
941
Dan Shi2c88eed2013-11-12 10:18:38 -0800942 def _install_repair_with_powerwash(self):
Dan Shi9cc48452013-11-12 12:39:26 -0800943 """Attempt to powerwash first then repair this host using update-engine.
Dan Shi2c88eed2013-11-12 10:18:38 -0800944
Dan Shi9cc48452013-11-12 12:39:26 -0800945 update-engine may fail due to a bad image. In such case, powerwash
946 may help to cleanup the DUT for update-engine to work again.
Dan Shi2c88eed2013-11-12 10:18:38 -0800947
948 @raises AutoservRepairMethodNA if the DUT is not reachable.
949 @raises ChromiumOSError if the install failed for some reason.
950
951 """
952 if not self.is_up():
953 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
954
955 logging.info('Attempting to powerwash the DUT.')
956 self.run('echo "fast safe" > '
957 '/mnt/stateful_partition/factory_install_reset')
958 self.reboot(timeout=self.POWERWASH_BOOT_TIMEOUT, wait=True)
959 if not self.is_up():
Dan Shi9cc48452013-11-12 12:39:26 -0800960 logging.error('Powerwash failed. DUT did not come back after '
Dan Shi2c88eed2013-11-12 10:18:38 -0800961 'reboot.')
962 raise error.AutoservRepairFailure(
963 'DUT failed to boot from powerwash after %d seconds' %
964 self.POWERWASH_BOOT_TIMEOUT)
965
966 logging.info('Powerwash succeeded.')
967 self._install_repair()
968
969
beepsf079cfb2013-09-18 17:49:51 -0700970 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
971 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500972 """
973 Re-install the OS on the DUT by:
974 1) installing a test image on a USB storage device attached to the Servo
975 board,
Richard Barnette03a0c132012-11-05 12:40:35 -0800976 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700977 3) installing the image with chromeos-install.
978
Scott Zawalski62bacae2013-03-05 10:40:32 -0500979 @param image_url: If specified use as the url to install on the DUT.
980 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -0700981 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
982 Factory images need a longer usb_boot_timeout than regular
983 cros images.
984 @param install_timeout: The timeout to use when installing the chromeos
985 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -0800986
Scott Zawalski62bacae2013-03-05 10:40:32 -0500987 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -0700988
J. Richard Barnette0199cc82014-12-05 17:08:40 -0800989 """
beepsf079cfb2013-09-18 17:49:51 -0700990 usb_boot_timer_key = ('servo_install.usb_boot_timeout_%s'
991 % usb_boot_timeout)
992 logging.info('Downloading image to USB, then booting from it. Usb boot '
993 'timeout = %s', usb_boot_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800994 timer = autotest_stats.Timer(usb_boot_timer_key)
beepsf079cfb2013-09-18 17:49:51 -0700995 timer.start()
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700996 self.servo.install_recovery_image(image_url)
beepsf079cfb2013-09-18 17:49:51 -0700997 if not self.wait_up(timeout=usb_boot_timeout):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500998 raise error.AutoservRepairFailure(
999 'DUT failed to boot from USB after %d seconds' %
beepsf079cfb2013-09-18 17:49:51 -07001000 usb_boot_timeout)
1001 timer.stop()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001002
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001003 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
1004 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001005 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001006 try:
1007 self.run('chromeos-tpm-recovery')
1008 except error.AutoservRunError:
1009 logging.warn('chromeos-tpm-recovery is too old.')
1010
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001011
beepsf079cfb2013-09-18 17:49:51 -07001012 install_timer_key = ('servo_install.install_timeout_%s'
1013 % install_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -08001014 timer = autotest_stats.Timer(install_timer_key)
beepsf079cfb2013-09-18 17:49:51 -07001015 timer.start()
1016 logging.info('Installing image through chromeos-install.')
J. Richard Barnette9af19632015-09-25 12:18:03 -07001017 self.run('chromeos-install --yes', timeout=install_timeout)
1018 self.halt()
beepsf079cfb2013-09-18 17:49:51 -07001019 timer.stop()
1020
1021 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001022 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -07001023 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001024 # N.B. The Servo API requires that we use power_on() here
1025 # for two reasons:
1026 # 1) After turning on a DUT in recovery mode, you must turn
1027 # it off and then on with power_on() once more to
1028 # disable recovery mode (this is a Parrot specific
1029 # requirement).
1030 # 2) After power_off(), the only way to turn on is with
1031 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -07001032 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001033
1034 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001035 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
1036 raise error.AutoservError('DUT failed to reboot installed '
1037 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -05001038 self.BOOT_TIMEOUT)
1039
1040
Dan Shic1b8bdd2015-09-14 23:11:24 -07001041 def _setup_servo(self):
1042 """Try to force to create servo object if it's not set up yet.
1043 """
1044 if self.servo:
1045 return
1046
1047 try:
1048 # Setting servo_args to {} will force it to create the servo_host
1049 # object if possible.
1050 self._servo_host = servo_host.create_servo_host(
1051 dut=self.hostname, servo_args={})
1052 if self._servo_host:
1053 self.servo = self._servo_host.get_servo()
1054 else:
1055 logging.error('Failed to create servo_host object.')
1056 except Exception as e:
1057 logging.error('Failed to create servo object: %s', e)
1058
1059
J. Richard Barnettee4af8b92013-05-01 13:16:12 -07001060 def _servo_repair_reinstall(self):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001061 """Reinstall the DUT utilizing servo and a test image.
1062
1063 Re-install the OS on the DUT by:
1064 1) installing a test image on a USB storage device attached to the Servo
1065 board,
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001066 2) booting that image in recovery mode,
1067 3) resetting the TPM status, and then
1068 4) installing the image with chromeos-install.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001069
Scott Zawalski62bacae2013-03-05 10:40:32 -05001070 @raises AutoservRepairMethodNA if the device does not have servo
1071 support.
1072
1073 """
Dan Shic1b8bdd2015-09-14 23:11:24 -07001074 # To repair a DUT connected to a moblab, try to create a servo object if
1075 # it was failed to be created earlier as there may be a servo_host host
1076 # attribute for this host.
1077 if utils.is_moblab():
1078 self._setup_servo()
1079
Scott Zawalski62bacae2013-03-05 10:40:32 -05001080 if not self.servo:
1081 raise error.AutoservRepairMethodNA('Repair Reinstall NA: '
1082 'DUT has no servo support.')
1083
1084 logging.info('Attempting to recovery servo enabled device with '
1085 'servo_repair_reinstall')
1086
J. Richard Barnettee4af8b92013-05-01 13:16:12 -07001087 image_url = self.stage_image_for_servo()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001088 self.servo_install(image_url)
1089
1090
1091 def _servo_repair_power(self):
1092 """Attempt to repair DUT using an attached Servo.
1093
1094 Attempt to power on the DUT via power_long_press.
1095
1096 @raises AutoservRepairMethodNA if the device does not have servo
1097 support.
1098 @raises AutoservRepairFailure if the repair fails for any reason.
1099 """
1100 if not self.servo:
1101 raise error.AutoservRepairMethodNA('Repair Power NA: '
1102 'DUT has no servo support.')
1103
1104 logging.info('Attempting to recover servo enabled device by '
1105 'powering it off and on.')
1106 self.servo.get_power_state_controller().power_off()
1107 self.servo.get_power_state_controller().power_on()
1108 if self.wait_up(self.BOOT_TIMEOUT):
1109 return
1110
1111 raise error.AutoservRepairFailure('DUT did not boot after long_press.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001112
1113
Richard Barnette82c35912012-11-20 10:09:10 -08001114 def _powercycle_to_repair(self):
1115 """Utilize the RPM Infrastructure to bring the host back up.
1116
1117 If the host is not up/repaired after the first powercycle we utilize
1118 auto fallback to the last good install by powercycling and rebooting the
1119 host 6 times.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001120
1121 @raises AutoservRepairMethodNA if the device does not support remote
1122 power.
1123 @raises AutoservRepairFailure if the repair fails for any reason.
1124
Richard Barnette82c35912012-11-20 10:09:10 -08001125 """
Scott Zawalski62bacae2013-03-05 10:40:32 -05001126 if not self.has_power():
1127 raise error.AutoservRepairMethodNA('Device does not support power.')
1128
Richard Barnette82c35912012-11-20 10:09:10 -08001129 logging.info('Attempting repair via RPM powercycle.')
1130 failed_cycles = 0
1131 self.power_cycle()
1132 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
1133 failed_cycles += 1
1134 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
Scott Zawalski62bacae2013-03-05 10:40:32 -05001135 raise error.AutoservRepairFailure(
1136 'Powercycled host %s %d times; device did not come back'
1137 ' online.' % (self.hostname, failed_cycles))
Richard Barnette82c35912012-11-20 10:09:10 -08001138 self.power_cycle()
1139 if failed_cycles == 0:
1140 logging.info('Powercycling was successful first time.')
1141 else:
1142 logging.info('Powercycling was successful after %d failures.',
1143 failed_cycles)
1144
1145
MK Ryu35d661e2014-09-25 17:44:10 -07001146 def _reboot_repair(self):
1147 """SSH to this host and reboot."""
1148 if not self.is_up(self._CHECK_HOST_UP_TIMEOUT_SECS):
1149 raise error.AutoservRepairMethodNA('DUT unreachable for reboot.')
1150 logging.info('Attempting repair via SSH reboot.')
1151 self.reboot(timeout=self.BOOT_TIMEOUT, wait=True)
1152
1153
Prashanth B4d8184f2014-05-05 12:22:02 -07001154 def check_device(self):
1155 """Check if a device is ssh-able, and if so, clean and verify it.
1156
1157 @raise AutoservSSHTimeout: If the ssh ping times out.
1158 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
1159 permissions.
1160 @raise AutoservSshPingHostError: For other AutoservRunErrors during
1161 ssh_ping.
1162 @raises AutoservError: As appropriate, during cleanup and verify.
1163 """
1164 self.ssh_ping()
1165 self.cleanup()
1166 self.verify()
1167
1168
Dan Shi90466352015-09-22 15:01:05 -07001169 def confirm_servo(self):
1170 """Confirm servo is initialized and verified.
1171
1172 @raise AutoservError: If servo is not initialized and verified.
1173 """
1174 if self._servo_host.required_by_test and self.servo:
1175 return
1176
1177 # Force to re-create the servo object to make sure servo is verified.
1178 logging.debug('Rebuilding the servo object.')
1179 self.servo = None
1180 self._servo_host = None
1181 self._setup_servo()
1182 if not self.servo:
1183 raise error.AutoservError('Failed to create servo object.')
1184
1185
Dan Shid07ee2e2015-09-24 14:49:25 -07001186 def _is_last_provision_failed(self):
1187 """Checks if the last provision job failed.
1188
1189 @return: True if there exists file /var/tmp/provision_failed, which
1190 indicates the last provision job failed.
1191 False if the file does not exist or the dut can't be reached.
1192 """
1193 try:
1194 result = self.run('test -f %s' % PROVISION_FAILED,
1195 ignore_status=True, timeout=5)
1196 return result.exit_status == 0
1197 except (error.AutoservRunError, error.AutoservSSHTimeout):
1198 # Default to False, for repair to try all repair method if the dut
1199 # can't be reached.
1200 return False
1201
1202
Richard Barnette82c35912012-11-20 10:09:10 -08001203 def repair_full(self):
1204 """Repair a host for repair level NO_PROTECTION.
1205
1206 This overrides the base class function for repair; it does
1207 not call back to the parent class, but instead offers a
1208 simplified implementation based on the capabilities in the
1209 Chrome OS test lab.
1210
Fang Deng5d518f42013-08-02 14:04:32 -07001211 It first verifies and repairs servo if it is a DUT in CrOS
Fang Deng03590af2013-10-07 17:34:20 -07001212 lab and a servo is attached.
Fang Deng5d518f42013-08-02 14:04:32 -07001213
Jakob Juelich82b7d1c2014-09-15 16:10:57 -07001214 This escalates in order through the following procedures and verifies
1215 the status using `self.check_device()` after each of them. This is done
1216 until both the repair and the veryfing step succeed.
1217
MK Ryu35d661e2014-09-25 17:44:10 -07001218 Escalation order of repair procedures from less intrusive to
1219 more intrusive repairs:
1220 1. SSH to the DUT and reboot.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001221 2. If there's a servo for the DUT, try to power the DUT off and
1222 on.
MK Ryu35d661e2014-09-25 17:44:10 -07001223 3. If the DUT can be power-cycled via RPM, try to repair
Richard Barnette82c35912012-11-20 10:09:10 -08001224 by power-cycling.
MK Ryu35d661e2014-09-25 17:44:10 -07001225 4. Try to re-install to a known stable image using
1226 auto-update.
1227 5. If there's a servo for the DUT, try to re-install via
1228 the servo.
Richard Barnette82c35912012-11-20 10:09:10 -08001229
1230 As with the parent method, the last operation performed on
Prashanth B4d8184f2014-05-05 12:22:02 -07001231 the DUT must be to call `self.check_device()`; If that call fails the
1232 exception it raises is passed back to the caller.
J. Richard Barnettefde55fc2013-03-15 17:47:01 -07001233
Scott Zawalski62bacae2013-03-05 10:40:32 -05001234 @raises AutoservRepairTotalFailure if the repair process fails to
1235 fix the DUT.
Fang Deng5d518f42013-08-02 14:04:32 -07001236 @raises ServoHostRepairTotalFailure if the repair process fails to
1237 fix the servo host if one is attached to the DUT.
1238 @raises AutoservSshPermissionDeniedError if it is unable
1239 to ssh to the servo host due to permission error.
1240
Richard Barnette82c35912012-11-20 10:09:10 -08001241 """
Jakob Juelich82b7d1c2014-09-15 16:10:57 -07001242 # Caution: Deleting shards relies on repair to always reboot the DUT.
1243
Dan Shi4d478522014-02-14 13:46:32 -08001244 if self._servo_host and not self.servo:
Fang Deng03590af2013-10-07 17:34:20 -07001245 try:
Dan Shi4d478522014-02-14 13:46:32 -08001246 self._servo_host.repair_full()
Fang Deng03590af2013-10-07 17:34:20 -07001247 except Exception as e:
Fang Deng03590af2013-10-07 17:34:20 -07001248 logging.error('Could not create a healthy servo: %s', e)
Dan Shi4d478522014-02-14 13:46:32 -08001249 self.servo = self._servo_host.get_servo()
Fang Deng5d518f42013-08-02 14:04:32 -07001250
MK Ryu35d661e2014-09-25 17:44:10 -07001251 self.try_collect_crashlogs()
1252
Scott Zawalski62bacae2013-03-05 10:40:32 -05001253 # TODO(scottz): This should use something similar to label_decorator,
1254 # but needs to be populated in order so DUTs are repaired with the
1255 # least amount of effort.
Dan Shid07ee2e2015-09-24 14:49:25 -07001256 force_powerwash = self._is_last_provision_failed()
1257 if force_powerwash:
1258 logging.info('Last provision failed, try powerwash first.')
1259 autotest_stats.Counter(
1260 'repair_force_powerwash.TOTAL').increment()
1261 repair_funcs = [self._install_repair_with_powerwash,
1262 self._servo_repair_reinstall]
1263 else:
1264 repair_funcs = [self._reboot_repair,
1265 self._servo_repair_power,
1266 self._powercycle_to_repair,
1267 self._install_repair,
1268 self._install_repair_with_powerwash,
1269 self._servo_repair_reinstall]
Scott Zawalski62bacae2013-03-05 10:40:32 -05001270 errors = []
Simran Basie6130932013-10-01 14:07:52 -07001271 board = self._get_board_from_afe()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001272 for repair_func in repair_funcs:
1273 try:
1274 repair_func()
MK Ryu35d661e2014-09-25 17:44:10 -07001275 self.try_collect_crashlogs()
Prashanth B4d8184f2014-05-05 12:22:02 -07001276 self.check_device()
Gabe Black1e1c41b2015-02-04 23:55:15 -08001277 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001278 '%s.SUCCEEDED' % repair_func.__name__).increment()
1279 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001280 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001281 '%s.%s.SUCCEEDED' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001282 board)).increment()
Dan Shid07ee2e2015-09-24 14:49:25 -07001283 if force_powerwash:
1284 autotest_stats.Counter(
1285 'repair_force_powerwash.SUCCEEDED').increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001286 return
Simran Basie6130932013-10-01 14:07:52 -07001287 except error.AutoservRepairMethodNA as e:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001288 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001289 '%s.RepairNA' % repair_func.__name__).increment()
1290 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001291 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001292 '%s.%s.RepairNA' % (repair_func.__name__,
1293 board)).increment()
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001294 logging.warning('Repair function NA: %s', e)
Simran Basie6130932013-10-01 14:07:52 -07001295 errors.append(str(e))
Scott Zawalski62bacae2013-03-05 10:40:32 -05001296 except Exception as e:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001297 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001298 '%s.FAILED' % repair_func.__name__).increment()
1299 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001300 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001301 '%s.%s.FAILED' % (repair_func.__name__,
1302 board)).increment()
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001303 logging.warning('Failed to repair device: %s', e)
Scott Zawalski62bacae2013-03-05 10:40:32 -05001304 errors.append(str(e))
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001305
Dan Shid07ee2e2015-09-24 14:49:25 -07001306 if force_powerwash:
1307 autotest_stats.Counter(
1308 'repair_force_powerwash.FAILED').increment()
Gabe Black1e1c41b2015-02-04 23:55:15 -08001309 autotest_stats.Counter('Full_Repair_Failed').increment()
Simran Basie6130932013-10-01 14:07:52 -07001310 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001311 autotest_stats.Counter(
Dan Shid07ee2e2015-09-24 14:49:25 -07001312 'Full_Repair_Failed.%s' % board).increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001313 raise error.AutoservRepairTotalFailure(
1314 'All attempts at repairing the device failed:\n%s' %
1315 '\n'.join(errors))
Richard Barnette82c35912012-11-20 10:09:10 -08001316
1317
MK Ryu35d661e2014-09-25 17:44:10 -07001318 def try_collect_crashlogs(self, check_host_up=True):
1319 """
1320 Check if a host is up and logs need to be collected from the host,
1321 if yes, collect them.
1322
1323 @param check_host_up: Flag for checking host is up. Default is True.
1324 """
1325 try:
1326 crash_job = self._need_crash_logs()
1327 if crash_job:
1328 logging.debug('%s: Job %s was crashed', self._CRASHLOGS_PREFIX,
1329 crash_job)
1330 if not check_host_up or self.is_up(
1331 self._CHECK_HOST_UP_TIMEOUT_SECS):
1332 self._collect_crashlogs(crash_job)
1333 logging.debug('%s: Completed collecting logs for the '
1334 'crashed job %s', self._CRASHLOGS_PREFIX,
1335 crash_job)
1336 except Exception as e:
1337 # Exception should not result in repair failure.
1338 # Therefore, suppress all exceptions here.
1339 logging.error('%s: Failed while trying to collect crash-logs: %s',
1340 self._CRASHLOGS_PREFIX, e)
1341
1342
1343 def _need_crash_logs(self):
1344 """Get the value of need_crash_logs attribute of this host.
1345
1346 @return: Value string of need_crash_logs attribute
1347 None if there is no need_crash_logs attribute
1348 """
1349 attrs = self._AFE.get_host_attribute(constants.CRASHLOGS_HOST_ATTRIBUTE,
1350 hostname=self.hostname)
1351 assert len(attrs) < 2
1352 return attrs[0].value if attrs else None
1353
1354
1355 def _collect_crashlogs(self, job_id):
1356 """Grab logs from the host where a job was crashed.
1357
1358 First, check if PRIOR_LOGS_DIR exists in the host.
1359 If yes, collect them.
1360 Otherwise, check if a lab-machine marker (_LAB_MACHINE_FILE) exists
1361 in the host.
1362 If yes, the host was repaired automatically, and we collect normal
1363 system logs.
1364
1365 @param job_id: Id of the job that was crashed.
1366 """
1367 crashlogs_dir = crashcollect.get_crashinfo_dir(self,
1368 constants.CRASHLOGS_DEST_DIR_PREFIX)
1369 flag_prior_logs = False
1370
1371 if self.path_exists(client_constants.PRIOR_LOGS_DIR):
1372 flag_prior_logs = True
1373 self._collect_prior_logs(crashlogs_dir)
1374 elif self.path_exists(self._LAB_MACHINE_FILE):
1375 self._collect_system_logs(crashlogs_dir)
1376 else:
1377 logging.warning('%s: Host was manually re-installed without '
1378 '--lab_preserve_log option. Skip collecting '
1379 'crash-logs.', self._CRASHLOGS_PREFIX)
1380
1381 # We make crash collection be one-time effort.
1382 # _collect_prior_logs() and _collect_system_logs() will not throw
1383 # any exception, and following codes will be executed even when
1384 # those methods fail.
1385 # _collect_crashlogs() is called only when the host is up (refer
1386 # to try_collect_crashlogs()). We assume _collect_prior_logs() and
1387 # _collect_system_logs() fail rarely when the host is up.
1388 # In addition, it is not clear how many times we should try crash
1389 # collection again while not triggering next repair unnecessarily.
1390 # Threfore, we try crash collection one time.
1391
1392 # Create a marker file as soon as log collection is done.
1393 # Leave the job id to this marker for gs_offloader to consume.
1394 marker_file = os.path.join(crashlogs_dir, constants.CRASHLOGS_MARKER)
1395 with open(marker_file, 'a') as f:
1396 f.write('%s\n' % job_id)
1397
1398 # Remove need_crash_logs attribute
1399 logging.debug('%s: Remove attribute need_crash_logs from host %s',
1400 self._CRASHLOGS_PREFIX, self.hostname)
1401 self._AFE.set_host_attribute(constants.CRASHLOGS_HOST_ATTRIBUTE,
1402 None, hostname=self.hostname)
1403
1404 if flag_prior_logs:
1405 logging.debug('%s: Remove %s from host %s', self._CRASHLOGS_PREFIX,
1406 client_constants.PRIOR_LOGS_DIR, self.hostname)
1407 self.run('rm -rf %s; sync' % client_constants.PRIOR_LOGS_DIR)
1408 # Wait for a few seconds to make sure the prior command is
1409 # done deep through storage.
1410 time.sleep(self._SAFE_WAIT_SECS)
1411
1412
1413 def _collect_prior_logs(self, crashlogs_dir):
1414 """Grab prior logs that were stashed before re-installing a host.
1415
1416 @param crashlogs_dir: Directory path where crash-logs are stored.
1417 """
1418 logging.debug('%s: Found %s, collecting them...',
1419 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR)
1420 try:
1421 self.collect_logs(client_constants.PRIOR_LOGS_DIR,
1422 crashlogs_dir, False)
1423 logging.debug('%s: %s is collected',
1424 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR)
1425 except Exception as e:
1426 logging.error('%s: Failed to collect %s: %s',
1427 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR,
1428 e)
1429
1430
1431 def _collect_system_logs(self, crashlogs_dir):
1432 """Grab normal system logs from a host.
1433
1434 @param crashlogs_dir: Directory path where crash-logs are stored.
1435 """
1436 logging.debug('%s: Found %s, collecting system logs...',
1437 self._CRASHLOGS_PREFIX, self._LAB_MACHINE_FILE)
1438 sources = server_utils.parse_simple_config(self._LOGS_TO_COLLECT_FILE)
1439 for src in sources:
1440 try:
1441 if self.path_exists(src):
1442 logging.debug('%s: Collecting %s...',
1443 self._CRASHLOGS_PREFIX, src)
1444 dest = server_utils.concat_path_except_last(
1445 crashlogs_dir, src)
1446 self.collect_logs(src, dest, False)
1447 logging.debug('%s: %s is collected',
1448 self._CRASHLOGS_PREFIX, src)
1449 except Exception as e:
1450 logging.error('%s: Failed to collect %s: %s',
1451 self._CRASHLOGS_PREFIX, src, e)
1452
1453
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001454 def close(self):
beeps32a63082013-08-22 14:02:29 -07001455 self.rpc_disconnect_all()
Fang Deng0ca40e22013-08-27 17:47:44 -07001456 super(CrosHost, self).close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001457
1458
Dan Shi49ca0932014-11-14 11:22:27 -08001459 def get_power_supply_info(self):
1460 """Get the output of power_supply_info.
1461
1462 power_supply_info outputs the info of each power supply, e.g.,
1463 Device: Line Power
1464 online: no
1465 type: Mains
1466 voltage (V): 0
1467 current (A): 0
1468 Device: Battery
1469 state: Discharging
1470 percentage: 95.9276
1471 technology: Li-ion
1472
1473 Above output shows two devices, Line Power and Battery, with details of
1474 each device listed. This function parses the output into a dictionary,
1475 with key being the device name, and value being a dictionary of details
1476 of the device info.
1477
1478 @return: The dictionary of power_supply_info, e.g.,
1479 {'Line Power': {'online': 'yes', 'type': 'main'},
1480 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001481 @raise error.AutoservRunError if power_supply_info tool is not found in
1482 the DUT. Caller should handle this error to avoid false failure
1483 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001484 """
1485 result = self.run('power_supply_info').stdout.strip()
1486 info = {}
1487 device_name = None
1488 device_info = {}
1489 for line in result.split('\n'):
1490 pair = [v.strip() for v in line.split(':')]
1491 if len(pair) != 2:
1492 continue
1493 if pair[0] == 'Device':
1494 if device_name:
1495 info[device_name] = device_info
1496 device_name = pair[1]
1497 device_info = {}
1498 else:
1499 device_info[pair[0]] = pair[1]
1500 if device_name and not device_name in info:
1501 info[device_name] = device_info
1502 return info
1503
1504
1505 def get_battery_percentage(self):
1506 """Get the battery percentage.
1507
1508 @return: The percentage of battery level, value range from 0-100. Return
1509 None if the battery info cannot be retrieved.
1510 """
1511 try:
1512 info = self.get_power_supply_info()
1513 logging.info(info)
1514 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001515 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001516 return None
1517
1518
1519 def is_ac_connected(self):
1520 """Check if the dut has power adapter connected and charging.
1521
1522 @return: True if power adapter is connected and charging.
1523 """
1524 try:
1525 info = self.get_power_supply_info()
1526 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001527 except (KeyError, error.AutoservRunError):
1528 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001529
1530
Simran Basi5e6339a2013-03-21 11:34:32 -07001531 def _cleanup_poweron(self):
1532 """Special cleanup method to make sure hosts always get power back."""
1533 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1534 hosts = afe.get_hosts(hostname=self.hostname)
1535 if not hosts or not (self._RPM_OUTLET_CHANGED in
1536 hosts[0].attributes):
1537 return
1538 logging.debug('This host has recently interacted with the RPM'
1539 ' Infrastructure. Ensuring power is on.')
1540 try:
1541 self.power_on()
Dan Shi7dca56e2014-11-11 17:07:56 -08001542 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1543 hostname=self.hostname)
Simran Basi5e6339a2013-03-21 11:34:32 -07001544 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001545 logging.error('Failed to turn Power On for this host after '
1546 'cleanup through the RPM Infrastructure.')
Gabe Blackb72f4fb2015-01-20 16:47:13 -08001547 autotest_es.post(
Dan Shi7dca56e2014-11-11 17:07:56 -08001548 type_str='RPM_poweron_failure',
1549 metadata={'hostname': self.hostname})
Dan Shi49ca0932014-11-14 11:22:27 -08001550
1551 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -08001552 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -08001553 raise
1554 elif self.is_ac_connected():
1555 logging.info('The device has power adapter connected and '
1556 'charging. No need to try to turn RPM on '
1557 'again.')
1558 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1559 hostname=self.hostname)
1560 logging.info('Battery level is now at %s%%. The device may '
1561 'still have enough power to run test, so no '
1562 'exception will be raised.', battery_percentage)
1563
Simran Basi5e6339a2013-03-21 11:34:32 -07001564
beepsc87ff602013-07-31 21:53:00 -07001565 def _is_factory_image(self):
1566 """Checks if the image on the DUT is a factory image.
1567
1568 @return: True if the image on the DUT is a factory image.
1569 False otherwise.
1570 """
1571 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1572 return result.exit_status == 0
1573
1574
1575 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001576 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001577
1578 @raises: FactoryImageCheckerException for factory images, since
1579 we cannot attempt to restart ui on them.
1580 error.AutoservRunError for any other type of error that
1581 occurs while restarting ui.
1582 """
1583 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001584 raise FactoryImageCheckerException('Cannot restart ui on factory '
1585 'images')
beepsc87ff602013-07-31 21:53:00 -07001586
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001587 # TODO(jrbarnette): The command to stop/start the ui job
1588 # should live inside cros_ui, too. However that would seem
1589 # to imply interface changes to the existing start()/restart()
1590 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -07001591 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001592 self.run('stop ui; start ui')
1593 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001594
1595
Dan Shi549fb822015-03-24 18:01:11 -07001596 def get_release_version(self):
1597 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1598
1599 @returns The version string in lsb-release, under attribute
1600 CHROMEOS_RELEASE_VERSION.
1601 """
1602 lsb_release_content = self.run(
1603 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1604 return lsbrelease_utils.get_chromeos_release_version(
1605 lsb_release_content=lsb_release_content)
1606
1607
1608 def verify_cros_version_label(self):
1609 """ Make sure host's cros-version label match the actual image in dut.
1610
1611 Remove any cros-version: label that doesn't match that installed in
1612 the dut.
1613
1614 @param raise_error: Set to True to raise exception if any mismatch found
1615
1616 @raise error.AutoservError: If any mismatch between cros-version label
1617 and the build installed in dut is found.
1618 """
1619 labels = self._AFE.get_labels(
1620 name__startswith=ds_constants.VERSION_PREFIX,
1621 host__hostname=self.hostname)
1622 mismatch_found = False
1623 if labels:
1624 # Get CHROMEOS_RELEASE_VERSION from lsb-release, e.g., 6908.0.0.
1625 # Note that it's different from cros-version label, which has
1626 # builder and branch info, e.g.,
1627 # cros-version:peppy-release/R43-6908.0.0
1628 release_version = self.get_release_version()
1629 host_list = [self.hostname]
1630 for label in labels:
1631 # Remove any cros-version label that does not match
1632 # release_version.
1633 build_version = label.name[len(ds_constants.VERSION_PREFIX):]
1634 if not utils.version_match(build_version, release_version):
1635 logging.warn('cros-version label "%s" does not match '
1636 'release version %s. Removing the label.',
1637 label.name, release_version)
1638 label.remove_hosts(hosts=host_list)
1639 mismatch_found = True
1640 if mismatch_found:
Dan Shi1057bae2015-03-30 11:35:09 -07001641 autotest_es.post(use_http=True,
1642 type_str='cros_version_label_mismatch',
1643 metadata={'hostname': self.hostname})
Dan Shi549fb822015-03-24 18:01:11 -07001644 raise error.AutoservError('The host has wrong cros-version label.')
1645
1646
beepsc87ff602013-07-31 21:53:00 -07001647 def cleanup(self):
MK Ryu35d661e2014-09-25 17:44:10 -07001648 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001649 try:
beepsc87ff602013-07-31 21:53:00 -07001650 self._restart_ui()
1651 except (error.AutotestRunError, error.AutoservRunError,
1652 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001653 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001654 # Since restarting the UI fails fall back to normal Autotest
1655 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001656 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001657 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001658 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001659 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001660 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001661
1662
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001663 def reboot(self, **dargs):
1664 """
1665 This function reboots the site host. The more generic
1666 RemoteHost.reboot() performs sync and sleeps for 5
1667 seconds. This is not necessary for Chrome OS devices as the
1668 sync should be finished in a short time during the reboot
1669 command.
1670 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001671 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001672 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001673 dargs['reboot_cmd'] = ('sleep 1; '
1674 'reboot & sleep %d; '
1675 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001676 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001677 if 'fastsync' not in dargs:
1678 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001679
Charlie Mooneya8e6dab2014-05-29 14:37:55 -07001680 # For purposes of logging reboot times:
1681 # Get the board name i.e. 'daisy_spring'
Michael Liangca4f5a62014-07-10 15:45:13 -07001682 board_fullname = self.get_board()
1683
1684 # Strip the prefix and add it to dargs.
1685 dargs['board'] = board_fullname[board_fullname.find(':')+1:]
Fang Deng0ca40e22013-08-27 17:47:44 -07001686 super(CrosHost, self).reboot(**dargs)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001687
1688
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001689 def suspend(self, **dargs):
1690 """
1691 This function suspends the site host.
1692 """
1693 suspend_time = dargs.get('suspend_time', 60)
1694 dargs['timeout'] = suspend_time
1695 if 'suspend_cmd' not in dargs:
J. Richard Barnette9af19632015-09-25 12:18:03 -07001696 dargs['suspend_cmd'] = ' && '.join([
1697 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001698 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
J. Richard Barnette9af19632015-09-25 12:18:03 -07001699 'powerd_dbus_suspend --delay=0'])
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001700 super(CrosHost, self).suspend(**dargs)
1701
1702
Simran Basiec564392014-08-25 16:48:09 -07001703 def upstart_status(self, service_name):
1704 """Check the status of an upstart init script.
1705
1706 @param service_name: Service to look up.
1707
1708 @returns True if the service is running, False otherwise.
1709 """
1710 return self.run('status %s | grep start/running' %
1711 service_name).stdout.strip() != ''
1712
1713
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001714 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001715 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001716
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001717 Tests for the following conditions:
1718 1. All conditions tested by the parent version of this
1719 function.
1720 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001721 3. Sufficient space in /mnt/stateful_partition/encrypted.
1722 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001723
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001724 """
MK Ryu35d661e2014-09-25 17:44:10 -07001725 # Check if a job was crashed on this host.
1726 # If yes, avoid verification until crash-logs are collected.
1727 if self._need_crash_logs():
1728 raise error.AutoservCrashLogCollectRequired(
1729 'Need to collect crash-logs before verification')
1730
Fang Deng0ca40e22013-08-27 17:47:44 -07001731 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001732 default_kilo_inodes_required = CONFIG.get_config_value(
1733 'SERVER', 'kilo_inodes_required', type=int, default=100)
1734 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1735 kilo_inodes_required = CONFIG.get_config_value(
1736 'SERVER', 'kilo_inodes_required_%s' % board,
1737 type=int, default=default_kilo_inodes_required)
1738 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001739 self.check_diskspace(
1740 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001741 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001742 'SERVER', 'gb_diskspace_required', type=float,
1743 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001744 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1745 # Not all targets build with encrypted stateful support.
1746 if self.path_exists(encrypted_stateful_path):
1747 self.check_diskspace(
1748 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001749 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001750 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1751 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001752
Simran Basiec564392014-08-25 16:48:09 -07001753 if not self.upstart_status('system-services'):
Prashanth B5d0a0512014-04-25 12:26:08 -07001754 raise error.AutoservError('Chrome failed to reach login. '
1755 'System services not running.')
1756
beepsc87ff602013-07-31 21:53:00 -07001757 # Factory images don't run update engine,
1758 # goofy controls dbus on these DUTs.
1759 if not self._is_factory_image():
1760 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -05001761 # Makes sure python is present, loads and can use built in functions.
1762 # We have seen cases where importing cPickle fails with undefined
1763 # symbols in cPickle.so.
1764 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001765
Dan Shi549fb822015-03-24 18:01:11 -07001766 self.verify_cros_version_label()
1767
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001768
Dan Shi49ca0932014-11-14 11:22:27 -08001769 def verify_hardware(self):
1770 """Verify hardware system of a Chrome OS system.
1771
1772 Check following hardware conditions:
1773 1. Battery level.
1774 2. Is power adapter connected.
1775 """
1776 logging.info('Battery percentage: %s', self.get_battery_percentage())
Dan Shie9b765d2014-12-29 16:59:49 -08001777 if self.is_ac_connected() is None:
1778 logging.info('Can not determine if the device has power adapter '
1779 'connected.')
1780 else:
1781 logging.info('Device %s power adapter connected and charging.',
1782 'has' if self.is_ac_connected() else 'does not have')
Dan Shi49ca0932014-11-14 11:22:27 -08001783
1784
Fang Deng96667ca2013-08-01 17:46:18 -07001785 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
1786 connect_timeout=None, alive_interval=None):
1787 """Override default make_ssh_command to use options tuned for Chrome OS.
1788
1789 Tuning changes:
1790 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1791 connection failure. Consistency with remote_access.sh.
1792
Samuel Tan2ce155b2015-06-23 18:24:38 -07001793 - ServerAliveInterval=900; which causes SSH to ping connection every
1794 900 seconds. In conjunction with ServerAliveCountMax ensures
1795 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001796 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001797 the test completed successfully. Later increased from 180 seconds to
1798 900 seconds to account for tests where the DUT is suspended for
1799 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001800
1801 - ServerAliveCountMax=3; consistency with remote_access.sh.
1802
1803 - ConnectAttempts=4; reduce flakiness in connection errors;
1804 consistency with remote_access.sh.
1805
1806 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1807 Host keys change with every new installation, don't waste
1808 memory/space saving them.
1809
1810 - SSH protocol forced to 2; needed for ServerAliveInterval.
1811
1812 @param user User name to use for the ssh connection.
1813 @param port Port on the target host to use for ssh connection.
1814 @param opts Additional options to the ssh command.
1815 @param hosts_file Ignored.
1816 @param connect_timeout Ignored.
1817 @param alive_interval Ignored.
1818 """
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001819 base_command = ('/usr/bin/ssh -a -x %s %s %s'
1820 ' -o StrictHostKeyChecking=no'
Fang Deng96667ca2013-08-01 17:46:18 -07001821 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Samuel Tan2ce155b2015-06-23 18:24:38 -07001822 ' -o ConnectTimeout=30 -o ServerAliveInterval=900'
Fang Deng96667ca2013-08-01 17:46:18 -07001823 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
1824 ' -o Protocol=2 -l %s -p %d')
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001825 return base_command % (self._ssh_verbosity_flag, self._ssh_options,
1826 opts, user, port)
Fang Deng96667ca2013-08-01 17:46:18 -07001827
1828
Christopher Wileydd181852013-10-10 19:56:58 -07001829 def _setup_rpc(self, port, command_name, remote_pid=None):
beeps32a63082013-08-22 14:02:29 -07001830 """Sets up a tunnel process and performs rpc connection book keeping.
1831
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +08001832 Chrome OS on the target closes down most external ports for security.
1833 We could open the port, but doing that would conflict with security
1834 tests that check that only expected ports are open. So, to get to
1835 the port on the target we use an ssh tunnel.
1836
beeps32a63082013-08-22 14:02:29 -07001837 This method assumes that xmlrpc and jsonrpc never conflict, since
1838 we can only either have an xmlrpc or a jsonrpc server listening on
1839 a remote port. As such, it enforces a single proxy->remote port
1840 policy, i.e if one starts a jsonrpc proxy/server from port A->B,
1841 and then tries to start an xmlrpc proxy forwarded to the same port,
1842 the xmlrpc proxy will override the jsonrpc tunnel process, however:
1843
1844 1. None of the methods on the xmlrpc proxy will work because
1845 the server listening on B is jsonrpc.
1846
1847 2. The xmlrpc client cannot initiate a termination of the JsonRPC
1848 server, as the only use case currently is goofy, which is tied to
1849 the factory image. It is much easier to handle a failed xmlrpc
1850 call on the client than it is to terminate goofy in this scenario,
1851 as doing the latter might leave the DUT in a hard to recover state.
1852
1853 With the current implementation newer rpc proxy connections will
1854 terminate the tunnel processes of older rpc connections tunneling
1855 to the same remote port. If methods are invoked on the client
1856 after this has happened they will fail with connection closed errors.
1857
1858 @param port: The remote forwarding port.
1859 @param command_name: The name of the remote process, to terminate
1860 using pkill.
1861
1862 @return A url that we can use to initiate the rpc connection.
1863 """
1864 self.rpc_disconnect(port)
1865 local_port = utils.get_unused_port()
1866 tunnel_proc = self._create_ssh_tunnel(port, local_port)
Christopher Wileydd181852013-10-10 19:56:58 -07001867 self._rpc_proxy_map[port] = (command_name, tunnel_proc, remote_pid)
beeps32a63082013-08-22 14:02:29 -07001868 return self._RPC_PROXY_URL % local_port
1869
1870
Christopher Wileyd78249a2013-03-01 13:05:31 -08001871 def xmlrpc_connect(self, command, port, command_name=None,
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001872 ready_test_name=None, timeout_seconds=10,
1873 logfile='/dev/null'):
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001874 """Connect to an XMLRPC server on the host.
1875
1876 The `command` argument should be a simple shell command that
1877 starts an XMLRPC server on the given `port`. The command
1878 must not daemonize, and must terminate cleanly on SIGTERM.
1879 The command is started in the background on the host, and a
1880 local XMLRPC client for the server is created and returned
1881 to the caller.
1882
1883 Note that the process of creating an XMLRPC client makes no
1884 attempt to connect to the remote server; the caller is
1885 responsible for determining whether the server is running
1886 correctly, and is ready to serve requests.
1887
Christopher Wileyd78249a2013-03-01 13:05:31 -08001888 Optionally, the caller can pass ready_test_name, a string
1889 containing the name of a method to call on the proxy. This
1890 method should take no parameters and return successfully only
1891 when the server is ready to process client requests. When
1892 ready_test_name is set, xmlrpc_connect will block until the
1893 proxy is ready, and throw a TestError if the server isn't
1894 ready by timeout_seconds.
1895
beeps32a63082013-08-22 14:02:29 -07001896 If a server is already running on the remote port, this
1897 method will kill it and disconnect the tunnel process
1898 associated with the connection before establishing a new one,
1899 by consulting the rpc_proxy_map in rpc_disconnect.
1900
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001901 @param command Shell command to start the server.
1902 @param port Port number on which the server is expected to
1903 be serving.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001904 @param command_name String to use as input to `pkill` to
1905 terminate the XMLRPC server on the host.
Christopher Wileyd78249a2013-03-01 13:05:31 -08001906 @param ready_test_name String containing the name of a
1907 method defined on the XMLRPC server.
1908 @param timeout_seconds Number of seconds to wait
1909 for the server to become 'ready.' Will throw a
1910 TestFail error if server is not ready in time.
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001911 @param logfile Logfile to send output when running
1912 'command' argument.
Yusuf Mohsinally8d19e3c2013-11-21 14:25:45 -08001913
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001914 """
Christopher Wileyc14f06a2013-10-16 13:55:39 -07001915 # Clean up any existing state. If the caller is willing
1916 # to believe their server is down, we ought to clean up
1917 # any tunnels we might have sitting around.
1918 self.rpc_disconnect(port)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001919 # Start the server on the host. Redirection in the command
1920 # below is necessary, because 'ssh' won't terminate until
1921 # background child processes close stdin, stdout, and
1922 # stderr.
J. Richard Barnette9af19632015-09-25 12:18:03 -07001923 remote_cmd = '%s >%s 2>&1' % (command, logfile)
1924 remote_pid = self.run_background(remote_cmd)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001925 logging.debug('Started XMLRPC server on host %s, pid = %s',
1926 self.hostname, remote_pid)
1927
Christopher Wileydd181852013-10-10 19:56:58 -07001928 # Tunnel through SSH to be able to reach that remote port.
1929 rpc_url = self._setup_rpc(port, command_name, remote_pid=remote_pid)
Christopher Wileyd78249a2013-03-01 13:05:31 -08001930 proxy = xmlrpclib.ServerProxy(rpc_url, allow_none=True)
Christopher Wileydd181852013-10-10 19:56:58 -07001931
Christopher Wileyd78249a2013-03-01 13:05:31 -08001932 if ready_test_name is not None:
J. Richard Barnette13eb7c02013-03-07 12:06:29 -08001933 # retry.retry logs each attempt; calculate delay_sec to
1934 # keep log spam to a dull roar.
Christopher Wiley0ed712b2013-04-09 15:25:12 -07001935 @retry.retry((socket.error,
1936 xmlrpclib.ProtocolError,
1937 httplib.BadStatusLine),
Chris Sosa65425082013-10-16 13:26:22 -07001938 timeout_min=timeout_seconds / 60.0,
1939 delay_sec=min(max(timeout_seconds / 20.0, 0.1), 1))
Christopher Wileyd78249a2013-03-01 13:05:31 -08001940 def ready_test():
1941 """ Call proxy.ready_test_name(). """
1942 getattr(proxy, ready_test_name)()
1943 successful = False
1944 try:
1945 logging.info('Waiting %d seconds for XMLRPC server '
1946 'to start.', timeout_seconds)
1947 ready_test()
1948 successful = True
Christopher Wileyd78249a2013-03-01 13:05:31 -08001949 finally:
1950 if not successful:
1951 logging.error('Failed to start XMLRPC server.')
beeps32a63082013-08-22 14:02:29 -07001952 self.rpc_disconnect(port)
Christopher Wileyd78249a2013-03-01 13:05:31 -08001953 logging.info('XMLRPC server started successfully.')
1954 return proxy
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001955
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001956
Jason Abeleb6f924f2013-11-13 16:01:54 -08001957 def syslog(self, message, tag='autotest'):
1958 """Logs a message to syslog on host.
1959
1960 @param message String message to log into syslog
1961 @param tag String tag prefix for syslog
1962
1963 """
1964 self.run('logger -t "%s" "%s"' % (tag, message))
1965
1966
beeps32a63082013-08-22 14:02:29 -07001967 def jsonrpc_connect(self, port):
1968 """Creates a jsonrpc proxy connection through an ssh tunnel.
1969
1970 This method exists to facilitate communication with goofy (which is
1971 the default system manager on all factory images) and as such, leaves
1972 most of the rpc server sanity checking to the caller. Unlike
1973 xmlrpc_connect, this method does not facilitate the creation of a remote
1974 jsonrpc server, as the only clients of this code are factory tests,
1975 for which the goofy system manager is built in to the image and starts
1976 when the target boots.
1977
1978 One can theoretically create multiple jsonrpc proxies all forwarded
1979 to the same remote port, provided the remote port has an rpc server
1980 listening. However, in doing so we stand the risk of leaking an
1981 existing tunnel process, so we always disconnect any older tunnels
1982 we might have through rpc_disconnect.
1983
1984 @param port: port on the remote host that is serving this proxy.
1985
1986 @return: The client proxy.
1987 """
1988 if not jsonrpclib:
1989 logging.warning('Jsonrpclib could not be imported. Check that '
1990 'site-packages contains jsonrpclib.')
1991 return None
1992
1993 proxy = jsonrpclib.jsonrpc.ServerProxy(self._setup_rpc(port, None))
1994
1995 logging.info('Established a jsonrpc connection through port %s.', port)
1996 return proxy
1997
1998
1999 def rpc_disconnect(self, port):
2000 """Disconnect from an RPC server on the host.
2001
2002 Terminates the remote RPC server previously started for
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002003 the given `port`. Also closes the local ssh tunnel created
2004 for the connection to the host. This function does not
beeps32a63082013-08-22 14:02:29 -07002005 directly alter the state of a previously returned RPC
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002006 client object; however disconnection will cause all
2007 subsequent calls to methods on the object to fail.
2008
2009 This function does nothing if requested to disconnect a port
beeps32a63082013-08-22 14:02:29 -07002010 that was not previously connected via _setup_rpc.
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002011
2012 @param port Port number passed to a previous call to
beeps32a63082013-08-22 14:02:29 -07002013 `_setup_rpc()`.
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002014 """
beeps32a63082013-08-22 14:02:29 -07002015 if port not in self._rpc_proxy_map:
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002016 return
Christopher Wileydd181852013-10-10 19:56:58 -07002017 remote_name, tunnel_proc, remote_pid = self._rpc_proxy_map[port]
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002018 if remote_name:
2019 # We use 'pkill' to find our target process rather than
2020 # a PID, because the host may have rebooted since
2021 # connecting, and we don't want to kill an innocent
2022 # process with the same PID.
2023 #
2024 # 'pkill' helpfully exits with status 1 if no target
2025 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -07002026 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002027 # status.
2028 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
Christopher Wileydd181852013-10-10 19:56:58 -07002029 if remote_pid:
2030 logging.info('Waiting for RPC server "%s" shutdown',
2031 remote_name)
2032 start_time = time.time()
2033 while (time.time() - start_time <
2034 self._RPC_SHUTDOWN_TIMEOUT_SECONDS):
2035 running_processes = self.run(
2036 "pgrep -f '%s'" % remote_name,
2037 ignore_status=True).stdout.split()
2038 if not remote_pid in running_processes:
2039 logging.info('Shut down RPC server.')
2040 break
2041 time.sleep(self._RPC_SHUTDOWN_POLLING_PERIOD_SECONDS)
2042 else:
2043 raise error.TestError('Failed to shutdown RPC server %s' %
2044 remote_name)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002045
2046 if tunnel_proc.poll() is None:
2047 tunnel_proc.terminate()
2048 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
2049 else:
2050 logging.debug('Tunnel pid %d terminated early, status %d',
2051 tunnel_proc.pid, tunnel_proc.returncode)
beeps32a63082013-08-22 14:02:29 -07002052 del self._rpc_proxy_map[port]
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002053
2054
beeps32a63082013-08-22 14:02:29 -07002055 def rpc_disconnect_all(self):
2056 """Disconnect all known RPC proxy ports."""
2057 for port in self._rpc_proxy_map.keys():
2058 self.rpc_disconnect(port)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002059
2060
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002061 def poor_mans_rpc(self, fun):
2062 """
2063 Calls a function from client utils on the host and returns a string.
2064
2065 @param fun function in client utils namespace.
2066 @return output string from calling fun.
2067 """
Simran Basi263a9d32014-08-19 11:16:51 -07002068 script = 'cd %s/bin; ' % autotest.Autotest.get_installed_autodir(self)
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002069 script += 'python -c "import common; import utils;'
2070 script += 'print utils.%s"' % fun
2071 return script
2072
2073
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002074 def _ping_check_status(self, status):
2075 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002076
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002077 @param status Check the ping status against this value.
2078 @return True iff `status` and the result of ping are the same
2079 (i.e. both True or both False).
2080
2081 """
2082 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
2083 return not (status ^ (ping_val == 0))
2084
2085 def _ping_wait_for_status(self, status, timeout):
2086 """Wait for the host to have a given status (UP or DOWN).
2087
2088 Status is checked by polling. Polling will not last longer
2089 than the number of seconds in `timeout`. The polling
2090 interval will be long enough that only approximately
2091 _PING_WAIT_COUNT polling cycles will be executed, subject
2092 to a maximum interval of about one minute.
2093
2094 @param status Waiting will stop immediately if `ping` of the
2095 host returns this status.
2096 @param timeout Poll for at most this many seconds.
2097 @return True iff the host status from `ping` matched the
2098 requested status at the time of return.
2099
2100 """
2101 # _ping_check_status() takes about 1 second, hence the
2102 # "- 1" in the formula below.
2103 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
2104 end_time = time.time() + timeout
2105 while time.time() <= end_time:
2106 if self._ping_check_status(status):
2107 return True
2108 if poll_interval > 0:
2109 time.sleep(poll_interval)
2110
2111 # The last thing we did was sleep(poll_interval), so it may
2112 # have been too long since the last `ping`. Check one more
2113 # time, just to be sure.
2114 return self._ping_check_status(status)
2115
2116 def ping_wait_up(self, timeout):
2117 """Wait for the host to respond to `ping`.
2118
2119 N.B. This method is not a reliable substitute for
2120 `wait_up()`, because a host that responds to ping will not
2121 necessarily respond to ssh. This method should only be used
2122 if the target DUT can be considered functional even if it
2123 can't be reached via ssh.
2124
2125 @param timeout Minimum time to allow before declaring the
2126 host to be non-responsive.
2127 @return True iff the host answered to ping before the timeout.
2128
2129 """
2130 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002131
Andrew Bresticker678c0c72013-01-22 10:44:09 -08002132 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002133 """Wait until the host no longer responds to `ping`.
2134
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002135 This function can be used as a slightly faster version of
2136 `wait_down()`, by avoiding potentially long ssh timeouts.
2137
2138 @param timeout Minimum time to allow for the host to become
2139 non-responsive.
2140 @return True iff the host quit answering ping before the
2141 timeout.
2142
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002143 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002144 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002145
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002146 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002147 """Wait for the client to enter low-power sleep mode.
2148
2149 The test for "is asleep" can't distinguish a system that is
2150 powered off; to confirm that the unit was asleep, it is
2151 necessary to force resume, and then call
2152 `test_wait_for_resume()`.
2153
2154 This function is expected to be called from a test as part
2155 of a sequence like the following:
2156
2157 ~~~~~~~~
2158 boot_id = host.get_boot_id()
2159 # trigger sleep on the host
2160 host.test_wait_for_sleep()
2161 # trigger resume on the host
2162 host.test_wait_for_resume(boot_id)
2163 ~~~~~~~~
2164
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002165 @param sleep_timeout time limit in seconds to allow the host sleep.
2166
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002167 @exception TestFail The host did not go to sleep within
2168 the allowed time.
2169 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002170 if sleep_timeout is None:
2171 sleep_timeout = self.SLEEP_TIMEOUT
2172
2173 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002174 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002175 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002176
2177
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002178 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002179 """Wait for the client to resume from low-power sleep mode.
2180
2181 The `old_boot_id` parameter should be the value from
2182 `get_boot_id()` obtained prior to entering sleep mode. A
2183 `TestFail` exception is raised if the boot id changes.
2184
2185 See @ref test_wait_for_sleep for more on this function's
2186 usage.
2187
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002188 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002189 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002190 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002191
2192 @exception TestFail The host did not respond within the
2193 allowed time.
2194 @exception TestFail The host responded, but the boot id test
2195 indicated a reboot rather than a sleep
2196 cycle.
2197 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002198 if resume_timeout is None:
2199 resume_timeout = self.RESUME_TIMEOUT
2200
2201 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002202 raise error.TestFail(
2203 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002204 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002205 else:
2206 new_boot_id = self.get_boot_id()
2207 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002208 logging.error('client rebooted (old boot %s, new boot %s)',
2209 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002210 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002211 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002212
2213
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002214 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002215 """Wait for the client to shut down.
2216
2217 The test for "has shut down" can't distinguish a system that
2218 is merely asleep; to confirm that the unit was down, it is
2219 necessary to force boot, and then call test_wait_for_boot().
2220
2221 This function is expected to be called from a test as part
2222 of a sequence like the following:
2223
2224 ~~~~~~~~
2225 boot_id = host.get_boot_id()
2226 # trigger shutdown on the host
2227 host.test_wait_for_shutdown()
2228 # trigger boot on the host
2229 host.test_wait_for_boot(boot_id)
2230 ~~~~~~~~
2231
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002232 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002233 @exception TestFail The host did not shut down within the
2234 allowed time.
2235 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002236 if shutdown_timeout is None:
2237 shutdown_timeout = self.SHUTDOWN_TIMEOUT
2238
2239 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002240 raise error.TestFail(
2241 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002242 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002243
2244
2245 def test_wait_for_boot(self, old_boot_id=None):
2246 """Wait for the client to boot from cold power.
2247
2248 The `old_boot_id` parameter should be the value from
2249 `get_boot_id()` obtained prior to shutting down. A
2250 `TestFail` exception is raised if the boot id does not
2251 change. The boot id test is omitted if `old_boot_id` is not
2252 specified.
2253
2254 See @ref test_wait_for_shutdown for more on this function's
2255 usage.
2256
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002257 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002258 shut down.
2259
2260 @exception TestFail The host did not respond within the
2261 allowed time.
2262 @exception TestFail The host responded, but the boot id test
2263 indicated that there was no reboot.
2264 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002265 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002266 raise error.TestFail(
2267 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002268 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002269 elif old_boot_id:
2270 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002271 logging.error('client not rebooted (boot %s)',
2272 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002273 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002274 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07002275
2276
2277 @staticmethod
2278 def check_for_rpm_support(hostname):
2279 """For a given hostname, return whether or not it is powered by an RPM.
2280
Simran Basi1df55112013-09-06 11:25:09 -07002281 @param hostname: hostname to check for rpm support.
2282
Simran Basid5e5e272012-09-24 15:23:59 -07002283 @return None if this host does not follows the defined naming format
2284 for RPM powered DUT's in the lab. If it does follow the format,
2285 it returns a regular expression MatchObject instead.
2286 """
Fang Dengbaff9082015-01-06 13:46:15 -08002287 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002288
2289
2290 def has_power(self):
2291 """For this host, return whether or not it is powered by an RPM.
2292
2293 @return True if this host is in the CROS lab and follows the defined
2294 naming format.
2295 """
Fang Deng0ca40e22013-08-27 17:47:44 -07002296 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002297
2298
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002299 def _set_power(self, state, power_method):
2300 """Sets the power to the host via RPM, Servo or manual.
2301
2302 @param state Specifies which power state to set to DUT
2303 @param power_method Specifies which method of power control to
2304 use. By default "RPM" will be used. Valid values
2305 are the strings "RPM", "manual", "servoj10".
2306
2307 """
2308 ACCEPTABLE_STATES = ['ON', 'OFF']
2309
2310 if state.upper() not in ACCEPTABLE_STATES:
2311 raise error.TestError('State must be one of: %s.'
2312 % (ACCEPTABLE_STATES,))
2313
2314 if power_method == self.POWER_CONTROL_SERVO:
2315 logging.info('Setting servo port J10 to %s', state)
2316 self.servo.set('prtctl3_pwren', state.lower())
2317 time.sleep(self._USB_POWER_TIMEOUT)
2318 elif power_method == self.POWER_CONTROL_MANUAL:
2319 logging.info('You have %d seconds to set the AC power to %s.',
2320 self._POWER_CYCLE_TIMEOUT, state)
2321 time.sleep(self._POWER_CYCLE_TIMEOUT)
2322 else:
2323 if not self.has_power():
2324 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07002325 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
2326 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
2327 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07002328 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07002329
2330
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002331 def power_off(self, power_method=POWER_CONTROL_RPM):
2332 """Turn off power to this host via RPM, Servo or manual.
2333
2334 @param power_method Specifies which method of power control to
2335 use. By default "RPM" will be used. Valid values
2336 are the strings "RPM", "manual", "servoj10".
2337
2338 """
2339 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07002340
2341
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002342 def power_on(self, power_method=POWER_CONTROL_RPM):
2343 """Turn on power to this host via RPM, Servo or manual.
2344
2345 @param power_method Specifies which method of power control to
2346 use. By default "RPM" will be used. Valid values
2347 are the strings "RPM", "manual", "servoj10".
2348
2349 """
2350 self._set_power('ON', power_method)
2351
2352
2353 def power_cycle(self, power_method=POWER_CONTROL_RPM):
2354 """Cycle power to this host by turning it OFF, then ON.
2355
2356 @param power_method Specifies which method of power control to
2357 use. By default "RPM" will be used. Valid values
2358 are the strings "RPM", "manual", "servoj10".
2359
2360 """
2361 if power_method in (self.POWER_CONTROL_SERVO,
2362 self.POWER_CONTROL_MANUAL):
2363 self.power_off(power_method=power_method)
2364 time.sleep(self._POWER_CYCLE_TIMEOUT)
2365 self.power_on(power_method=power_method)
2366 else:
2367 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002368
2369
2370 def get_platform(self):
2371 """Determine the correct platform label for this host.
2372
2373 @returns a string representing this host's platform.
2374 """
2375 crossystem = utils.Crossystem(self)
2376 crossystem.init()
2377 # Extract fwid value and use the leading part as the platform id.
2378 # fwid generally follow the format of {platform}.{firmware version}
2379 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
2380 platform = crossystem.fwid().split('.')[0].lower()
2381 # Newer platforms start with 'Google_' while the older ones do not.
2382 return platform.replace('google_', '')
2383
2384
Hung-ying Tyanb1328032014-04-01 14:18:54 +08002385 def get_architecture(self):
2386 """Determine the correct architecture label for this host.
2387
2388 @returns a string representing this host's architecture.
2389 """
2390 crossystem = utils.Crossystem(self)
2391 crossystem.init()
2392 return crossystem.arch()
2393
2394
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002395 def get_chrome_version(self):
2396 """Gets the Chrome version number and milestone as strings.
2397
2398 Invokes "chrome --version" to get the version number and milestone.
2399
2400 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
2401 current Chrome version number as a string (in the form "W.X.Y.Z")
2402 and "milestone" is the first component of the version number
2403 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
2404 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
2405 of "chrome --version" and the milestone will be the empty string.
2406
2407 """
MK Ryu35d661e2014-09-25 17:44:10 -07002408 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002409 return utils.parse_chrome_version(version_string)
2410
Aviv Keshet74c89a92013-02-04 15:18:30 -08002411 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07002412 def get_board(self):
2413 """Determine the correct board label for this host.
2414
2415 @returns a string representing this host's board.
2416 """
2417 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
2418 run_method=self.run)
2419 board = release_info['CHROMEOS_RELEASE_BOARD']
2420 # Devices in the lab generally have the correct board name but our own
2421 # development devices have {board_name}-signed-{key_type}. The board
2422 # name may also begin with 'x86-' which we need to keep.
Simran Basi833814b2013-01-29 13:13:43 -08002423 board_format_string = ds_constants.BOARD_PREFIX + '%s'
Simran Basic6f1f7a2012-10-16 10:47:46 -07002424 if 'x86' not in board:
Simran Basi833814b2013-01-29 13:13:43 -08002425 return board_format_string % board.split('-')[0]
2426 return board_format_string % '-'.join(board.split('-')[0:2])
Simran Basic6f1f7a2012-10-16 10:47:46 -07002427
2428
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002429 @label_decorator('board_freq_mem')
2430 def get_board_with_frequency_and_memory(self):
2431 """
2432 Determines the board name with frequency and memory.
2433
2434 @returns a more detailed string representing the board. Examples are
2435 butterfly_1.1GHz_2GB, link_1.8GHz_4GB, x86-zgb_1.7GHz_2GB
2436 """
2437 board = self.run(self.poor_mans_rpc(
2438 'get_board_with_frequency_and_memory()')).stdout
2439 return 'board_freq_mem:%s' % str.strip(board)
2440
2441
Aviv Keshet74c89a92013-02-04 15:18:30 -08002442 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002443 def has_lightsensor(self):
2444 """Determine the correct board label for this host.
2445
2446 @returns the string 'lightsensor' if this host has a lightsensor or
2447 None if it does not.
2448 """
2449 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08002450 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07002451 try:
2452 # Run the search cmd following the symlinks. Stderr_tee is set to
2453 # None as there can be a symlink loop, but the command will still
2454 # execute correctly with a few messages printed to stderr.
2455 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
2456 return 'lightsensor'
2457 except error.AutoservRunError:
2458 # egrep exited with a return code of 1 meaning none of the possible
2459 # lightsensor files existed.
2460 return None
2461
2462
Aviv Keshet74c89a92013-02-04 15:18:30 -08002463 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002464 def has_bluetooth(self):
2465 """Determine the correct board label for this host.
2466
2467 @returns the string 'bluetooth' if this host has bluetooth or
2468 None if it does not.
2469 """
2470 try:
2471 self.run('test -d /sys/class/bluetooth/hci0')
2472 # test exited with a return code of 0.
2473 return 'bluetooth'
2474 except error.AutoservRunError:
2475 # test exited with a return code 1 meaning the directory did not
2476 # exist.
2477 return None
2478
2479
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002480 @label_decorator('gpu_family')
2481 def get_gpu_family(self):
2482 """
2483 Determine GPU family.
2484
2485 @returns a string representing the gpu family. Examples are mali, tegra,
2486 pinetrail, sandybridge, ivybridge, haswell and baytrail.
2487 """
2488 gpu_family = self.run(self.poor_mans_rpc('get_gpu_family()')).stdout
2489 return 'gpu_family:%s' % str.strip(gpu_family)
2490
2491
Ilja Friedel0ce0b602013-08-15 18:45:27 -07002492 @label_decorator('graphics')
2493 def get_graphics(self):
2494 """
2495 Determine the correct board label for this host.
2496
2497 @returns a string representing this host's graphics. For now ARM boards
2498 return graphics:gles while all other boards return graphics:gl. This
2499 may change over time, but for robustness reasons this should avoid
2500 executing code in actual graphics libraries (which may not be ready and
2501 is tested by graphics_GLAPICheck).
2502 """
2503 uname = self.run('uname -a').stdout.lower()
2504 if 'arm' in uname:
2505 return 'graphics:gles'
2506 return 'graphics:gl'
2507
2508
Bill Richardson4f595f52014-02-13 16:20:26 -08002509 @label_decorator('ec')
2510 def get_ec(self):
2511 """
2512 Determine the type of EC on this host.
2513
2514 @returns a string representing this host's embedded controller type.
2515 At present, it only returns "ec:cros", for Chrome OS ECs. Other types
2516 of EC (or none) don't return any strings, since no tests depend on
2517 those.
2518 """
2519 cmd = 'mosys ec info'
2520 # The output should look like these, so that the last field should
2521 # match our EC version scheme:
2522 #
2523 # stm | stm32f100 | snow_v1.3.139-375eb9f
2524 # ti | Unknown-10de | peppy_v1.5.114-5d52788
2525 #
2526 # Non-Chrome OS ECs will look like these:
2527 #
2528 # ENE | KB932 | 00BE107A00
2529 # ite | it8518 | 3.08
2530 #
2531 # And some systems don't have ECs at all (Lumpy, for example).
2532 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
2533
2534 ecinfo = self.run(command=cmd, ignore_status=True)
2535 if ecinfo.exit_status == 0:
2536 res = re.search(regexp, ecinfo.stdout)
2537 if res:
2538 logging.info("EC version is %s", res.groups()[0])
2539 return 'ec:cros'
2540 logging.info("%s got: %s", cmd, ecinfo.stdout)
2541 # Has an EC, but it's not a Chrome OS EC
2542 return None
2543 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
2544 # No EC present
2545 return None
2546
2547
Alec Berg31b932b2014-04-04 16:09:11 -07002548 @label_decorator('accels')
2549 def get_accels(self):
2550 """
2551 Determine the type of accelerometers on this host.
2552
2553 @returns a string representing this host's accelerometer type.
2554 At present, it only returns "accel:cros-ec", for accelerometers
2555 attached to a Chrome OS EC, or none, if no accelerometers.
2556 """
2557 # Check to make sure we have ectool
2558 rv = self.run('which ectool', ignore_status=True)
2559 if rv.exit_status:
2560 logging.info("No ectool cmd found, assuming no EC accelerometers")
2561 return None
2562
2563 # Check that the EC supports the motionsense command
2564 rv = self.run('ectool motionsense', ignore_status=True)
2565 if rv.exit_status:
2566 logging.info("EC does not support motionsense command "
2567 "assuming no EC accelerometers")
2568 return None
2569
2570 # Check that EC motion sensors are active
2571 active = self.run('ectool motionsense active').stdout.split('\n')
2572 if active[0] == "0":
2573 logging.info("Motion sense inactive, assuming no EC accelerometers")
2574 return None
2575
2576 logging.info("EC accelerometers found")
2577 return 'accel:cros-ec'
2578
2579
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002580 @label_decorator('chameleon')
2581 def has_chameleon(self):
2582 """Determine if a Chameleon connected to this host.
2583
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08002584 @returns a list containing two strings ('chameleon' and
2585 'chameleon:' + label, e.g. 'chameleon:hdmi') if this host
2586 has a Chameleon or None if it has not.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002587 """
2588 if self._chameleon_host:
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08002589 return ['chameleon', 'chameleon:' + self.chameleon.get_label()]
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002590 else:
2591 return None
2592
2593
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +08002594 @label_decorator('audio_loopback_dongle')
2595 def has_loopback_dongle(self):
2596 """Determine if an audio loopback dongle is plugged to this host.
2597
2598 @returns 'audio_loopback_dongle' when there is an audio loopback dongle
2599 plugged to this host.
2600 None when there is no audio loopback dongle
2601 plugged to this host.
2602 """
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +08002603 nodes_info = self.run(command=cras_utils.get_cras_nodes_cmd(),
2604 ignore_status=True).stdout
2605 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
2606 cras_utils.node_type_is_plugged('MIC', nodes_info)):
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +08002607 return 'audio_loopback_dongle'
2608 else:
2609 return None
2610
2611
Derek Basehorec71ff622014-07-07 15:18:40 -07002612 @label_decorator('power_supply')
2613 def get_power_supply(self):
2614 """
2615 Determine what type of power supply the host has
2616
2617 @returns a string representing this host's power supply.
2618 'power:battery' when the device has a battery intended for
2619 extended use
2620 'power:AC_primary' when the device has a battery not intended
2621 for extended use (for moving the machine, etc)
2622 'power:AC_only' when the device has no battery at all.
2623 """
2624 psu = self.run(command='mosys psu type', ignore_status=True)
2625 if psu.exit_status:
2626 # The psu command for mosys is not included for all platforms. The
2627 # assumption is that the device will have a battery if the command
2628 # is not found.
2629 return 'power:battery'
2630
2631 psu_str = psu.stdout.strip()
2632 if psu_str == 'unknown':
2633 return None
2634
2635 return 'power:%s' % psu_str
2636
2637
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002638 @label_decorator('storage')
2639 def get_storage(self):
2640 """
2641 Determine the type of boot device for this host.
2642
2643 Determine if the internal device is SCSI or dw_mmc device.
2644 Then check that it is SSD or HDD or eMMC or something else.
2645
2646 @returns a string representing this host's internal device type.
2647 'storage:ssd' when internal device is solid state drive
2648 'storage:hdd' when internal device is hard disk drive
2649 'storage:mmc' when internal device is mmc drive
2650 None When internal device is something else or
2651 when we are unable to determine the type
2652 """
2653 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
2654 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2655 '. /usr/share/misc/chromeos-common.sh;',
2656 'load_base_vars;',
2657 'get_fixed_dst_drive'])
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002658 rootdev = self.run(command=rootdev_cmd, ignore_status=True)
2659 if rootdev.exit_status:
2660 logging.info("Fail to run %s", rootdev_cmd)
2661 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002662 rootdev_str = rootdev.stdout.strip()
2663
2664 if not rootdev_str:
2665 return None
2666
2667 rootdev_base = os.path.basename(rootdev_str)
2668
2669 mmc_pattern = '/dev/mmcblk[0-9]'
2670 if re.match(mmc_pattern, rootdev_str):
2671 # Use type to determine if the internal device is eMMC or somthing
2672 # else. We can assume that MMC is always an internal device.
2673 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002674 type = self.run(command=type_cmd, ignore_status=True)
2675 if type.exit_status:
2676 logging.info("Fail to run %s", type_cmd)
2677 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002678 type_str = type.stdout.strip()
2679
2680 if type_str == 'MMC':
2681 return 'storage:mmc'
2682
2683 scsi_pattern = '/dev/sd[a-z]+'
2684 if re.match(scsi_pattern, rootdev.stdout):
2685 # Read symlink for /sys/block/sd* to determine if the internal
2686 # device is connected via ata or usb.
2687 link_cmd = 'readlink /sys/block/%s' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002688 link = self.run(command=link_cmd, ignore_status=True)
2689 if link.exit_status:
2690 logging.info("Fail to run %s", link_cmd)
2691 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002692 link_str = link.stdout.strip()
2693 if 'usb' in link_str:
2694 return None
2695
2696 # Read rotation to determine if the internal device is ssd or hdd.
2697 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
2698 % rootdev_base)
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002699 rotate = self.run(command=rotate_cmd, ignore_status=True)
2700 if rotate.exit_status:
2701 logging.info("Fail to run %s", rotate_cmd)
2702 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002703 rotate_str = rotate.stdout.strip()
2704
2705 rotate_dict = {'0':'storage:ssd', '1':'storage:hdd'}
2706 return rotate_dict.get(rotate_str)
2707
2708 # All other internal device / error case will always fall here
2709 return None
2710
2711
Dan Shi4e9a2aa2014-03-24 14:28:42 -07002712 @label_decorator('servo')
2713 def get_servo(self):
2714 """Determine if the host has a servo attached.
2715
2716 If the host has a working servo attached, it should have a servo label.
2717
2718 @return: string 'servo' if the host has servo attached. Otherwise,
2719 returns None.
2720 """
2721 return 'servo' if self._servo_host else None
2722
2723
Dan Shi5beba472014-05-28 22:46:07 -07002724 @label_decorator('video_labels')
2725 def get_video_labels(self):
2726 """Run /usr/local/bin/avtest_label_detect to get a list of video labels.
2727
2728 Sample output of avtest_label_detect:
2729 Detected label: hw_video_acc_vp8
2730 Detected label: webcam
2731
2732 @return: A list of labels detected by tool avtest_label_detect.
2733 """
2734 try:
2735 result = self.run('/usr/local/bin/avtest_label_detect').stdout
2736 return re.findall('^Detected label: (\w+)$', result, re.M)
2737 except error.AutoservRunError:
2738 # The tool is not installed.
2739 return []
2740
2741
mussa584b4462014-06-20 15:13:28 -07002742 @label_decorator('video_glitch_detection')
2743 def is_video_glitch_detection_supported(self):
2744 """ Determine if a board under test is supported for video glitch
2745 detection tests.
2746
2747 @return: 'video_glitch_detection' if board is supported, None otherwise.
2748 """
2749 parser = ConfigParser.SafeConfigParser()
2750 filename = os.path.join(
2751 common.autotest_dir, 'client/cros/video/device_spec.conf')
2752
2753 dut = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2754
2755 try:
2756 parser.read(filename)
mussa584b4462014-06-20 15:13:28 -07002757 supported_boards = parser.sections()
2758
Mussa83c84d62014-10-02 12:11:28 -07002759 return 'video_glitch_detection' if dut in supported_boards else None
mussa584b4462014-06-20 15:13:28 -07002760
2761 except ConfigParser.error:
2762 # something went wrong while parsing the conf file
2763 return None
2764
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002765 @label_decorator('touch_labels')
2766 def get_touch(self):
2767 """
2768 Determine whether board under test has a touchpad or touchscreen.
2769
2770 @return: A list of some combination of 'touchscreen' and 'touchpad',
2771 depending on what is present on the device.
Katherine Threlkeldab83d392015-06-18 16:45:57 -07002772
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002773 """
2774 labels = []
Katherine Threlkeldab83d392015-06-18 16:45:57 -07002775 looking_for = ['touchpad', 'touchscreen']
2776 player = input_playback.InputPlayback()
2777 input_events = self.run('ls /dev/input/event*').stdout.strip().split()
2778 filename = '/tmp/touch_labels'
2779 for event in input_events:
2780 self.run('evtest %s > %s' % (event, filename), timeout=1,
2781 ignore_timeout=True)
2782 properties = self.run('cat %s' % filename).stdout
2783 input_type = player._determine_input_type(properties)
2784 if input_type in looking_for:
2785 labels.append(input_type)
2786 looking_for.remove(input_type)
2787 if len(looking_for) == 0:
2788 break
2789 self.run('rm %s' % filename)
2790
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002791 return labels
2792
Hung-ying Tyana39b0542015-06-30 10:36:42 +08002793
2794 @label_decorator('internal_display')
2795 def has_internal_display(self):
2796 """Determine if the device under test is equipped with an internal
2797 display.
2798
2799 @return: 'internal_display' if one is present; None otherwise.
2800 """
2801 from autotest_lib.client.cros.graphics import graphics_utils
2802 from autotest_lib.client.common_lib import utils as common_utils
2803
2804 def __system_output(cmd):
2805 return self.run(cmd).stdout
2806
2807 def __read_file(remote_path):
2808 return self.run('cat %s' % remote_path).stdout
2809
2810 # Hijack the necessary client functions so that we can take advantage
2811 # of the client lib here.
2812 # FIXME: find a less hacky way than this
2813 original_system_output = utils.system_output
2814 original_read_file = common_utils.read_file
2815 utils.system_output = __system_output
2816 common_utils.read_file = __read_file
2817 try:
2818 return ('internal_display' if graphics_utils.has_internal_display()
2819 else None)
2820 finally:
2821 utils.system_output = original_system_output
2822 common_utils.read_file = original_read_file
2823
2824
Eric Carusoee673ac2015-08-05 17:03:04 -07002825 @label_decorator('lucidsleep')
2826 def has_lucid_sleep_support(self):
2827 """Determine if the device under test has support for lucid sleep.
2828
2829 @return 'lucidsleep' if this board supports lucid sleep; None otherwise
2830 """
2831 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2832 return 'lucidsleep' if board in LUCID_SLEEP_BOARDS else None
2833
2834
Dan Shi85276d42014-04-08 22:11:45 -07002835 def is_boot_from_usb(self):
2836 """Check if DUT is boot from USB.
2837
2838 @return: True if DUT is boot from usb.
2839 """
2840 device = self.run('rootdev -s -d').stdout.strip()
2841 removable = int(self.run('cat /sys/block/%s/removable' %
2842 os.path.basename(device)).stdout.strip())
2843 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002844
2845
2846 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002847 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002848
2849 @param key: meminfo requested
2850
2851 @return the memory value as a string
2852
2853 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002854 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2855 logging.debug('%s', meminfo)
2856 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002857
2858
2859 def get_board_type(self):
2860 """
2861 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002862 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2863
2864 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002865 """
Danny Chan471a8d12015-08-18 14:57:41 -07002866 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2867 ignore_status=True).stdout
2868 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002869 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002870 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002871
2872
2873 def get_os_type(self):
2874 return 'cros'