blob: d91f1ab5804a2ce8042fe49c989b79feba9f28aa [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 Barnette1d78b012012-05-15 13:56:30 -070012import subprocess
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070013import time
J. Richard Barnette1d78b012012-05-15 13:56:30 -070014import xmlrpclib
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070015
mussa584b4462014-06-20 15:13:28 -070016import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070017from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070018from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080019from autotest_lib.client.common_lib import error
20from autotest_lib.client.common_lib import global_config
Dan Shi549fb822015-03-24 18:01:11 -070021from autotest_lib.client.common_lib import lsbrelease_utils
J. Richard Barnette45e93de2012-04-11 17:24:15 -070022from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette03a0c132012-11-05 12:40:35 -080023from autotest_lib.client.common_lib.cros import dev_server
Christopher Wileyd78249a2013-03-01 13:05:31 -080024from autotest_lib.client.common_lib.cros import retry
Gabe Blackb72f4fb2015-01-20 16:47:13 -080025from autotest_lib.client.common_lib.cros.graphite import autotest_es
Gabe Black1e1c41b2015-02-04 23:55:15 -080026from autotest_lib.client.common_lib.cros.graphite import autotest_stats
MK Ryu35d661e2014-09-25 17:44:10 -070027from autotest_lib.client.cros import constants as client_constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080028from autotest_lib.client.cros import cros_ui
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +080029from autotest_lib.client.cros.audio import cras_utils
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
beepsc87ff602013-07-31 21:53:00 -070051class FactoryImageCheckerException(error.AutoservError):
52 """Exception raised when an image is a factory image."""
53 pass
54
55
Aviv Keshet74c89a92013-02-04 15:18:30 -080056def add_label_detector(label_function_list, label_list=None, label=None):
57 """Decorator used to group functions together into the provided list.
58 @param label_function_list: List of label detecting functions to add
59 decorated function to.
60 @param label_list: List of detectable labels to add detectable labels to.
61 (Default: None)
62 @param label: Label string that is detectable by this detection function
63 (Default: None)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -080064 """
Simran Basic6f1f7a2012-10-16 10:47:46 -070065 def add_func(func):
Aviv Keshet74c89a92013-02-04 15:18:30 -080066 """
67 @param func: The function to be added as a detector.
68 """
69 label_function_list.append(func)
70 if label and label_list is not None:
71 label_list.append(label)
Simran Basic6f1f7a2012-10-16 10:47:46 -070072 return func
73 return add_func
74
75
Fang Deng0ca40e22013-08-27 17:47:44 -070076class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070077 """Chromium OS specific subclass of Host."""
78
79 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -050080 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070081
Richard Barnette03a0c132012-11-05 12:40:35 -080082 # Timeout values (in seconds) associated with various Chrome OS
83 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070084 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080085 # In general, a good rule of thumb is that the timeout can be up
86 # to twice the typical measured value on the slowest platform.
87 # The times here have not necessarily been empirically tested to
88 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070089 #
90 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080091 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
92 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080093 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070094 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080095 # other things, this must account for the 30 second dev-mode
J. Richard Barnetted4649c62013-03-06 17:42:27 -080096 # screen delay and time to start the network.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070097 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080098 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080099 # network.
beepsf079cfb2013-09-18 17:49:51 -0700100 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800101 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
102 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700103
104 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -0800105 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +0800106 SHUTDOWN_TIMEOUT = 10
J. Richard Barnettefbcc7122013-07-24 18:24:59 -0700107 BOOT_TIMEOUT = 60
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700108 USB_BOOT_TIMEOUT = 150
J. Richard Barnette7817b052014-08-28 09:47:29 -0700109 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -0800110 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -0700111
Dan Shica503482015-03-30 17:23:25 -0700112 # Minimum OS version that supports server side packaging. Older builds may
113 # not have server side package built or with Autotest code change to support
114 # server-side packaging.
115 MIN_VERSION_SUPPORT_SSP = 6919
116
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800117 # REBOOT_TIMEOUT: How long to wait for a reboot.
118 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700119 # We have a long timeout to ensure we don't flakily fail due to other
120 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700121 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
122 # return from reboot' bug is solved.
123 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700124
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800125 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
126 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
127 _USB_POWER_TIMEOUT = 5
128 _POWER_CYCLE_TIMEOUT = 10
129
beeps32a63082013-08-22 14:02:29 -0700130 _RPC_PROXY_URL = 'http://localhost:%d'
Christopher Wileydd181852013-10-10 19:56:58 -0700131 _RPC_SHUTDOWN_POLLING_PERIOD_SECONDS = 2
Peter Qiu4410db72014-06-05 10:32:41 -0700132 # Set shutdown timeout to account for the time for restarting the UI.
133 _RPC_SHUTDOWN_TIMEOUT_SECONDS = cros_ui.RESTART_UI_TIMEOUT
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800134
Richard Barnette82c35912012-11-20 10:09:10 -0800135 _RPM_RECOVERY_BOARDS = global_config.global_config.get_config_value('CROS',
136 'rpm_recovery_boards', type=str).split(',')
137
138 _MAX_POWER_CYCLE_ATTEMPTS = 6
139 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Fang Dengdeba14f2014-11-14 11:54:09 -0800140 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
141 '-host(\d+)')
Gwendal Grignoua66f1d12014-12-03 10:07:26 -0800142 _LIGHT_SENSOR_FILES = [ "in_illuminance0_input",
143 "in_illuminance_input",
144 "in_illuminance0_raw",
145 "in_illuminance_raw",
146 "illuminance0_input"]
Richard Barnette82c35912012-11-20 10:09:10 -0800147 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
148 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800149 _DETECTABLE_LABELS = []
150 label_decorator = functools.partial(add_label_detector, _LABEL_FUNCTIONS,
151 _DETECTABLE_LABELS)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700152
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800153 # Constants used in ping_wait_up() and ping_wait_down().
154 #
155 # _PING_WAIT_COUNT is the approximate number of polling
156 # cycles to use when waiting for a host state change.
157 #
158 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
159 # for arguments to the internal _ping_wait_for_status()
160 # method.
161 _PING_WAIT_COUNT = 40
162 _PING_STATUS_DOWN = False
163 _PING_STATUS_UP = True
164
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800165 # Allowed values for the power_method argument.
166
167 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
168 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
169 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
170 POWER_CONTROL_RPM = 'RPM'
171 POWER_CONTROL_SERVO = 'servoj10'
172 POWER_CONTROL_MANUAL = 'manual'
173
174 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
175 POWER_CONTROL_SERVO,
176 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800177
Simran Basi5e6339a2013-03-21 11:34:32 -0700178 _RPM_OUTLET_CHANGED = 'outlet_changed'
179
Dan Shi9cb0eec2014-06-03 09:04:50 -0700180 # URL pattern to download firmware image.
181 _FW_IMAGE_URL_PATTERN = global_config.global_config.get_config_value(
182 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700183
MK Ryu35d661e2014-09-25 17:44:10 -0700184 # File that has a list of directories to be collected
185 _LOGS_TO_COLLECT_FILE = os.path.join(
186 common.client_dir, 'common_lib', 'logs_to_collect')
187
188 # Prefix of logging message w.r.t. crash collection
189 _CRASHLOGS_PREFIX = 'collect_crashlogs'
190
191 # Time duration waiting for host up/down check
192 _CHECK_HOST_UP_TIMEOUT_SECS = 15
193
194 # A command that interacts with kernel and hardware (e.g., rm, mkdir, etc)
195 # might not be completely done deep through the hardware when the machine
196 # is powered down right after the command returns.
197 # We should wait for a few seconds to make them done. Finger crossed.
198 _SAFE_WAIT_SECS = 10
199
200
J. Richard Barnette964fba02012-10-24 17:34:29 -0700201 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800202 def check_host(host, timeout=10):
203 """
204 Check if the given host is a chrome-os host.
205
206 @param host: An ssh host representing a device.
207 @param timeout: The timeout for the run command.
208
209 @return: True if the host device is chromeos.
210
beeps46dadc92013-11-07 14:07:10 -0800211 """
212 try:
Christopher Wiley1ea80942014-02-26 16:45:08 -0800213 result = host.run('grep -q CHROMEOS /etc/lsb-release && '
Simran Basie5f7ae42014-06-26 15:44:06 -0700214 '! which adb >/dev/null 2>&1 && '
215 '! grep -q moblab /etc/lsb-release',
Christopher Wileyfc3eac02013-11-21 16:24:57 -0800216 ignore_status=True, timeout=timeout)
beeps46dadc92013-11-07 14:07:10 -0800217 except (error.AutoservRunError, error.AutoservSSHTimeout):
218 return False
219 return result.exit_status == 0
220
221
222 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800223 def _extract_arguments(args_dict, key_subset):
224 """Extract options from `args_dict` and return a subset result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800225
226 Take the provided dictionary of argument options and return
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800227 a subset that represent standard arguments needed to construct
228 a test-assistant object (chameleon or servo) for a host. The
229 intent is to provide standard argument processing from
230 run_remote_tests for tests that require a test-assistant board
231 to operate.
232
233 @param args_dict Dictionary from which to extract the arguments.
234 @param key_subset Tuple of keys to extract from the args_dict, e.g.
235 ('servo_host', 'servo_port').
236 """
237 result = {}
238 for arg in key_subset:
239 if arg in args_dict:
240 result[arg] = args_dict[arg]
241 return result
242
243
244 @staticmethod
245 def get_chameleon_arguments(args_dict):
246 """Extract chameleon options from `args_dict` and return the result.
247
248 Recommended usage:
249 ~~~~~~~~
250 args_dict = utils.args_to_dict(args)
251 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
252 host = hosts.create_host(machine, chameleon_args=chameleon_args)
253 ~~~~~~~~
254
255 @param args_dict Dictionary from which to extract the chameleon
256 arguments.
257 """
258 return CrosHost._extract_arguments(
259 args_dict, ('chameleon_host', 'chameleon_port'))
260
261
262 @staticmethod
263 def get_servo_arguments(args_dict):
264 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800265
266 Recommended usage:
267 ~~~~~~~~
268 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700269 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800270 host = hosts.create_host(machine, servo_args=servo_args)
271 ~~~~~~~~
272
273 @param args_dict Dictionary from which to extract the servo
274 arguments.
275 """
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800276 return CrosHost._extract_arguments(
277 args_dict, ('servo_host', 'servo_port'))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700278
J. Richard Barnette964fba02012-10-24 17:34:29 -0700279
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800280 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
Fang Denge545abb2014-12-30 18:43:47 -0800281 try_lab_servo=False, ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700282 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800283 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700284
Fang Denge545abb2014-12-30 18:43:47 -0800285 This method will attempt to create the test-assistant object
286 (chameleon/servo) when it is needed by the test. Check
287 the docstring of chameleon_host.create_chameleon_host and
288 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700289
Fang Denge545abb2014-12-30 18:43:47 -0800290 @param hostname: Hostname of the dut.
291 @param chameleon_args: A dictionary that contains args for creating
292 a ChameleonHost. See chameleon_host for details.
293 @param servo_args: A dictionary that contains args for creating
294 a ServoHost object. See servo_host for details.
295 @param try_lab_servo: Boolean, False indicates that ServoHost should
296 not be created for a device in Cros test lab.
297 See servo_host for details.
298 @param ssh_verbosity_flag: String, to pass to the ssh command to control
299 verbosity.
300 @param ssh_options: String, other ssh options to pass to the ssh
301 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700302 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700303 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700304 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700305 # self.env is a dictionary of environment variable settings
306 # to be exported for commands run on the host.
307 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
308 # errors that might happen.
309 self.env['LIBC_FATAL_STDERR_'] = '1'
beeps32a63082013-08-22 14:02:29 -0700310 self._rpc_proxy_map = {}
Fang Dengd1c2b732013-08-20 12:59:46 -0700311 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700312 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700313 # TODO(fdeng): We need to simplify the
314 # process of servo and servo_host initialization.
315 # crbug.com/298432
Fang Denge545abb2014-12-30 18:43:47 -0800316 self._servo_host = servo_host.create_servo_host(
317 dut=self.hostname, servo_args=servo_args,
318 try_lab_servo=try_lab_servo)
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800319 # TODO(waihong): Do the simplication on Chameleon too.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800320 self._chameleon_host = chameleon_host.create_chameleon_host(
321 dut=self.hostname, chameleon_args=chameleon_args)
322
Dan Shi4d478522014-02-14 13:46:32 -0800323 if self._servo_host is not None:
324 self.servo = self._servo_host.get_servo()
325 else:
326 self.servo = None
327
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800328 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800329 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800330 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800331 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700332
333
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500334 def get_repair_image_name(self):
335 """Generate a image_name from variables in the global config.
336
337 @returns a str of $board-version/$BUILD.
338
339 """
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500340 board = self._get_board_from_afe()
341 if board is None:
342 raise error.AutoservError('DUT has no board attribute, '
343 'cannot be repaired.')
Dan Shi6964fa52014-12-18 11:04:27 -0800344 stable_version = self._AFE.run('get_stable_version', board=board)
345 build_pattern = global_config.global_config.get_config_value(
346 'CROS', 'stable_build_pattern')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500347 return build_pattern % (board, stable_version)
348
349
Scott Zawalski62bacae2013-03-05 10:40:32 -0500350 def _host_in_AFE(self):
351 """Check if the host is an object the AFE knows.
352
353 @returns the host object.
354 """
355 return self._AFE.get_hosts(hostname=self.hostname)
356
357
Chris Sosab76e0ee2013-05-22 16:55:41 -0700358 def lookup_job_repo_url(self):
359 """Looks up the job_repo_url for the host.
360
361 @returns job_repo_url from AFE or None if not found.
362
363 @raises KeyError if the host does not have a job_repo_url
364 """
365 if not self._host_in_AFE():
366 return None
367
368 hosts = self._AFE.get_hosts(hostname=self.hostname)
beepsb5efc532013-06-04 11:29:34 -0700369 if hosts and ds_constants.JOB_REPO_URL in hosts[0].attributes:
370 return hosts[0].attributes[ds_constants.JOB_REPO_URL]
Chris Sosab76e0ee2013-05-22 16:55:41 -0700371
372
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500373 def clear_cros_version_labels_and_job_repo_url(self):
374 """Clear cros_version labels and host attribute job_repo_url."""
Scott Zawalski62bacae2013-03-05 10:40:32 -0500375 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400376 return
377
Scott Zawalski62bacae2013-03-05 10:40:32 -0500378 host_list = [self.hostname]
379 labels = self._AFE.get_labels(
380 name__startswith=ds_constants.VERSION_PREFIX,
381 host__hostname=self.hostname)
Dan Shi0f466e82013-02-22 15:44:58 -0800382
Scott Zawalski62bacae2013-03-05 10:40:32 -0500383 for label in labels:
384 label.remove_hosts(hosts=host_list)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500385
beepscb6f1e22013-06-28 19:14:10 -0700386 self.update_job_repo_url(None, None)
387
388
389 def update_job_repo_url(self, devserver_url, image_name):
390 """
391 Updates the job_repo_url host attribute and asserts it's value.
392
393 @param devserver_url: The devserver to use in the job_repo_url.
394 @param image_name: The name of the image to use in the job_repo_url.
395
396 @raises AutoservError: If we failed to update the job_repo_url.
397 """
398 repo_url = None
399 if devserver_url and image_name:
400 repo_url = tools.get_package_url(devserver_url, image_name)
401 self._AFE.set_host_attribute(ds_constants.JOB_REPO_URL, repo_url,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500402 hostname=self.hostname)
beepscb6f1e22013-06-28 19:14:10 -0700403 if self.lookup_job_repo_url() != repo_url:
404 raise error.AutoservError('Failed to update job_repo_url with %s, '
405 'host %s' % (repo_url, self.hostname))
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500406
407
Dan Shie9309262013-06-19 22:50:21 -0700408 def add_cros_version_labels_and_job_repo_url(self, image_name):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400409 """Add cros_version labels and host attribute job_repo_url.
410
411 @param image_name: The name of the image e.g.
412 lumpy-release/R27-3837.0.0
Dan Shi7458bf62013-06-10 12:50:16 -0700413
Scott Zawalskieadbf702013-03-14 09:23:06 -0400414 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500415 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400416 return
Scott Zawalski62bacae2013-03-05 10:40:32 -0500417
Scott Zawalskieadbf702013-03-14 09:23:06 -0400418 cros_label = '%s%s' % (ds_constants.VERSION_PREFIX, image_name)
Dan Shie9309262013-06-19 22:50:21 -0700419 devserver_url = dev_server.ImageServer.resolve(image_name).url()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500420
421 labels = self._AFE.get_labels(name=cros_label)
422 if labels:
423 label = labels[0]
424 else:
425 label = self._AFE.create_label(name=cros_label)
426
427 label.add_hosts([self.hostname])
beepscb6f1e22013-06-28 19:14:10 -0700428 self.update_job_repo_url(devserver_url, image_name)
429
430
beepsdae65fd2013-07-26 16:24:41 -0700431 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700432 """
433 Make sure job_repo_url of this host is valid.
434
joychen03eaad92013-06-26 09:55:21 -0700435 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700436 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
437 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
438 download and extract it. If the devserver embedded in the url is
439 unresponsive, update the job_repo_url of the host after staging it on
440 another devserver.
441
442 @param job_repo_url: A url pointing to the devserver where the autotest
443 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700444 @param tag: The tag from the server job, in the format
445 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700446
447 @raises DevServerException: If we could not resolve a devserver.
448 @raises AutoservError: If we're unable to save the new job_repo_url as
449 a result of choosing a new devserver because the old one failed to
450 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700451 @raises urllib2.URLError: If the devserver embedded in job_repo_url
452 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700453 """
454 job_repo_url = self.lookup_job_repo_url()
455 if not job_repo_url:
456 logging.warning('No job repo url set on host %s', self.hostname)
457 return
458
459 logging.info('Verifying job repo url %s', job_repo_url)
460 devserver_url, image_name = tools.get_devserver_build_from_package_url(
461 job_repo_url)
462
beeps0c865032013-07-30 11:37:06 -0700463 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700464
465 logging.info('Staging autotest artifacts for %s on devserver %s',
466 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700467
468 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700469 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700470 stage_time = time.time() - start_time
471
472 # Record how much of the verification time comes from a devserver
473 # restage. If we're doing things right we should not see multiple
474 # devservers for a given board/build/branch path.
475 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800476 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700477 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800478 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700479 pass
480 else:
beeps0c865032013-07-30 11:37:06 -0700481 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700482 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700483 stats_key = {
484 'board': board,
485 'build_type': build_type,
486 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700487 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700488 }
Gabe Black1e1c41b2015-02-04 23:55:15 -0800489 autotest_stats.Gauge('verify_job_repo_url').send(
beeps687243d2013-07-18 15:29:27 -0700490 '%(board)s.%(build_type)s.%(branch)s.%(devserver)s' % stats_key,
491 stage_time)
beepscb6f1e22013-06-28 19:14:10 -0700492
Scott Zawalskieadbf702013-03-14 09:23:06 -0400493
Dan Shicf4d2032015-03-12 15:04:21 -0700494 def stage_server_side_package(self, image=None):
495 """Stage autotest server-side package on devserver.
496
497 @param image: Full path of an OS image to install or a build name.
498
499 @return: A url to the autotest server-side package.
500 """
501 if image:
502 image_name = tools.get_build_from_image(image)
503 if not image_name:
504 raise error.AutoservError(
505 'Failed to parse build name from %s' % image)
506 ds = dev_server.ImageServer.resolve(image_name)
507 else:
508 job_repo_url = self.lookup_job_repo_url()
509 if job_repo_url:
510 devserver_url, image_name = (
511 tools.get_devserver_build_from_package_url(job_repo_url))
512 ds = dev_server.ImageServer(devserver_url)
513 else:
514 labels = self._AFE.get_labels(
515 name__startswith=ds_constants.VERSION_PREFIX,
516 host__hostname=self.hostname)
517 if not labels:
518 raise error.AutoservError(
519 'Failed to stage server-side package. The host has '
520 'no job_report_url attribute or version label.')
521 image_name = labels[0].name[len(ds_constants.VERSION_PREFIX):]
522 ds = dev_server.ImageServer.resolve(image_name)
Dan Shica503482015-03-30 17:23:25 -0700523
524 # Get the OS version of the build, for any build older than
525 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
526 match = re.match('.*/R\d+-(\d+)\.', image_name)
527 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
528 logging.warn('Build %s is older than %s. Server side packaging is '
529 'disabled.', image_name, self.MIN_VERSION_SUPPORT_SSP)
530 return None
531
Dan Shicf4d2032015-03-12 15:04:21 -0700532 ds.stage_artifacts(image_name, ['autotest_server_package'])
533 return '%s/static/%s/%s' % (ds.url(), image_name,
534 'autotest_server_package.tar.bz2')
535
536
Dan Shi0f466e82013-02-22 15:44:58 -0800537 def _try_stateful_update(self, update_url, force_update, updater):
538 """Try to use stateful update to initialize DUT.
539
540 When DUT is already running the same version that machine_install
541 tries to install, stateful update is a much faster way to clean up
542 the DUT for testing, compared to a full reimage. It is implemeted
543 by calling autoupdater.run_update, but skipping updating root, as
544 updating the kernel is time consuming and not necessary.
545
546 @param update_url: url of the image.
547 @param force_update: Set to True to update the image even if the DUT
548 is running the same version.
549 @param updater: ChromiumOSUpdater instance used to update the DUT.
550 @returns: True if the DUT was updated with stateful update.
551
552 """
J. Richard Barnette3f731032014-04-07 17:42:59 -0700553 # TODO(jrbarnette): Yes, I hate this re.match() test case.
554 # It's better than the alternative: see crbug.com/360944.
555 image_name = autoupdater.url_to_image_name(update_url)
556 release_pattern = r'^.*-release/R[0-9]+-[0-9]+\.[0-9]+\.0$'
557 if not re.match(release_pattern, image_name):
558 return False
Dan Shi0f466e82013-02-22 15:44:58 -0800559 if not updater.check_version():
560 return False
561 if not force_update:
562 logging.info('Canceling stateful update because the new and '
563 'old versions are the same.')
564 return False
565 # Following folders should be rebuilt after stateful update.
566 # A test file is used to confirm each folder gets rebuilt after
567 # the stateful update.
568 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
569 test_file = '.test_file_to_be_deleted'
570 for folder in folders_to_check:
571 touch_path = os.path.join(folder, test_file)
572 self.run('touch %s' % touch_path)
573
574 if not updater.run_update(force_update=True, update_root=False):
575 return False
576
577 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700578 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800579 check_file_cmd = 'test -f %s; echo $?'
580 for folder in folders_to_check:
581 test_file_path = os.path.join(folder, test_file)
582 result = self.run(check_file_cmd % test_file_path,
583 ignore_status=True)
584 if result.exit_status == 1:
585 return False
586 return True
587
588
J. Richard Barnette7275b612013-06-04 18:13:11 -0700589 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800590 """After the DUT is updated, confirm machine_install succeeded.
591
592 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700593 @param expected_kernel: kernel expected to be active after reboot,
594 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800595
596 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700597 # Touch the lab machine file to leave a marker that
598 # distinguishes this image from other test images.
599 # Afterwards, we must re-run the autoreboot script because
600 # it depends on the _LAB_MACHINE_FILE.
Dan Shi0f466e82013-02-22 15:44:58 -0800601 self.run('touch %s' % self._LAB_MACHINE_FILE)
Dan Shi0f466e82013-02-22 15:44:58 -0800602 self.run('start autoreboot')
Chris Sosa65425082013-10-16 13:26:22 -0700603 updater.verify_boot_expectations(
604 expected_kernel, rollback_message=
605 'Build %s failed to boot on %s; system rolled back to previous'
606 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700607 # Check that we've got the build we meant to install.
608 if not updater.check_version_to_confirm_install():
609 raise autoupdater.ChromiumOSError(
610 'Failed to update %s to build %s; found build '
611 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700612 updater.update_version,
Dan Shi0942b1d2015-03-31 11:07:00 -0700613 self.get_release_version()))
Dan Shi0f466e82013-02-22 15:44:58 -0800614
615
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700616 def _stage_image_for_update(self, image_name=None):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400617 """Stage a build on a devserver and return the update_url.
618
619 @param image_name: a name like lumpy-release/R27-3837.0.0
620 @returns an update URL like:
621 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
622 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700623 if not image_name:
624 image_name = self.get_repair_image_name()
625 logging.info('Staging build for AU: %s', image_name)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400626 devserver = dev_server.ImageServer.resolve(image_name)
627 devserver.trigger_download(image_name, synchronous=False)
628 return tools.image_url_pattern() % (devserver.url(), image_name)
629
630
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700631 def stage_image_for_servo(self, image_name=None):
632 """Stage a build on a devserver and return the update_url.
633
634 @param image_name: a name like lumpy-release/R27-3837.0.0
635 @returns an update URL like:
636 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
637 """
638 if not image_name:
639 image_name = self.get_repair_image_name()
640 logging.info('Staging build for servo install: %s', image_name)
641 devserver = dev_server.ImageServer.resolve(image_name)
642 devserver.stage_artifacts(image_name, ['test_image'])
643 return devserver.get_test_image_url(image_name)
644
645
beepse539be02013-07-31 21:57:39 -0700646 def stage_factory_image_for_servo(self, image_name):
647 """Stage a build on a devserver and return the update_url.
648
649 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700650
beepse539be02013-07-31 21:57:39 -0700651 @return: An update URL, eg:
652 http://<devserver>/static/canary-channel/\
653 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700654
655 @raises: ValueError if the factory artifact name is missing from
656 the config.
657
beepse539be02013-07-31 21:57:39 -0700658 """
659 if not image_name:
660 logging.error('Need an image_name to stage a factory image.')
661 return
662
beeps12c0a3c2013-09-03 11:58:27 -0700663 factory_artifact = global_config.global_config.get_config_value(
664 'CROS', 'factory_artifact', type=str, default='')
665 if not factory_artifact:
666 raise ValueError('Cannot retrieve the factory artifact name from '
667 'autotest config, and hence cannot stage factory '
668 'artifacts.')
669
beepse539be02013-07-31 21:57:39 -0700670 logging.info('Staging build for servo install: %s', image_name)
671 devserver = dev_server.ImageServer.resolve(image_name)
672 devserver.stage_artifacts(
673 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700674 [factory_artifact],
675 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700676
677 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
678
679
Chris Sosaa3ac2152012-05-23 22:23:13 -0700680 def machine_install(self, update_url=None, force_update=False,
Fang Deng3d3b9272014-12-22 12:20:28 -0800681 local_devserver=False, repair=False,
682 force_full_update=False):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500683 """Install the DUT.
684
Dan Shi0f466e82013-02-22 15:44:58 -0800685 Use stateful update if the DUT is already running the same build.
686 Stateful update does not update kernel and tends to run much faster
687 than a full reimage. If the DUT is running a different build, or it
688 failed to do a stateful update, full update, including kernel update,
689 will be applied to the DUT.
690
Scott Zawalskieadbf702013-03-14 09:23:06 -0400691 Once a host enters machine_install its cros_version label will be
692 removed as well as its host attribute job_repo_url (used for
693 package install).
694
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500695 @param update_url: The url to use for the update
696 pattern: http://$devserver:###/update/$build
697 If update_url is None and repair is True we will install the
Dan Shi6964fa52014-12-18 11:04:27 -0800698 stable image listed in afe_stable_versions table. If the table
699 is not setup, global_config value under CROS.stable_cros_version
700 will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500701 @param force_update: Force an update even if the version installed
702 is the same. Default:False
703 @param local_devserver: Used by run_remote_test to allow people to
704 use their local devserver. Default: False
705 @param repair: Whether or not we are in repair mode. This adds special
706 cases for repairing a machine like starting update_engine.
707 Setting repair to True sets force_update to True as well.
708 default: False
Fang Deng3d3b9272014-12-22 12:20:28 -0800709 @param force_full_update: If True, do not attempt to run stateful
710 update, force a full reimage. If False, try stateful update
711 first when the dut is already installed with the same version.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500712 @raises autoupdater.ChromiumOSError
713
714 """
Dan Shi7458bf62013-06-10 12:50:16 -0700715 if update_url:
716 logging.debug('update url is set to %s', update_url)
717 else:
718 logging.debug('update url is not set, resolving...')
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700719 if self._parser.options.image:
720 requested_build = self._parser.options.image
721 if requested_build.startswith('http://'):
722 update_url = requested_build
Dan Shi7458bf62013-06-10 12:50:16 -0700723 logging.debug('update url is retrieved from requested_build'
724 ': %s', update_url)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700725 else:
726 # Try to stage any build that does not start with
727 # http:// on the devservers defined in
728 # global_config.ini.
Dan Shi7458bf62013-06-10 12:50:16 -0700729 update_url = self._stage_image_for_update(requested_build)
730 logging.debug('Build staged, and update_url is set to: %s',
731 update_url)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700732 elif repair:
733 update_url = self._stage_image_for_update()
Dan Shi7458bf62013-06-10 12:50:16 -0700734 logging.debug('Build staged, and update_url is set to: %s',
735 update_url)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400736 else:
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700737 raise autoupdater.ChromiumOSError(
738 'Update failed. No update URL provided.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500739
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500740 if repair:
J. Richard Barnette158f1792015-02-24 17:43:53 -0800741 # In case the system is in a bad state, we always reboot
742 # the machine before trying to repair.
743 #
744 # If Chrome is crashing, the ui-respawn job may reboot
745 # the DUT to try and "fix" it. Guard against that
746 # behavior by stopping the 'ui' job.
747 #
748 # If Chrome failed to start, update-engine won't be running,
749 # so restart it by force.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700750 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
J. Richard Barnette158f1792015-02-24 17:43:53 -0800751 self.run('stop ui || true')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500752 self.run('stop update-engine; start update-engine')
753 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800754
Chris Sosaa3ac2152012-05-23 22:23:13 -0700755 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
Chris Sosa72312602013-04-16 15:01:56 -0700756 local_devserver=local_devserver)
Dan Shi0f466e82013-02-22 15:44:58 -0800757 updated = False
Scott Zawalskieadbf702013-03-14 09:23:06 -0400758 # Remove cros-version and job_repo_url host attribute from host.
759 self.clear_cros_version_labels_and_job_repo_url()
Dan Shi0f466e82013-02-22 15:44:58 -0800760 # If the DUT is already running the same build, try stateful update
761 # first. Stateful update does not update kernel and tends to run much
762 # faster than a full reimage.
Fang Deng3d3b9272014-12-22 12:20:28 -0800763 if not force_full_update:
764 try:
765 updated = self._try_stateful_update(
766 update_url, force_update, updater)
767 if updated:
768 logging.info('DUT is updated with stateful update.')
769 except Exception as e:
770 logging.exception(e)
771 logging.warning('Failed to stateful update DUT, force to update.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700772
Dan Shi0f466e82013-02-22 15:44:58 -0800773 inactive_kernel = None
774 # Do a full update if stateful update is not applicable or failed.
775 if not updated:
Chris Sosab7612bc2013-03-21 10:32:37 -0700776 # TODO(sosa): Remove temporary hack to get rid of bricked machines
777 # that can't update due to a corrupted policy.
778 self.run('rm -rf /var/lib/whitelist')
Danny Chanad490bd2014-11-04 14:15:57 -0800779 self.run('mkdir /var/lib/whitelist')
Chris Sosab7612bc2013-03-21 10:32:37 -0700780 self.run('chmod -w /var/lib/whitelist')
Scott Zawalskib550d5a2013-03-22 09:23:59 -0400781 self.run('stop update-engine; start update-engine')
Chris Sosab7612bc2013-03-21 10:32:37 -0700782
Dan Shi0f466e82013-02-22 15:44:58 -0800783 if updater.run_update(force_update):
784 updated = True
785 # Figure out active and inactive kernel.
786 active_kernel, inactive_kernel = updater.get_kernel_state()
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700787
Dan Shi0f466e82013-02-22 15:44:58 -0800788 # 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.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700795
Dan Shi0f466e82013-02-22 15:44:58 -0800796 # Updater has returned successfully; reboot the host.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700797 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi5699ac22014-12-19 10:55:49 -0800798
799 if updated:
Dan Shi0f466e82013-02-22 15:44:58 -0800800 self._post_update_processing(updater, inactive_kernel)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400801 image_name = autoupdater.url_to_image_name(update_url)
Dan Shie9309262013-06-19 22:50:21 -0700802 self.add_cros_version_labels_and_job_repo_url(image_name)
Simran Basi13fa1ba2013-03-04 10:56:47 -0800803
Simran Basiae08c8c2014-09-02 11:17:26 -0700804 logging.debug('Cleaning up old autotest directories.')
805 try:
806 installed_autodir = autotest.Autotest.get_installed_autodir(self)
807 self.run('rm -rf ' + installed_autodir)
808 except autotest.AutodirNotFoundError:
809 logging.debug('No autotest installed directory found.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700810
811
Dan Shi9cb0eec2014-06-03 09:04:50 -0700812 def _clear_fw_version_labels(self):
813 """Clear firmware version labels from the machine."""
814 labels = self._AFE.get_labels(
815 name__startswith=provision.FW_VERSION_PREFIX,
816 host__hostname=self.hostname)
817 for label in labels:
818 label.remove_hosts(hosts=[self.hostname])
819
820
821 def _add_fw_version_label(self, build):
822 """Add firmware version label to the machine.
823
824 @param build: Build of firmware.
825
826 """
827 fw_label = provision.fw_version_to_label(build)
828 provision.ensure_label_exists(fw_label)
829 label = self._AFE.get_labels(name__startswith=fw_label)[0]
830 label.add_hosts([self.hostname])
831
832
833 def firmware_install(self, build=None):
834 """Install firmware to the DUT.
835
836 Use stateful update if the DUT is already running the same build.
837 Stateful update does not update kernel and tends to run much faster
838 than a full reimage. If the DUT is running a different build, or it
839 failed to do a stateful update, full update, including kernel update,
840 will be applied to the DUT.
841
842 Once a host enters firmware_install its fw_version label will be
843 removed. After the firmware is updated successfully, a new fw_version
844 label will be added to the host.
845
846 @param build: The build version to which we want to provision the
847 firmware of the machine,
848 e.g. 'link-firmware/R22-2695.1.144'.
849
850 TODO(dshi): After bug 381718 is fixed, update here with corresponding
851 exceptions that could be raised.
852
853 """
854 if not self.servo:
855 raise error.TestError('Host %s does not have servo.' %
856 self.hostname)
857
858 # TODO(fdeng): use host.get_board() after
859 # crbug.com/271834 is fixed.
860 board = self._get_board_from_afe()
861
862 # If build is not set, assume it's repair mode and try to install
863 # firmware from stable CrOS.
864 if not build:
865 build = self.get_repair_image_name()
866
867 config = FAFTConfig(board)
868 if config.use_u_boot:
869 ap_image = 'image-%s.bin' % board
870 else: # Depthcharge platform
871 ap_image = 'image.bin'
872 ec_image = 'ec.bin'
873 ds = dev_server.ImageServer.resolve(build)
874 ds.stage_artifacts(build, ['firmware'])
875
876 tmpd = autotemp.tempdir(unique_id='fwimage')
877 try:
878 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
879 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
880 server_utils.system('wget -O %s %s' % (local_tarball, fwurl),
881 timeout=60)
882 server_utils.system('tar xf %s -C %s %s %s' %
883 (local_tarball, tmpd.name, ap_image, ec_image),
884 timeout=60)
885 server_utils.system('tar xf %s --wildcards -C %s "dts/*"' %
886 (local_tarball, tmpd.name),
887 timeout=60, ignore_status=True)
888
889 self._clear_fw_version_labels()
890 logging.info('Will re-program EC now')
891 self.servo.program_ec(os.path.join(tmpd.name, ec_image))
892 logging.info('Will re-program BIOS now')
893 self.servo.program_bios(os.path.join(tmpd.name, ap_image))
894 self.servo.get_power_state_controller().reset()
895 time.sleep(self.servo.BOOT_DELAY)
896 self._add_fw_version_label()
897 finally:
898 tmpd.clean()
899
900
Dan Shi10e992b2013-08-30 11:02:59 -0700901 def show_update_engine_log(self):
902 """Output update engine log."""
MK Ryu35d661e2014-09-25 17:44:10 -0700903 logging.debug('Dumping %s', client_constants.UPDATE_ENGINE_LOG)
904 self.run('cat %s' % client_constants.UPDATE_ENGINE_LOG)
Dan Shi10e992b2013-08-30 11:02:59 -0700905
906
Richard Barnette82c35912012-11-20 10:09:10 -0800907 def _get_board_from_afe(self):
908 """Retrieve this host's board from its labels in the AFE.
909
910 Looks for a host label of the form "board:<board>", and
911 returns the "<board>" part of the label. `None` is returned
912 if there is not a single, unique label matching the pattern.
913
914 @returns board from label, or `None`.
915 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700916 return server_utils.get_board_from_afe(self.hostname, self._AFE)
Simran Basi833814b2013-01-29 13:13:43 -0800917
918
919 def get_build(self):
920 """Retrieve the current build for this Host from the AFE.
921
922 Looks through this host's labels in the AFE to determine its build.
923
924 @returns The current build or None if it could not find it or if there
925 were multiple build labels assigned to this host.
926 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700927 return server_utils.get_build_from_afe(self.hostname, self._AFE)
Richard Barnette82c35912012-11-20 10:09:10 -0800928
929
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500930 def _install_repair(self):
931 """Attempt to repair this host using upate-engine.
932
933 If the host is up, try installing the DUT with a stable
Dan Shi6964fa52014-12-18 11:04:27 -0800934 "repair" version of Chrome OS as defined in afe_stable_versions table.
935 If the table is not setup, global_config value under
936 CROS.stable_cros_version will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500937
Scott Zawalski62bacae2013-03-05 10:40:32 -0500938 @raises AutoservRepairMethodNA if the DUT is not reachable.
939 @raises ChromiumOSError if the install failed for some reason.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500940
941 """
942 if not self.is_up():
Scott Zawalski62bacae2013-03-05 10:40:32 -0500943 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500944 logging.info('Attempting to reimage machine to repair image.')
945 try:
946 self.machine_install(repair=True)
Fang Dengd0672f32013-03-18 17:18:09 -0700947 except autoupdater.ChromiumOSError as e:
948 logging.exception(e)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500949 logging.info('Repair via install failed.')
Scott Zawalski62bacae2013-03-05 10:40:32 -0500950 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500951
952
Dan Shi2c88eed2013-11-12 10:18:38 -0800953 def _install_repair_with_powerwash(self):
Dan Shi9cc48452013-11-12 12:39:26 -0800954 """Attempt to powerwash first then repair this host using update-engine.
Dan Shi2c88eed2013-11-12 10:18:38 -0800955
Dan Shi9cc48452013-11-12 12:39:26 -0800956 update-engine may fail due to a bad image. In such case, powerwash
957 may help to cleanup the DUT for update-engine to work again.
Dan Shi2c88eed2013-11-12 10:18:38 -0800958
959 @raises AutoservRepairMethodNA if the DUT is not reachable.
960 @raises ChromiumOSError if the install failed for some reason.
961
962 """
963 if not self.is_up():
964 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
965
966 logging.info('Attempting to powerwash the DUT.')
967 self.run('echo "fast safe" > '
968 '/mnt/stateful_partition/factory_install_reset')
969 self.reboot(timeout=self.POWERWASH_BOOT_TIMEOUT, wait=True)
970 if not self.is_up():
Dan Shi9cc48452013-11-12 12:39:26 -0800971 logging.error('Powerwash failed. DUT did not come back after '
Dan Shi2c88eed2013-11-12 10:18:38 -0800972 'reboot.')
973 raise error.AutoservRepairFailure(
974 'DUT failed to boot from powerwash after %d seconds' %
975 self.POWERWASH_BOOT_TIMEOUT)
976
977 logging.info('Powerwash succeeded.')
978 self._install_repair()
979
980
beepsf079cfb2013-09-18 17:49:51 -0700981 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
982 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500983 """
984 Re-install the OS on the DUT by:
985 1) installing a test image on a USB storage device attached to the Servo
986 board,
Richard Barnette03a0c132012-11-05 12:40:35 -0800987 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700988 3) installing the image with chromeos-install.
989
Scott Zawalski62bacae2013-03-05 10:40:32 -0500990 @param image_url: If specified use as the url to install on the DUT.
991 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -0700992 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
993 Factory images need a longer usb_boot_timeout than regular
994 cros images.
995 @param install_timeout: The timeout to use when installing the chromeos
996 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -0800997
Scott Zawalski62bacae2013-03-05 10:40:32 -0500998 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -0700999
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001000 """
beepsf079cfb2013-09-18 17:49:51 -07001001 usb_boot_timer_key = ('servo_install.usb_boot_timeout_%s'
1002 % usb_boot_timeout)
1003 logging.info('Downloading image to USB, then booting from it. Usb boot '
1004 'timeout = %s', usb_boot_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -08001005 timer = autotest_stats.Timer(usb_boot_timer_key)
beepsf079cfb2013-09-18 17:49:51 -07001006 timer.start()
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001007 self.servo.install_recovery_image(image_url)
beepsf079cfb2013-09-18 17:49:51 -07001008 if not self.wait_up(timeout=usb_boot_timeout):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001009 raise error.AutoservRepairFailure(
1010 'DUT failed to boot from USB after %d seconds' %
beepsf079cfb2013-09-18 17:49:51 -07001011 usb_boot_timeout)
1012 timer.stop()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001013
beepsf079cfb2013-09-18 17:49:51 -07001014 install_timer_key = ('servo_install.install_timeout_%s'
1015 % install_timeout)
Gabe Black1e1c41b2015-02-04 23:55:15 -08001016 timer = autotest_stats.Timer(install_timer_key)
beepsf079cfb2013-09-18 17:49:51 -07001017 timer.start()
1018 logging.info('Installing image through chromeos-install.')
J. Richard Barnette2522a8f2015-03-04 15:59:15 -08001019 self.run('chromeos-install --yes',
MK Ryu35d661e2014-09-25 17:44:10 -07001020 timeout=install_timeout)
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001021 self.run('halt')
beepsf079cfb2013-09-18 17:49:51 -07001022 timer.stop()
1023
1024 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001025 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -07001026 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001027 # N.B. The Servo API requires that we use power_on() here
1028 # for two reasons:
1029 # 1) After turning on a DUT in recovery mode, you must turn
1030 # it off and then on with power_on() once more to
1031 # disable recovery mode (this is a Parrot specific
1032 # requirement).
1033 # 2) After power_off(), the only way to turn on is with
1034 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -07001035 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001036
1037 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001038 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
1039 raise error.AutoservError('DUT failed to reboot installed '
1040 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -05001041 self.BOOT_TIMEOUT)
1042
1043
J. Richard Barnettee4af8b92013-05-01 13:16:12 -07001044 def _servo_repair_reinstall(self):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001045 """Reinstall the DUT utilizing servo and a test image.
1046
1047 Re-install the OS on the DUT by:
1048 1) installing a test image on a USB storage device attached to the Servo
1049 board,
1050 2) booting that image in recovery mode, and then
1051 3) installing the image with chromeos-install.
1052
Scott Zawalski62bacae2013-03-05 10:40:32 -05001053 @raises AutoservRepairMethodNA if the device does not have servo
1054 support.
1055
1056 """
1057 if not self.servo:
1058 raise error.AutoservRepairMethodNA('Repair Reinstall NA: '
1059 'DUT has no servo support.')
1060
1061 logging.info('Attempting to recovery servo enabled device with '
1062 'servo_repair_reinstall')
1063
J. Richard Barnettee4af8b92013-05-01 13:16:12 -07001064 image_url = self.stage_image_for_servo()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001065 self.servo_install(image_url)
1066
1067
1068 def _servo_repair_power(self):
1069 """Attempt to repair DUT using an attached Servo.
1070
1071 Attempt to power on the DUT via power_long_press.
1072
1073 @raises AutoservRepairMethodNA if the device does not have servo
1074 support.
1075 @raises AutoservRepairFailure if the repair fails for any reason.
1076 """
1077 if not self.servo:
1078 raise error.AutoservRepairMethodNA('Repair Power NA: '
1079 'DUT has no servo support.')
1080
1081 logging.info('Attempting to recover servo enabled device by '
1082 'powering it off and on.')
1083 self.servo.get_power_state_controller().power_off()
1084 self.servo.get_power_state_controller().power_on()
1085 if self.wait_up(self.BOOT_TIMEOUT):
1086 return
1087
1088 raise error.AutoservRepairFailure('DUT did not boot after long_press.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001089
1090
Richard Barnette82c35912012-11-20 10:09:10 -08001091 def _powercycle_to_repair(self):
1092 """Utilize the RPM Infrastructure to bring the host back up.
1093
1094 If the host is not up/repaired after the first powercycle we utilize
1095 auto fallback to the last good install by powercycling and rebooting the
1096 host 6 times.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001097
1098 @raises AutoservRepairMethodNA if the device does not support remote
1099 power.
1100 @raises AutoservRepairFailure if the repair fails for any reason.
1101
Richard Barnette82c35912012-11-20 10:09:10 -08001102 """
Scott Zawalski62bacae2013-03-05 10:40:32 -05001103 if not self.has_power():
1104 raise error.AutoservRepairMethodNA('Device does not support power.')
1105
Richard Barnette82c35912012-11-20 10:09:10 -08001106 logging.info('Attempting repair via RPM powercycle.')
1107 failed_cycles = 0
1108 self.power_cycle()
1109 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
1110 failed_cycles += 1
1111 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
Scott Zawalski62bacae2013-03-05 10:40:32 -05001112 raise error.AutoservRepairFailure(
1113 'Powercycled host %s %d times; device did not come back'
1114 ' online.' % (self.hostname, failed_cycles))
Richard Barnette82c35912012-11-20 10:09:10 -08001115 self.power_cycle()
1116 if failed_cycles == 0:
1117 logging.info('Powercycling was successful first time.')
1118 else:
1119 logging.info('Powercycling was successful after %d failures.',
1120 failed_cycles)
1121
1122
MK Ryu35d661e2014-09-25 17:44:10 -07001123 def _reboot_repair(self):
1124 """SSH to this host and reboot."""
1125 if not self.is_up(self._CHECK_HOST_UP_TIMEOUT_SECS):
1126 raise error.AutoservRepairMethodNA('DUT unreachable for reboot.')
1127 logging.info('Attempting repair via SSH reboot.')
1128 self.reboot(timeout=self.BOOT_TIMEOUT, wait=True)
1129
1130
Prashanth B4d8184f2014-05-05 12:22:02 -07001131 def check_device(self):
1132 """Check if a device is ssh-able, and if so, clean and verify it.
1133
1134 @raise AutoservSSHTimeout: If the ssh ping times out.
1135 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
1136 permissions.
1137 @raise AutoservSshPingHostError: For other AutoservRunErrors during
1138 ssh_ping.
1139 @raises AutoservError: As appropriate, during cleanup and verify.
1140 """
1141 self.ssh_ping()
1142 self.cleanup()
1143 self.verify()
1144
1145
Richard Barnette82c35912012-11-20 10:09:10 -08001146 def repair_full(self):
1147 """Repair a host for repair level NO_PROTECTION.
1148
1149 This overrides the base class function for repair; it does
1150 not call back to the parent class, but instead offers a
1151 simplified implementation based on the capabilities in the
1152 Chrome OS test lab.
1153
Fang Deng5d518f42013-08-02 14:04:32 -07001154 It first verifies and repairs servo if it is a DUT in CrOS
Fang Deng03590af2013-10-07 17:34:20 -07001155 lab and a servo is attached.
Fang Deng5d518f42013-08-02 14:04:32 -07001156
Jakob Juelich82b7d1c2014-09-15 16:10:57 -07001157 This escalates in order through the following procedures and verifies
1158 the status using `self.check_device()` after each of them. This is done
1159 until both the repair and the veryfing step succeed.
1160
MK Ryu35d661e2014-09-25 17:44:10 -07001161 Escalation order of repair procedures from less intrusive to
1162 more intrusive repairs:
1163 1. SSH to the DUT and reboot.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001164 2. If there's a servo for the DUT, try to power the DUT off and
1165 on.
MK Ryu35d661e2014-09-25 17:44:10 -07001166 3. If the DUT can be power-cycled via RPM, try to repair
Richard Barnette82c35912012-11-20 10:09:10 -08001167 by power-cycling.
MK Ryu35d661e2014-09-25 17:44:10 -07001168 4. Try to re-install to a known stable image using
1169 auto-update.
1170 5. If there's a servo for the DUT, try to re-install via
1171 the servo.
Richard Barnette82c35912012-11-20 10:09:10 -08001172
1173 As with the parent method, the last operation performed on
Prashanth B4d8184f2014-05-05 12:22:02 -07001174 the DUT must be to call `self.check_device()`; If that call fails the
1175 exception it raises is passed back to the caller.
J. Richard Barnettefde55fc2013-03-15 17:47:01 -07001176
Scott Zawalski62bacae2013-03-05 10:40:32 -05001177 @raises AutoservRepairTotalFailure if the repair process fails to
1178 fix the DUT.
Fang Deng5d518f42013-08-02 14:04:32 -07001179 @raises ServoHostRepairTotalFailure if the repair process fails to
1180 fix the servo host if one is attached to the DUT.
1181 @raises AutoservSshPermissionDeniedError if it is unable
1182 to ssh to the servo host due to permission error.
1183
Richard Barnette82c35912012-11-20 10:09:10 -08001184 """
Jakob Juelich82b7d1c2014-09-15 16:10:57 -07001185 # Caution: Deleting shards relies on repair to always reboot the DUT.
1186
Dan Shi4d478522014-02-14 13:46:32 -08001187 if self._servo_host and not self.servo:
Fang Deng03590af2013-10-07 17:34:20 -07001188 try:
Dan Shi4d478522014-02-14 13:46:32 -08001189 self._servo_host.repair_full()
Fang Deng03590af2013-10-07 17:34:20 -07001190 except Exception as e:
Fang Deng03590af2013-10-07 17:34:20 -07001191 logging.error('Could not create a healthy servo: %s', e)
Dan Shi4d478522014-02-14 13:46:32 -08001192 self.servo = self._servo_host.get_servo()
Fang Deng5d518f42013-08-02 14:04:32 -07001193
MK Ryu35d661e2014-09-25 17:44:10 -07001194 self.try_collect_crashlogs()
1195
Scott Zawalski62bacae2013-03-05 10:40:32 -05001196 # TODO(scottz): This should use something similar to label_decorator,
1197 # but needs to be populated in order so DUTs are repaired with the
1198 # least amount of effort.
MK Ryu35d661e2014-09-25 17:44:10 -07001199 repair_funcs = [self._reboot_repair,
1200 self._servo_repair_power,
1201 self._powercycle_to_repair,
Dan Shi849a1c42014-03-05 11:10:43 -08001202 self._install_repair,
Dan Shi2c88eed2013-11-12 10:18:38 -08001203 self._install_repair_with_powerwash,
MK Ryu35d661e2014-09-25 17:44:10 -07001204 self._servo_repair_reinstall]
Scott Zawalski62bacae2013-03-05 10:40:32 -05001205 errors = []
Simran Basie6130932013-10-01 14:07:52 -07001206 board = self._get_board_from_afe()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001207 for repair_func in repair_funcs:
1208 try:
1209 repair_func()
MK Ryu35d661e2014-09-25 17:44:10 -07001210 self.try_collect_crashlogs()
Prashanth B4d8184f2014-05-05 12:22:02 -07001211 self.check_device()
Gabe Black1e1c41b2015-02-04 23:55:15 -08001212 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001213 '%s.SUCCEEDED' % repair_func.__name__).increment()
1214 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001215 autotest_stats.Counter(
Dan Shib87c3aa2014-02-12 15:40:31 -08001216 '%s.%s.SUCCEEDED' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001217 board)).increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001218 return
Simran Basie6130932013-10-01 14:07:52 -07001219 except error.AutoservRepairMethodNA as e:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001220 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001221 '%s.RepairNA' % repair_func.__name__).increment()
1222 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001223 autotest_stats.Counter(
Dan Shib87c3aa2014-02-12 15:40:31 -08001224 '%s.%s.RepairNA' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001225 board)).increment()
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001226 logging.warning('Repair function NA: %s', e)
Simran Basie6130932013-10-01 14:07:52 -07001227 errors.append(str(e))
Scott Zawalski62bacae2013-03-05 10:40:32 -05001228 except Exception as e:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001229 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001230 '%s.FAILED' % repair_func.__name__).increment()
1231 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001232 autotest_stats.Counter(
Dan Shib87c3aa2014-02-12 15:40:31 -08001233 '%s.%s.FAILED' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001234 board)).increment()
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001235 logging.warning('Failed to repair device: %s', e)
Scott Zawalski62bacae2013-03-05 10:40:32 -05001236 errors.append(str(e))
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001237
Gabe Black1e1c41b2015-02-04 23:55:15 -08001238 autotest_stats.Counter('Full_Repair_Failed').increment()
Simran Basie6130932013-10-01 14:07:52 -07001239 if board:
Gabe Black1e1c41b2015-02-04 23:55:15 -08001240 autotest_stats.Counter(
Simran Basie6130932013-10-01 14:07:52 -07001241 'Full_Repair_Failed.%s' % board).increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001242 raise error.AutoservRepairTotalFailure(
1243 'All attempts at repairing the device failed:\n%s' %
1244 '\n'.join(errors))
Richard Barnette82c35912012-11-20 10:09:10 -08001245
1246
MK Ryu35d661e2014-09-25 17:44:10 -07001247 def try_collect_crashlogs(self, check_host_up=True):
1248 """
1249 Check if a host is up and logs need to be collected from the host,
1250 if yes, collect them.
1251
1252 @param check_host_up: Flag for checking host is up. Default is True.
1253 """
1254 try:
1255 crash_job = self._need_crash_logs()
1256 if crash_job:
1257 logging.debug('%s: Job %s was crashed', self._CRASHLOGS_PREFIX,
1258 crash_job)
1259 if not check_host_up or self.is_up(
1260 self._CHECK_HOST_UP_TIMEOUT_SECS):
1261 self._collect_crashlogs(crash_job)
1262 logging.debug('%s: Completed collecting logs for the '
1263 'crashed job %s', self._CRASHLOGS_PREFIX,
1264 crash_job)
1265 except Exception as e:
1266 # Exception should not result in repair failure.
1267 # Therefore, suppress all exceptions here.
1268 logging.error('%s: Failed while trying to collect crash-logs: %s',
1269 self._CRASHLOGS_PREFIX, e)
1270
1271
1272 def _need_crash_logs(self):
1273 """Get the value of need_crash_logs attribute of this host.
1274
1275 @return: Value string of need_crash_logs attribute
1276 None if there is no need_crash_logs attribute
1277 """
1278 attrs = self._AFE.get_host_attribute(constants.CRASHLOGS_HOST_ATTRIBUTE,
1279 hostname=self.hostname)
1280 assert len(attrs) < 2
1281 return attrs[0].value if attrs else None
1282
1283
1284 def _collect_crashlogs(self, job_id):
1285 """Grab logs from the host where a job was crashed.
1286
1287 First, check if PRIOR_LOGS_DIR exists in the host.
1288 If yes, collect them.
1289 Otherwise, check if a lab-machine marker (_LAB_MACHINE_FILE) exists
1290 in the host.
1291 If yes, the host was repaired automatically, and we collect normal
1292 system logs.
1293
1294 @param job_id: Id of the job that was crashed.
1295 """
1296 crashlogs_dir = crashcollect.get_crashinfo_dir(self,
1297 constants.CRASHLOGS_DEST_DIR_PREFIX)
1298 flag_prior_logs = False
1299
1300 if self.path_exists(client_constants.PRIOR_LOGS_DIR):
1301 flag_prior_logs = True
1302 self._collect_prior_logs(crashlogs_dir)
1303 elif self.path_exists(self._LAB_MACHINE_FILE):
1304 self._collect_system_logs(crashlogs_dir)
1305 else:
1306 logging.warning('%s: Host was manually re-installed without '
1307 '--lab_preserve_log option. Skip collecting '
1308 'crash-logs.', self._CRASHLOGS_PREFIX)
1309
1310 # We make crash collection be one-time effort.
1311 # _collect_prior_logs() and _collect_system_logs() will not throw
1312 # any exception, and following codes will be executed even when
1313 # those methods fail.
1314 # _collect_crashlogs() is called only when the host is up (refer
1315 # to try_collect_crashlogs()). We assume _collect_prior_logs() and
1316 # _collect_system_logs() fail rarely when the host is up.
1317 # In addition, it is not clear how many times we should try crash
1318 # collection again while not triggering next repair unnecessarily.
1319 # Threfore, we try crash collection one time.
1320
1321 # Create a marker file as soon as log collection is done.
1322 # Leave the job id to this marker for gs_offloader to consume.
1323 marker_file = os.path.join(crashlogs_dir, constants.CRASHLOGS_MARKER)
1324 with open(marker_file, 'a') as f:
1325 f.write('%s\n' % job_id)
1326
1327 # Remove need_crash_logs attribute
1328 logging.debug('%s: Remove attribute need_crash_logs from host %s',
1329 self._CRASHLOGS_PREFIX, self.hostname)
1330 self._AFE.set_host_attribute(constants.CRASHLOGS_HOST_ATTRIBUTE,
1331 None, hostname=self.hostname)
1332
1333 if flag_prior_logs:
1334 logging.debug('%s: Remove %s from host %s', self._CRASHLOGS_PREFIX,
1335 client_constants.PRIOR_LOGS_DIR, self.hostname)
1336 self.run('rm -rf %s; sync' % client_constants.PRIOR_LOGS_DIR)
1337 # Wait for a few seconds to make sure the prior command is
1338 # done deep through storage.
1339 time.sleep(self._SAFE_WAIT_SECS)
1340
1341
1342 def _collect_prior_logs(self, crashlogs_dir):
1343 """Grab prior logs that were stashed before re-installing a host.
1344
1345 @param crashlogs_dir: Directory path where crash-logs are stored.
1346 """
1347 logging.debug('%s: Found %s, collecting them...',
1348 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR)
1349 try:
1350 self.collect_logs(client_constants.PRIOR_LOGS_DIR,
1351 crashlogs_dir, False)
1352 logging.debug('%s: %s is collected',
1353 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR)
1354 except Exception as e:
1355 logging.error('%s: Failed to collect %s: %s',
1356 self._CRASHLOGS_PREFIX, client_constants.PRIOR_LOGS_DIR,
1357 e)
1358
1359
1360 def _collect_system_logs(self, crashlogs_dir):
1361 """Grab normal system logs from a host.
1362
1363 @param crashlogs_dir: Directory path where crash-logs are stored.
1364 """
1365 logging.debug('%s: Found %s, collecting system logs...',
1366 self._CRASHLOGS_PREFIX, self._LAB_MACHINE_FILE)
1367 sources = server_utils.parse_simple_config(self._LOGS_TO_COLLECT_FILE)
1368 for src in sources:
1369 try:
1370 if self.path_exists(src):
1371 logging.debug('%s: Collecting %s...',
1372 self._CRASHLOGS_PREFIX, src)
1373 dest = server_utils.concat_path_except_last(
1374 crashlogs_dir, src)
1375 self.collect_logs(src, dest, False)
1376 logging.debug('%s: %s is collected',
1377 self._CRASHLOGS_PREFIX, src)
1378 except Exception as e:
1379 logging.error('%s: Failed to collect %s: %s',
1380 self._CRASHLOGS_PREFIX, src, e)
1381
1382
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001383 def close(self):
beeps32a63082013-08-22 14:02:29 -07001384 self.rpc_disconnect_all()
Fang Deng0ca40e22013-08-27 17:47:44 -07001385 super(CrosHost, self).close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001386
1387
Dan Shi49ca0932014-11-14 11:22:27 -08001388 def get_power_supply_info(self):
1389 """Get the output of power_supply_info.
1390
1391 power_supply_info outputs the info of each power supply, e.g.,
1392 Device: Line Power
1393 online: no
1394 type: Mains
1395 voltage (V): 0
1396 current (A): 0
1397 Device: Battery
1398 state: Discharging
1399 percentage: 95.9276
1400 technology: Li-ion
1401
1402 Above output shows two devices, Line Power and Battery, with details of
1403 each device listed. This function parses the output into a dictionary,
1404 with key being the device name, and value being a dictionary of details
1405 of the device info.
1406
1407 @return: The dictionary of power_supply_info, e.g.,
1408 {'Line Power': {'online': 'yes', 'type': 'main'},
1409 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001410 @raise error.AutoservRunError if power_supply_info tool is not found in
1411 the DUT. Caller should handle this error to avoid false failure
1412 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001413 """
1414 result = self.run('power_supply_info').stdout.strip()
1415 info = {}
1416 device_name = None
1417 device_info = {}
1418 for line in result.split('\n'):
1419 pair = [v.strip() for v in line.split(':')]
1420 if len(pair) != 2:
1421 continue
1422 if pair[0] == 'Device':
1423 if device_name:
1424 info[device_name] = device_info
1425 device_name = pair[1]
1426 device_info = {}
1427 else:
1428 device_info[pair[0]] = pair[1]
1429 if device_name and not device_name in info:
1430 info[device_name] = device_info
1431 return info
1432
1433
1434 def get_battery_percentage(self):
1435 """Get the battery percentage.
1436
1437 @return: The percentage of battery level, value range from 0-100. Return
1438 None if the battery info cannot be retrieved.
1439 """
1440 try:
1441 info = self.get_power_supply_info()
1442 logging.info(info)
1443 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001444 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001445 return None
1446
1447
1448 def is_ac_connected(self):
1449 """Check if the dut has power adapter connected and charging.
1450
1451 @return: True if power adapter is connected and charging.
1452 """
1453 try:
1454 info = self.get_power_supply_info()
1455 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001456 except (KeyError, error.AutoservRunError):
1457 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001458
1459
Simran Basi5e6339a2013-03-21 11:34:32 -07001460 def _cleanup_poweron(self):
1461 """Special cleanup method to make sure hosts always get power back."""
1462 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1463 hosts = afe.get_hosts(hostname=self.hostname)
1464 if not hosts or not (self._RPM_OUTLET_CHANGED in
1465 hosts[0].attributes):
1466 return
1467 logging.debug('This host has recently interacted with the RPM'
1468 ' Infrastructure. Ensuring power is on.')
1469 try:
1470 self.power_on()
Dan Shi7dca56e2014-11-11 17:07:56 -08001471 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1472 hostname=self.hostname)
Simran Basi5e6339a2013-03-21 11:34:32 -07001473 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001474 logging.error('Failed to turn Power On for this host after '
1475 'cleanup through the RPM Infrastructure.')
Gabe Blackb72f4fb2015-01-20 16:47:13 -08001476 autotest_es.post(
Dan Shi7dca56e2014-11-11 17:07:56 -08001477 type_str='RPM_poweron_failure',
1478 metadata={'hostname': self.hostname})
Dan Shi49ca0932014-11-14 11:22:27 -08001479
1480 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -08001481 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -08001482 raise
1483 elif self.is_ac_connected():
1484 logging.info('The device has power adapter connected and '
1485 'charging. No need to try to turn RPM on '
1486 'again.')
1487 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1488 hostname=self.hostname)
1489 logging.info('Battery level is now at %s%%. The device may '
1490 'still have enough power to run test, so no '
1491 'exception will be raised.', battery_percentage)
1492
Simran Basi5e6339a2013-03-21 11:34:32 -07001493
beepsc87ff602013-07-31 21:53:00 -07001494 def _is_factory_image(self):
1495 """Checks if the image on the DUT is a factory image.
1496
1497 @return: True if the image on the DUT is a factory image.
1498 False otherwise.
1499 """
1500 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1501 return result.exit_status == 0
1502
1503
1504 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001505 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001506
1507 @raises: FactoryImageCheckerException for factory images, since
1508 we cannot attempt to restart ui on them.
1509 error.AutoservRunError for any other type of error that
1510 occurs while restarting ui.
1511 """
1512 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001513 raise FactoryImageCheckerException('Cannot restart ui on factory '
1514 'images')
beepsc87ff602013-07-31 21:53:00 -07001515
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001516 # TODO(jrbarnette): The command to stop/start the ui job
1517 # should live inside cros_ui, too. However that would seem
1518 # to imply interface changes to the existing start()/restart()
1519 # functions, which is a bridge too far (for now).
1520 prompt = cros_ui.get_login_prompt_state(self)
1521 self.run('stop ui; start ui')
1522 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001523
1524
Dan Shi549fb822015-03-24 18:01:11 -07001525 def get_release_version(self):
1526 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1527
1528 @returns The version string in lsb-release, under attribute
1529 CHROMEOS_RELEASE_VERSION.
1530 """
1531 lsb_release_content = self.run(
1532 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1533 return lsbrelease_utils.get_chromeos_release_version(
1534 lsb_release_content=lsb_release_content)
1535
1536
1537 def verify_cros_version_label(self):
1538 """ Make sure host's cros-version label match the actual image in dut.
1539
1540 Remove any cros-version: label that doesn't match that installed in
1541 the dut.
1542
1543 @param raise_error: Set to True to raise exception if any mismatch found
1544
1545 @raise error.AutoservError: If any mismatch between cros-version label
1546 and the build installed in dut is found.
1547 """
1548 labels = self._AFE.get_labels(
1549 name__startswith=ds_constants.VERSION_PREFIX,
1550 host__hostname=self.hostname)
1551 mismatch_found = False
1552 if labels:
1553 # Get CHROMEOS_RELEASE_VERSION from lsb-release, e.g., 6908.0.0.
1554 # Note that it's different from cros-version label, which has
1555 # builder and branch info, e.g.,
1556 # cros-version:peppy-release/R43-6908.0.0
1557 release_version = self.get_release_version()
1558 host_list = [self.hostname]
1559 for label in labels:
1560 # Remove any cros-version label that does not match
1561 # release_version.
1562 build_version = label.name[len(ds_constants.VERSION_PREFIX):]
1563 if not utils.version_match(build_version, release_version):
1564 logging.warn('cros-version label "%s" does not match '
1565 'release version %s. Removing the label.',
1566 label.name, release_version)
1567 label.remove_hosts(hosts=host_list)
1568 mismatch_found = True
1569 if mismatch_found:
Dan Shi1057bae2015-03-30 11:35:09 -07001570 autotest_es.post(use_http=True,
1571 type_str='cros_version_label_mismatch',
1572 metadata={'hostname': self.hostname})
Dan Shi549fb822015-03-24 18:01:11 -07001573 raise error.AutoservError('The host has wrong cros-version label.')
1574
1575
beepsc87ff602013-07-31 21:53:00 -07001576 def cleanup(self):
MK Ryu35d661e2014-09-25 17:44:10 -07001577 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001578 try:
beepsc87ff602013-07-31 21:53:00 -07001579 self._restart_ui()
1580 except (error.AutotestRunError, error.AutoservRunError,
1581 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001582 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001583 # Since restarting the UI fails fall back to normal Autotest
1584 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001585 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001586 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001587 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001588 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001589 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001590
1591
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001592 def reboot(self, **dargs):
1593 """
1594 This function reboots the site host. The more generic
1595 RemoteHost.reboot() performs sync and sleeps for 5
1596 seconds. This is not necessary for Chrome OS devices as the
1597 sync should be finished in a short time during the reboot
1598 command.
1599 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001600 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001601 reboot_timeout = dargs.get('reboot_timeout', 10)
1602 dargs['reboot_cmd'] = ('((reboot & sleep %d; reboot -f &)'
1603 ' </dev/null >/dev/null 2>&1 &)' %
1604 reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001605 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001606 if 'fastsync' not in dargs:
1607 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001608
Charlie Mooneya8e6dab2014-05-29 14:37:55 -07001609 # For purposes of logging reboot times:
1610 # Get the board name i.e. 'daisy_spring'
Michael Liangca4f5a62014-07-10 15:45:13 -07001611 board_fullname = self.get_board()
1612
1613 # Strip the prefix and add it to dargs.
1614 dargs['board'] = board_fullname[board_fullname.find(':')+1:]
Fang Deng0ca40e22013-08-27 17:47:44 -07001615 super(CrosHost, self).reboot(**dargs)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001616
1617
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001618 def suspend(self, **dargs):
1619 """
1620 This function suspends the site host.
1621 """
1622 suspend_time = dargs.get('suspend_time', 60)
1623 dargs['timeout'] = suspend_time
1624 if 'suspend_cmd' not in dargs:
1625 cmd = ' && '.join(['echo 0 > /sys/class/rtc/rtc0/wakealarm',
1626 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
1627 'powerd_dbus_suspend --delay=0 &'])
1628 dargs['suspend_cmd'] = ('(( %s )'
1629 '< /dev/null >/dev/null 2>&1 &)' % cmd)
1630 super(CrosHost, self).suspend(**dargs)
1631
1632
Simran Basiec564392014-08-25 16:48:09 -07001633 def upstart_status(self, service_name):
1634 """Check the status of an upstart init script.
1635
1636 @param service_name: Service to look up.
1637
1638 @returns True if the service is running, False otherwise.
1639 """
1640 return self.run('status %s | grep start/running' %
1641 service_name).stdout.strip() != ''
1642
1643
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001644 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001645 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001646
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001647 Tests for the following conditions:
1648 1. All conditions tested by the parent version of this
1649 function.
1650 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001651 3. Sufficient space in /mnt/stateful_partition/encrypted.
1652 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001653
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001654 """
MK Ryu35d661e2014-09-25 17:44:10 -07001655 # Check if a job was crashed on this host.
1656 # If yes, avoid verification until crash-logs are collected.
1657 if self._need_crash_logs():
1658 raise error.AutoservCrashLogCollectRequired(
1659 'Need to collect crash-logs before verification')
1660
Fang Deng0ca40e22013-08-27 17:47:44 -07001661 super(CrosHost, self).verify_software()
J. Richard Barnette4164d1d2014-12-02 17:52:33 -08001662 self.check_inodes(
1663 '/mnt/stateful_partition',
1664 global_config.global_config.get_config_value(
1665 'SERVER', 'kilo_inodes_required', type=int,
1666 default=100))
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001667 self.check_diskspace(
1668 '/mnt/stateful_partition',
1669 global_config.global_config.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001670 'SERVER', 'gb_diskspace_required', type=float,
1671 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001672 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1673 # Not all targets build with encrypted stateful support.
1674 if self.path_exists(encrypted_stateful_path):
1675 self.check_diskspace(
1676 encrypted_stateful_path,
1677 global_config.global_config.get_config_value(
1678 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1679 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001680
Simran Basiec564392014-08-25 16:48:09 -07001681 if not self.upstart_status('system-services'):
Prashanth B5d0a0512014-04-25 12:26:08 -07001682 raise error.AutoservError('Chrome failed to reach login. '
1683 'System services not running.')
1684
beepsc87ff602013-07-31 21:53:00 -07001685 # Factory images don't run update engine,
1686 # goofy controls dbus on these DUTs.
1687 if not self._is_factory_image():
1688 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -05001689 # Makes sure python is present, loads and can use built in functions.
1690 # We have seen cases where importing cPickle fails with undefined
1691 # symbols in cPickle.so.
1692 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001693
Dan Shi549fb822015-03-24 18:01:11 -07001694 self.verify_cros_version_label()
1695
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001696
Dan Shi49ca0932014-11-14 11:22:27 -08001697 def verify_hardware(self):
1698 """Verify hardware system of a Chrome OS system.
1699
1700 Check following hardware conditions:
1701 1. Battery level.
1702 2. Is power adapter connected.
1703 """
1704 logging.info('Battery percentage: %s', self.get_battery_percentage())
Dan Shie9b765d2014-12-29 16:59:49 -08001705 if self.is_ac_connected() is None:
1706 logging.info('Can not determine if the device has power adapter '
1707 'connected.')
1708 else:
1709 logging.info('Device %s power adapter connected and charging.',
1710 'has' if self.is_ac_connected() else 'does not have')
Dan Shi49ca0932014-11-14 11:22:27 -08001711
1712
Fang Deng96667ca2013-08-01 17:46:18 -07001713 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
1714 connect_timeout=None, alive_interval=None):
1715 """Override default make_ssh_command to use options tuned for Chrome OS.
1716
1717 Tuning changes:
1718 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1719 connection failure. Consistency with remote_access.sh.
1720
1721 - ServerAliveInterval=180; which causes SSH to ping connection every
1722 180 seconds. In conjunction with ServerAliveCountMax ensures
1723 that if the connection dies, Autotest will bail out quickly.
1724 Originally tried 60 secs, but saw frequent job ABORTS where
1725 the test completed successfully.
1726
1727 - ServerAliveCountMax=3; consistency with remote_access.sh.
1728
1729 - ConnectAttempts=4; reduce flakiness in connection errors;
1730 consistency with remote_access.sh.
1731
1732 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1733 Host keys change with every new installation, don't waste
1734 memory/space saving them.
1735
1736 - SSH protocol forced to 2; needed for ServerAliveInterval.
1737
1738 @param user User name to use for the ssh connection.
1739 @param port Port on the target host to use for ssh connection.
1740 @param opts Additional options to the ssh command.
1741 @param hosts_file Ignored.
1742 @param connect_timeout Ignored.
1743 @param alive_interval Ignored.
1744 """
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001745 base_command = ('/usr/bin/ssh -a -x %s %s %s'
1746 ' -o StrictHostKeyChecking=no'
Fang Deng96667ca2013-08-01 17:46:18 -07001747 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
1748 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
1749 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
1750 ' -o Protocol=2 -l %s -p %d')
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001751 return base_command % (self._ssh_verbosity_flag, self._ssh_options,
1752 opts, user, port)
Fang Deng96667ca2013-08-01 17:46:18 -07001753
1754
beeps32a63082013-08-22 14:02:29 -07001755 def _create_ssh_tunnel(self, port, local_port):
1756 """Create an ssh tunnel from local_port to port.
1757
1758 @param port: remote port on the host.
1759 @param local_port: local forwarding port.
1760
1761 @return: the tunnel process.
1762 """
1763 # Chrome OS on the target closes down most external ports
1764 # for security. We could open the port, but doing that
1765 # would conflict with security tests that check that only
1766 # expected ports are open. So, to get to the port on the
1767 # target we use an ssh tunnel.
1768 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
1769 ssh_cmd = self.make_ssh_command(opts=tunnel_options)
1770 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
1771 logging.debug('Full tunnel command: %s', tunnel_cmd)
1772 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
1773 logging.debug('Started ssh tunnel, local = %d'
1774 ' remote = %d, pid = %d',
1775 local_port, port, tunnel_proc.pid)
1776 return tunnel_proc
1777
1778
Christopher Wileydd181852013-10-10 19:56:58 -07001779 def _setup_rpc(self, port, command_name, remote_pid=None):
beeps32a63082013-08-22 14:02:29 -07001780 """Sets up a tunnel process and performs rpc connection book keeping.
1781
1782 This method assumes that xmlrpc and jsonrpc never conflict, since
1783 we can only either have an xmlrpc or a jsonrpc server listening on
1784 a remote port. As such, it enforces a single proxy->remote port
1785 policy, i.e if one starts a jsonrpc proxy/server from port A->B,
1786 and then tries to start an xmlrpc proxy forwarded to the same port,
1787 the xmlrpc proxy will override the jsonrpc tunnel process, however:
1788
1789 1. None of the methods on the xmlrpc proxy will work because
1790 the server listening on B is jsonrpc.
1791
1792 2. The xmlrpc client cannot initiate a termination of the JsonRPC
1793 server, as the only use case currently is goofy, which is tied to
1794 the factory image. It is much easier to handle a failed xmlrpc
1795 call on the client than it is to terminate goofy in this scenario,
1796 as doing the latter might leave the DUT in a hard to recover state.
1797
1798 With the current implementation newer rpc proxy connections will
1799 terminate the tunnel processes of older rpc connections tunneling
1800 to the same remote port. If methods are invoked on the client
1801 after this has happened they will fail with connection closed errors.
1802
1803 @param port: The remote forwarding port.
1804 @param command_name: The name of the remote process, to terminate
1805 using pkill.
1806
1807 @return A url that we can use to initiate the rpc connection.
1808 """
1809 self.rpc_disconnect(port)
1810 local_port = utils.get_unused_port()
1811 tunnel_proc = self._create_ssh_tunnel(port, local_port)
Christopher Wileydd181852013-10-10 19:56:58 -07001812 self._rpc_proxy_map[port] = (command_name, tunnel_proc, remote_pid)
beeps32a63082013-08-22 14:02:29 -07001813 return self._RPC_PROXY_URL % local_port
1814
1815
Christopher Wileyd78249a2013-03-01 13:05:31 -08001816 def xmlrpc_connect(self, command, port, command_name=None,
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001817 ready_test_name=None, timeout_seconds=10,
1818 logfile='/dev/null'):
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001819 """Connect to an XMLRPC server on the host.
1820
1821 The `command` argument should be a simple shell command that
1822 starts an XMLRPC server on the given `port`. The command
1823 must not daemonize, and must terminate cleanly on SIGTERM.
1824 The command is started in the background on the host, and a
1825 local XMLRPC client for the server is created and returned
1826 to the caller.
1827
1828 Note that the process of creating an XMLRPC client makes no
1829 attempt to connect to the remote server; the caller is
1830 responsible for determining whether the server is running
1831 correctly, and is ready to serve requests.
1832
Christopher Wileyd78249a2013-03-01 13:05:31 -08001833 Optionally, the caller can pass ready_test_name, a string
1834 containing the name of a method to call on the proxy. This
1835 method should take no parameters and return successfully only
1836 when the server is ready to process client requests. When
1837 ready_test_name is set, xmlrpc_connect will block until the
1838 proxy is ready, and throw a TestError if the server isn't
1839 ready by timeout_seconds.
1840
beeps32a63082013-08-22 14:02:29 -07001841 If a server is already running on the remote port, this
1842 method will kill it and disconnect the tunnel process
1843 associated with the connection before establishing a new one,
1844 by consulting the rpc_proxy_map in rpc_disconnect.
1845
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001846 @param command Shell command to start the server.
1847 @param port Port number on which the server is expected to
1848 be serving.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001849 @param command_name String to use as input to `pkill` to
1850 terminate the XMLRPC server on the host.
Christopher Wileyd78249a2013-03-01 13:05:31 -08001851 @param ready_test_name String containing the name of a
1852 method defined on the XMLRPC server.
1853 @param timeout_seconds Number of seconds to wait
1854 for the server to become 'ready.' Will throw a
1855 TestFail error if server is not ready in time.
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001856 @param logfile Logfile to send output when running
1857 'command' argument.
Yusuf Mohsinally8d19e3c2013-11-21 14:25:45 -08001858
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001859 """
Christopher Wileyc14f06a2013-10-16 13:55:39 -07001860 # Clean up any existing state. If the caller is willing
1861 # to believe their server is down, we ought to clean up
1862 # any tunnels we might have sitting around.
1863 self.rpc_disconnect(port)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001864 # Start the server on the host. Redirection in the command
1865 # below is necessary, because 'ssh' won't terminate until
1866 # background child processes close stdin, stdout, and
1867 # stderr.
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001868 remote_cmd = '%s </dev/null >%s 2>&1 & echo $!' % (command, logfile)
Christopher Wileydd181852013-10-10 19:56:58 -07001869 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001870 logging.debug('Started XMLRPC server on host %s, pid = %s',
1871 self.hostname, remote_pid)
1872
Christopher Wileydd181852013-10-10 19:56:58 -07001873 # Tunnel through SSH to be able to reach that remote port.
1874 rpc_url = self._setup_rpc(port, command_name, remote_pid=remote_pid)
Christopher Wileyd78249a2013-03-01 13:05:31 -08001875 proxy = xmlrpclib.ServerProxy(rpc_url, allow_none=True)
Christopher Wileydd181852013-10-10 19:56:58 -07001876
Christopher Wileyd78249a2013-03-01 13:05:31 -08001877 if ready_test_name is not None:
J. Richard Barnette13eb7c02013-03-07 12:06:29 -08001878 # retry.retry logs each attempt; calculate delay_sec to
1879 # keep log spam to a dull roar.
Christopher Wiley0ed712b2013-04-09 15:25:12 -07001880 @retry.retry((socket.error,
1881 xmlrpclib.ProtocolError,
1882 httplib.BadStatusLine),
Chris Sosa65425082013-10-16 13:26:22 -07001883 timeout_min=timeout_seconds / 60.0,
1884 delay_sec=min(max(timeout_seconds / 20.0, 0.1), 1))
Christopher Wileyd78249a2013-03-01 13:05:31 -08001885 def ready_test():
1886 """ Call proxy.ready_test_name(). """
1887 getattr(proxy, ready_test_name)()
1888 successful = False
1889 try:
1890 logging.info('Waiting %d seconds for XMLRPC server '
1891 'to start.', timeout_seconds)
1892 ready_test()
1893 successful = True
Christopher Wileyd78249a2013-03-01 13:05:31 -08001894 finally:
1895 if not successful:
1896 logging.error('Failed to start XMLRPC server.')
beeps32a63082013-08-22 14:02:29 -07001897 self.rpc_disconnect(port)
Christopher Wileyd78249a2013-03-01 13:05:31 -08001898 logging.info('XMLRPC server started successfully.')
1899 return proxy
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001900
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001901
Jason Abeleb6f924f2013-11-13 16:01:54 -08001902 def syslog(self, message, tag='autotest'):
1903 """Logs a message to syslog on host.
1904
1905 @param message String message to log into syslog
1906 @param tag String tag prefix for syslog
1907
1908 """
1909 self.run('logger -t "%s" "%s"' % (tag, message))
1910
1911
beeps32a63082013-08-22 14:02:29 -07001912 def jsonrpc_connect(self, port):
1913 """Creates a jsonrpc proxy connection through an ssh tunnel.
1914
1915 This method exists to facilitate communication with goofy (which is
1916 the default system manager on all factory images) and as such, leaves
1917 most of the rpc server sanity checking to the caller. Unlike
1918 xmlrpc_connect, this method does not facilitate the creation of a remote
1919 jsonrpc server, as the only clients of this code are factory tests,
1920 for which the goofy system manager is built in to the image and starts
1921 when the target boots.
1922
1923 One can theoretically create multiple jsonrpc proxies all forwarded
1924 to the same remote port, provided the remote port has an rpc server
1925 listening. However, in doing so we stand the risk of leaking an
1926 existing tunnel process, so we always disconnect any older tunnels
1927 we might have through rpc_disconnect.
1928
1929 @param port: port on the remote host that is serving this proxy.
1930
1931 @return: The client proxy.
1932 """
1933 if not jsonrpclib:
1934 logging.warning('Jsonrpclib could not be imported. Check that '
1935 'site-packages contains jsonrpclib.')
1936 return None
1937
1938 proxy = jsonrpclib.jsonrpc.ServerProxy(self._setup_rpc(port, None))
1939
1940 logging.info('Established a jsonrpc connection through port %s.', port)
1941 return proxy
1942
1943
1944 def rpc_disconnect(self, port):
1945 """Disconnect from an RPC server on the host.
1946
1947 Terminates the remote RPC server previously started for
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001948 the given `port`. Also closes the local ssh tunnel created
1949 for the connection to the host. This function does not
beeps32a63082013-08-22 14:02:29 -07001950 directly alter the state of a previously returned RPC
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001951 client object; however disconnection will cause all
1952 subsequent calls to methods on the object to fail.
1953
1954 This function does nothing if requested to disconnect a port
beeps32a63082013-08-22 14:02:29 -07001955 that was not previously connected via _setup_rpc.
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001956
1957 @param port Port number passed to a previous call to
beeps32a63082013-08-22 14:02:29 -07001958 `_setup_rpc()`.
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001959 """
beeps32a63082013-08-22 14:02:29 -07001960 if port not in self._rpc_proxy_map:
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001961 return
Christopher Wileydd181852013-10-10 19:56:58 -07001962 remote_name, tunnel_proc, remote_pid = self._rpc_proxy_map[port]
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001963 if remote_name:
1964 # We use 'pkill' to find our target process rather than
1965 # a PID, because the host may have rebooted since
1966 # connecting, and we don't want to kill an innocent
1967 # process with the same PID.
1968 #
1969 # 'pkill' helpfully exits with status 1 if no target
1970 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -07001971 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001972 # status.
1973 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
Christopher Wileydd181852013-10-10 19:56:58 -07001974 if remote_pid:
1975 logging.info('Waiting for RPC server "%s" shutdown',
1976 remote_name)
1977 start_time = time.time()
1978 while (time.time() - start_time <
1979 self._RPC_SHUTDOWN_TIMEOUT_SECONDS):
1980 running_processes = self.run(
1981 "pgrep -f '%s'" % remote_name,
1982 ignore_status=True).stdout.split()
1983 if not remote_pid in running_processes:
1984 logging.info('Shut down RPC server.')
1985 break
1986 time.sleep(self._RPC_SHUTDOWN_POLLING_PERIOD_SECONDS)
1987 else:
1988 raise error.TestError('Failed to shutdown RPC server %s' %
1989 remote_name)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001990
1991 if tunnel_proc.poll() is None:
1992 tunnel_proc.terminate()
1993 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
1994 else:
1995 logging.debug('Tunnel pid %d terminated early, status %d',
1996 tunnel_proc.pid, tunnel_proc.returncode)
beeps32a63082013-08-22 14:02:29 -07001997 del self._rpc_proxy_map[port]
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001998
1999
beeps32a63082013-08-22 14:02:29 -07002000 def rpc_disconnect_all(self):
2001 """Disconnect all known RPC proxy ports."""
2002 for port in self._rpc_proxy_map.keys():
2003 self.rpc_disconnect(port)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07002004
2005
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002006 def poor_mans_rpc(self, fun):
2007 """
2008 Calls a function from client utils on the host and returns a string.
2009
2010 @param fun function in client utils namespace.
2011 @return output string from calling fun.
2012 """
Simran Basi263a9d32014-08-19 11:16:51 -07002013 script = 'cd %s/bin; ' % autotest.Autotest.get_installed_autodir(self)
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002014 script += 'python -c "import common; import utils;'
2015 script += 'print utils.%s"' % fun
2016 return script
2017
2018
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002019 def _ping_check_status(self, status):
2020 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002021
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002022 @param status Check the ping status against this value.
2023 @return True iff `status` and the result of ping are the same
2024 (i.e. both True or both False).
2025
2026 """
2027 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
2028 return not (status ^ (ping_val == 0))
2029
2030 def _ping_wait_for_status(self, status, timeout):
2031 """Wait for the host to have a given status (UP or DOWN).
2032
2033 Status is checked by polling. Polling will not last longer
2034 than the number of seconds in `timeout`. The polling
2035 interval will be long enough that only approximately
2036 _PING_WAIT_COUNT polling cycles will be executed, subject
2037 to a maximum interval of about one minute.
2038
2039 @param status Waiting will stop immediately if `ping` of the
2040 host returns this status.
2041 @param timeout Poll for at most this many seconds.
2042 @return True iff the host status from `ping` matched the
2043 requested status at the time of return.
2044
2045 """
2046 # _ping_check_status() takes about 1 second, hence the
2047 # "- 1" in the formula below.
2048 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
2049 end_time = time.time() + timeout
2050 while time.time() <= end_time:
2051 if self._ping_check_status(status):
2052 return True
2053 if poll_interval > 0:
2054 time.sleep(poll_interval)
2055
2056 # The last thing we did was sleep(poll_interval), so it may
2057 # have been too long since the last `ping`. Check one more
2058 # time, just to be sure.
2059 return self._ping_check_status(status)
2060
2061 def ping_wait_up(self, timeout):
2062 """Wait for the host to respond to `ping`.
2063
2064 N.B. This method is not a reliable substitute for
2065 `wait_up()`, because a host that responds to ping will not
2066 necessarily respond to ssh. This method should only be used
2067 if the target DUT can be considered functional even if it
2068 can't be reached via ssh.
2069
2070 @param timeout Minimum time to allow before declaring the
2071 host to be non-responsive.
2072 @return True iff the host answered to ping before the timeout.
2073
2074 """
2075 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002076
Andrew Bresticker678c0c72013-01-22 10:44:09 -08002077 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002078 """Wait until the host no longer responds to `ping`.
2079
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002080 This function can be used as a slightly faster version of
2081 `wait_down()`, by avoiding potentially long ssh timeouts.
2082
2083 @param timeout Minimum time to allow for the host to become
2084 non-responsive.
2085 @return True iff the host quit answering ping before the
2086 timeout.
2087
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002088 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08002089 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002090
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002091 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002092 """Wait for the client to enter low-power sleep mode.
2093
2094 The test for "is asleep" can't distinguish a system that is
2095 powered off; to confirm that the unit was asleep, it is
2096 necessary to force resume, and then call
2097 `test_wait_for_resume()`.
2098
2099 This function is expected to be called from a test as part
2100 of a sequence like the following:
2101
2102 ~~~~~~~~
2103 boot_id = host.get_boot_id()
2104 # trigger sleep on the host
2105 host.test_wait_for_sleep()
2106 # trigger resume on the host
2107 host.test_wait_for_resume(boot_id)
2108 ~~~~~~~~
2109
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002110 @param sleep_timeout time limit in seconds to allow the host sleep.
2111
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002112 @exception TestFail The host did not go to sleep within
2113 the allowed time.
2114 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002115 if sleep_timeout is None:
2116 sleep_timeout = self.SLEEP_TIMEOUT
2117
2118 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002119 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002120 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002121
2122
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002123 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002124 """Wait for the client to resume from low-power sleep mode.
2125
2126 The `old_boot_id` parameter should be the value from
2127 `get_boot_id()` obtained prior to entering sleep mode. A
2128 `TestFail` exception is raised if the boot id changes.
2129
2130 See @ref test_wait_for_sleep for more on this function's
2131 usage.
2132
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002133 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002134 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002135 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002136
2137 @exception TestFail The host did not respond within the
2138 allowed time.
2139 @exception TestFail The host responded, but the boot id test
2140 indicated a reboot rather than a sleep
2141 cycle.
2142 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002143 if resume_timeout is None:
2144 resume_timeout = self.RESUME_TIMEOUT
2145
2146 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002147 raise error.TestFail(
2148 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08002149 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002150 else:
2151 new_boot_id = self.get_boot_id()
2152 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002153 logging.error('client rebooted (old boot %s, new boot %s)',
2154 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002155 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002156 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002157
2158
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002159 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002160 """Wait for the client to shut down.
2161
2162 The test for "has shut down" can't distinguish a system that
2163 is merely asleep; to confirm that the unit was down, it is
2164 necessary to force boot, and then call test_wait_for_boot().
2165
2166 This function is expected to be called from a test as part
2167 of a sequence like the following:
2168
2169 ~~~~~~~~
2170 boot_id = host.get_boot_id()
2171 # trigger shutdown on the host
2172 host.test_wait_for_shutdown()
2173 # trigger boot on the host
2174 host.test_wait_for_boot(boot_id)
2175 ~~~~~~~~
2176
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002177 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002178 @exception TestFail The host did not shut down within the
2179 allowed time.
2180 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002181 if shutdown_timeout is None:
2182 shutdown_timeout = self.SHUTDOWN_TIMEOUT
2183
2184 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002185 raise error.TestFail(
2186 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08002187 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002188
2189
2190 def test_wait_for_boot(self, old_boot_id=None):
2191 """Wait for the client to boot from cold power.
2192
2193 The `old_boot_id` parameter should be the value from
2194 `get_boot_id()` obtained prior to shutting down. A
2195 `TestFail` exception is raised if the boot id does not
2196 change. The boot id test is omitted if `old_boot_id` is not
2197 specified.
2198
2199 See @ref test_wait_for_shutdown for more on this function's
2200 usage.
2201
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08002202 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002203 shut down.
2204
2205 @exception TestFail The host did not respond within the
2206 allowed time.
2207 @exception TestFail The host responded, but the boot id test
2208 indicated that there was no reboot.
2209 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002210 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002211 raise error.TestFail(
2212 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07002213 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002214 elif old_boot_id:
2215 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002216 logging.error('client not rebooted (boot %s)',
2217 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07002218 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08002219 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07002220
2221
2222 @staticmethod
2223 def check_for_rpm_support(hostname):
2224 """For a given hostname, return whether or not it is powered by an RPM.
2225
Simran Basi1df55112013-09-06 11:25:09 -07002226 @param hostname: hostname to check for rpm support.
2227
Simran Basid5e5e272012-09-24 15:23:59 -07002228 @return None if this host does not follows the defined naming format
2229 for RPM powered DUT's in the lab. If it does follow the format,
2230 it returns a regular expression MatchObject instead.
2231 """
Fang Dengbaff9082015-01-06 13:46:15 -08002232 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002233
2234
2235 def has_power(self):
2236 """For this host, return whether or not it is powered by an RPM.
2237
2238 @return True if this host is in the CROS lab and follows the defined
2239 naming format.
2240 """
Fang Deng0ca40e22013-08-27 17:47:44 -07002241 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07002242
2243
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002244 def _set_power(self, state, power_method):
2245 """Sets the power to the host via RPM, Servo or manual.
2246
2247 @param state Specifies which power state to set to DUT
2248 @param power_method Specifies which method of power control to
2249 use. By default "RPM" will be used. Valid values
2250 are the strings "RPM", "manual", "servoj10".
2251
2252 """
2253 ACCEPTABLE_STATES = ['ON', 'OFF']
2254
2255 if state.upper() not in ACCEPTABLE_STATES:
2256 raise error.TestError('State must be one of: %s.'
2257 % (ACCEPTABLE_STATES,))
2258
2259 if power_method == self.POWER_CONTROL_SERVO:
2260 logging.info('Setting servo port J10 to %s', state)
2261 self.servo.set('prtctl3_pwren', state.lower())
2262 time.sleep(self._USB_POWER_TIMEOUT)
2263 elif power_method == self.POWER_CONTROL_MANUAL:
2264 logging.info('You have %d seconds to set the AC power to %s.',
2265 self._POWER_CYCLE_TIMEOUT, state)
2266 time.sleep(self._POWER_CYCLE_TIMEOUT)
2267 else:
2268 if not self.has_power():
2269 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07002270 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
2271 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
2272 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07002273 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07002274
2275
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002276 def power_off(self, power_method=POWER_CONTROL_RPM):
2277 """Turn off power to this host via RPM, Servo or manual.
2278
2279 @param power_method Specifies which method of power control to
2280 use. By default "RPM" will be used. Valid values
2281 are the strings "RPM", "manual", "servoj10".
2282
2283 """
2284 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07002285
2286
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08002287 def power_on(self, power_method=POWER_CONTROL_RPM):
2288 """Turn on power to this host via RPM, Servo or manual.
2289
2290 @param power_method Specifies which method of power control to
2291 use. By default "RPM" will be used. Valid values
2292 are the strings "RPM", "manual", "servoj10".
2293
2294 """
2295 self._set_power('ON', power_method)
2296
2297
2298 def power_cycle(self, power_method=POWER_CONTROL_RPM):
2299 """Cycle power to this host by turning it OFF, then ON.
2300
2301 @param power_method Specifies which method of power control to
2302 use. By default "RPM" will be used. Valid values
2303 are the strings "RPM", "manual", "servoj10".
2304
2305 """
2306 if power_method in (self.POWER_CONTROL_SERVO,
2307 self.POWER_CONTROL_MANUAL):
2308 self.power_off(power_method=power_method)
2309 time.sleep(self._POWER_CYCLE_TIMEOUT)
2310 self.power_on(power_method=power_method)
2311 else:
2312 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002313
2314
2315 def get_platform(self):
2316 """Determine the correct platform label for this host.
2317
2318 @returns a string representing this host's platform.
2319 """
2320 crossystem = utils.Crossystem(self)
2321 crossystem.init()
2322 # Extract fwid value and use the leading part as the platform id.
2323 # fwid generally follow the format of {platform}.{firmware version}
2324 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
2325 platform = crossystem.fwid().split('.')[0].lower()
2326 # Newer platforms start with 'Google_' while the older ones do not.
2327 return platform.replace('google_', '')
2328
2329
Hung-ying Tyanb1328032014-04-01 14:18:54 +08002330 def get_architecture(self):
2331 """Determine the correct architecture label for this host.
2332
2333 @returns a string representing this host's architecture.
2334 """
2335 crossystem = utils.Crossystem(self)
2336 crossystem.init()
2337 return crossystem.arch()
2338
2339
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002340 def get_chrome_version(self):
2341 """Gets the Chrome version number and milestone as strings.
2342
2343 Invokes "chrome --version" to get the version number and milestone.
2344
2345 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
2346 current Chrome version number as a string (in the form "W.X.Y.Z")
2347 and "milestone" is the first component of the version number
2348 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
2349 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
2350 of "chrome --version" and the milestone will be the empty string.
2351
2352 """
MK Ryu35d661e2014-09-25 17:44:10 -07002353 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002354 return utils.parse_chrome_version(version_string)
2355
Aviv Keshet74c89a92013-02-04 15:18:30 -08002356 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07002357 def get_board(self):
2358 """Determine the correct board label for this host.
2359
2360 @returns a string representing this host's board.
2361 """
2362 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
2363 run_method=self.run)
2364 board = release_info['CHROMEOS_RELEASE_BOARD']
2365 # Devices in the lab generally have the correct board name but our own
2366 # development devices have {board_name}-signed-{key_type}. The board
2367 # name may also begin with 'x86-' which we need to keep.
Simran Basi833814b2013-01-29 13:13:43 -08002368 board_format_string = ds_constants.BOARD_PREFIX + '%s'
Simran Basic6f1f7a2012-10-16 10:47:46 -07002369 if 'x86' not in board:
Simran Basi833814b2013-01-29 13:13:43 -08002370 return board_format_string % board.split('-')[0]
2371 return board_format_string % '-'.join(board.split('-')[0:2])
Simran Basic6f1f7a2012-10-16 10:47:46 -07002372
2373
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002374 @label_decorator('board_freq_mem')
2375 def get_board_with_frequency_and_memory(self):
2376 """
2377 Determines the board name with frequency and memory.
2378
2379 @returns a more detailed string representing the board. Examples are
2380 butterfly_1.1GHz_2GB, link_1.8GHz_4GB, x86-zgb_1.7GHz_2GB
2381 """
2382 board = self.run(self.poor_mans_rpc(
2383 'get_board_with_frequency_and_memory()')).stdout
2384 return 'board_freq_mem:%s' % str.strip(board)
2385
2386
Aviv Keshet74c89a92013-02-04 15:18:30 -08002387 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002388 def has_lightsensor(self):
2389 """Determine the correct board label for this host.
2390
2391 @returns the string 'lightsensor' if this host has a lightsensor or
2392 None if it does not.
2393 """
2394 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08002395 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07002396 try:
2397 # Run the search cmd following the symlinks. Stderr_tee is set to
2398 # None as there can be a symlink loop, but the command will still
2399 # execute correctly with a few messages printed to stderr.
2400 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
2401 return 'lightsensor'
2402 except error.AutoservRunError:
2403 # egrep exited with a return code of 1 meaning none of the possible
2404 # lightsensor files existed.
2405 return None
2406
2407
Aviv Keshet74c89a92013-02-04 15:18:30 -08002408 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002409 def has_bluetooth(self):
2410 """Determine the correct board label for this host.
2411
2412 @returns the string 'bluetooth' if this host has bluetooth or
2413 None if it does not.
2414 """
2415 try:
2416 self.run('test -d /sys/class/bluetooth/hci0')
2417 # test exited with a return code of 0.
2418 return 'bluetooth'
2419 except error.AutoservRunError:
2420 # test exited with a return code 1 meaning the directory did not
2421 # exist.
2422 return None
2423
2424
Ilja H. Friedel1232e8a2014-06-17 21:30:48 -07002425 @label_decorator('gpu_family')
2426 def get_gpu_family(self):
2427 """
2428 Determine GPU family.
2429
2430 @returns a string representing the gpu family. Examples are mali, tegra,
2431 pinetrail, sandybridge, ivybridge, haswell and baytrail.
2432 """
2433 gpu_family = self.run(self.poor_mans_rpc('get_gpu_family()')).stdout
2434 return 'gpu_family:%s' % str.strip(gpu_family)
2435
2436
Ilja Friedel0ce0b602013-08-15 18:45:27 -07002437 @label_decorator('graphics')
2438 def get_graphics(self):
2439 """
2440 Determine the correct board label for this host.
2441
2442 @returns a string representing this host's graphics. For now ARM boards
2443 return graphics:gles while all other boards return graphics:gl. This
2444 may change over time, but for robustness reasons this should avoid
2445 executing code in actual graphics libraries (which may not be ready and
2446 is tested by graphics_GLAPICheck).
2447 """
2448 uname = self.run('uname -a').stdout.lower()
2449 if 'arm' in uname:
2450 return 'graphics:gles'
2451 return 'graphics:gl'
2452
2453
Bill Richardson4f595f52014-02-13 16:20:26 -08002454 @label_decorator('ec')
2455 def get_ec(self):
2456 """
2457 Determine the type of EC on this host.
2458
2459 @returns a string representing this host's embedded controller type.
2460 At present, it only returns "ec:cros", for Chrome OS ECs. Other types
2461 of EC (or none) don't return any strings, since no tests depend on
2462 those.
2463 """
2464 cmd = 'mosys ec info'
2465 # The output should look like these, so that the last field should
2466 # match our EC version scheme:
2467 #
2468 # stm | stm32f100 | snow_v1.3.139-375eb9f
2469 # ti | Unknown-10de | peppy_v1.5.114-5d52788
2470 #
2471 # Non-Chrome OS ECs will look like these:
2472 #
2473 # ENE | KB932 | 00BE107A00
2474 # ite | it8518 | 3.08
2475 #
2476 # And some systems don't have ECs at all (Lumpy, for example).
2477 regexp = r'^.*\|\s*(\S+_v\d+\.\d+\.\d+-[0-9a-f]+)\s*$'
2478
2479 ecinfo = self.run(command=cmd, ignore_status=True)
2480 if ecinfo.exit_status == 0:
2481 res = re.search(regexp, ecinfo.stdout)
2482 if res:
2483 logging.info("EC version is %s", res.groups()[0])
2484 return 'ec:cros'
2485 logging.info("%s got: %s", cmd, ecinfo.stdout)
2486 # Has an EC, but it's not a Chrome OS EC
2487 return None
2488 logging.info("%s exited with status %d", cmd, ecinfo.exit_status)
2489 # No EC present
2490 return None
2491
2492
Alec Berg31b932b2014-04-04 16:09:11 -07002493 @label_decorator('accels')
2494 def get_accels(self):
2495 """
2496 Determine the type of accelerometers on this host.
2497
2498 @returns a string representing this host's accelerometer type.
2499 At present, it only returns "accel:cros-ec", for accelerometers
2500 attached to a Chrome OS EC, or none, if no accelerometers.
2501 """
2502 # Check to make sure we have ectool
2503 rv = self.run('which ectool', ignore_status=True)
2504 if rv.exit_status:
2505 logging.info("No ectool cmd found, assuming no EC accelerometers")
2506 return None
2507
2508 # Check that the EC supports the motionsense command
2509 rv = self.run('ectool motionsense', ignore_status=True)
2510 if rv.exit_status:
2511 logging.info("EC does not support motionsense command "
2512 "assuming no EC accelerometers")
2513 return None
2514
2515 # Check that EC motion sensors are active
2516 active = self.run('ectool motionsense active').stdout.split('\n')
2517 if active[0] == "0":
2518 logging.info("Motion sense inactive, assuming no EC accelerometers")
2519 return None
2520
2521 logging.info("EC accelerometers found")
2522 return 'accel:cros-ec'
2523
2524
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002525 @label_decorator('chameleon')
2526 def has_chameleon(self):
2527 """Determine if a Chameleon connected to this host.
2528
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08002529 @returns a list containing two strings ('chameleon' and
2530 'chameleon:' + label, e.g. 'chameleon:hdmi') if this host
2531 has a Chameleon or None if it has not.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002532 """
2533 if self._chameleon_host:
Tom Wai-Hong Tambadbb332014-10-10 02:59:41 +08002534 return ['chameleon', 'chameleon:' + self.chameleon.get_label()]
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +08002535 else:
2536 return None
2537
2538
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +08002539 @label_decorator('audio_loopback_dongle')
2540 def has_loopback_dongle(self):
2541 """Determine if an audio loopback dongle is plugged to this host.
2542
2543 @returns 'audio_loopback_dongle' when there is an audio loopback dongle
2544 plugged to this host.
2545 None when there is no audio loopback dongle
2546 plugged to this host.
2547 """
2548 server_info = self.run(command='cras_test_client --dump_s',
2549 ignore_status=True).stdout
2550 if (cras_utils.node_type_is_plugged('HEADPHONE', server_info) and
2551 cras_utils.node_type_is_plugged('MIC', server_info)):
2552 return 'audio_loopback_dongle'
2553 else:
2554 return None
2555
2556
Derek Basehorec71ff622014-07-07 15:18:40 -07002557 @label_decorator('power_supply')
2558 def get_power_supply(self):
2559 """
2560 Determine what type of power supply the host has
2561
2562 @returns a string representing this host's power supply.
2563 'power:battery' when the device has a battery intended for
2564 extended use
2565 'power:AC_primary' when the device has a battery not intended
2566 for extended use (for moving the machine, etc)
2567 'power:AC_only' when the device has no battery at all.
2568 """
2569 psu = self.run(command='mosys psu type', ignore_status=True)
2570 if psu.exit_status:
2571 # The psu command for mosys is not included for all platforms. The
2572 # assumption is that the device will have a battery if the command
2573 # is not found.
2574 return 'power:battery'
2575
2576 psu_str = psu.stdout.strip()
2577 if psu_str == 'unknown':
2578 return None
2579
2580 return 'power:%s' % psu_str
2581
2582
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002583 @label_decorator('storage')
2584 def get_storage(self):
2585 """
2586 Determine the type of boot device for this host.
2587
2588 Determine if the internal device is SCSI or dw_mmc device.
2589 Then check that it is SSD or HDD or eMMC or something else.
2590
2591 @returns a string representing this host's internal device type.
2592 'storage:ssd' when internal device is solid state drive
2593 'storage:hdd' when internal device is hard disk drive
2594 'storage:mmc' when internal device is mmc drive
2595 None When internal device is something else or
2596 when we are unable to determine the type
2597 """
2598 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
2599 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2600 '. /usr/share/misc/chromeos-common.sh;',
2601 'load_base_vars;',
2602 'get_fixed_dst_drive'])
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002603 rootdev = self.run(command=rootdev_cmd, ignore_status=True)
2604 if rootdev.exit_status:
2605 logging.info("Fail to run %s", rootdev_cmd)
2606 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002607 rootdev_str = rootdev.stdout.strip()
2608
2609 if not rootdev_str:
2610 return None
2611
2612 rootdev_base = os.path.basename(rootdev_str)
2613
2614 mmc_pattern = '/dev/mmcblk[0-9]'
2615 if re.match(mmc_pattern, rootdev_str):
2616 # Use type to determine if the internal device is eMMC or somthing
2617 # else. We can assume that MMC is always an internal device.
2618 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002619 type = self.run(command=type_cmd, ignore_status=True)
2620 if type.exit_status:
2621 logging.info("Fail to run %s", type_cmd)
2622 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002623 type_str = type.stdout.strip()
2624
2625 if type_str == 'MMC':
2626 return 'storage:mmc'
2627
2628 scsi_pattern = '/dev/sd[a-z]+'
2629 if re.match(scsi_pattern, rootdev.stdout):
2630 # Read symlink for /sys/block/sd* to determine if the internal
2631 # device is connected via ata or usb.
2632 link_cmd = 'readlink /sys/block/%s' % rootdev_base
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002633 link = self.run(command=link_cmd, ignore_status=True)
2634 if link.exit_status:
2635 logging.info("Fail to run %s", link_cmd)
2636 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002637 link_str = link.stdout.strip()
2638 if 'usb' in link_str:
2639 return None
2640
2641 # Read rotation to determine if the internal device is ssd or hdd.
2642 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
2643 % rootdev_base)
Puthikorn Voravootivat03c51682014-04-24 13:52:12 -07002644 rotate = self.run(command=rotate_cmd, ignore_status=True)
2645 if rotate.exit_status:
2646 logging.info("Fail to run %s", rotate_cmd)
2647 return None
Puthikorn Voravootivatfa011242014-03-14 18:45:11 -07002648 rotate_str = rotate.stdout.strip()
2649
2650 rotate_dict = {'0':'storage:ssd', '1':'storage:hdd'}
2651 return rotate_dict.get(rotate_str)
2652
2653 # All other internal device / error case will always fall here
2654 return None
2655
2656
Dan Shi4e9a2aa2014-03-24 14:28:42 -07002657 @label_decorator('servo')
2658 def get_servo(self):
2659 """Determine if the host has a servo attached.
2660
2661 If the host has a working servo attached, it should have a servo label.
2662
2663 @return: string 'servo' if the host has servo attached. Otherwise,
2664 returns None.
2665 """
2666 return 'servo' if self._servo_host else None
2667
2668
Dan Shi5beba472014-05-28 22:46:07 -07002669 @label_decorator('video_labels')
2670 def get_video_labels(self):
2671 """Run /usr/local/bin/avtest_label_detect to get a list of video labels.
2672
2673 Sample output of avtest_label_detect:
2674 Detected label: hw_video_acc_vp8
2675 Detected label: webcam
2676
2677 @return: A list of labels detected by tool avtest_label_detect.
2678 """
2679 try:
2680 result = self.run('/usr/local/bin/avtest_label_detect').stdout
2681 return re.findall('^Detected label: (\w+)$', result, re.M)
2682 except error.AutoservRunError:
2683 # The tool is not installed.
2684 return []
2685
2686
mussa584b4462014-06-20 15:13:28 -07002687 @label_decorator('video_glitch_detection')
2688 def is_video_glitch_detection_supported(self):
2689 """ Determine if a board under test is supported for video glitch
2690 detection tests.
2691
2692 @return: 'video_glitch_detection' if board is supported, None otherwise.
2693 """
2694 parser = ConfigParser.SafeConfigParser()
2695 filename = os.path.join(
2696 common.autotest_dir, 'client/cros/video/device_spec.conf')
2697
2698 dut = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2699
2700 try:
2701 parser.read(filename)
mussa584b4462014-06-20 15:13:28 -07002702 supported_boards = parser.sections()
2703
Mussa83c84d62014-10-02 12:11:28 -07002704 return 'video_glitch_detection' if dut in supported_boards else None
mussa584b4462014-06-20 15:13:28 -07002705
2706 except ConfigParser.error:
2707 # something went wrong while parsing the conf file
2708 return None
2709
Katherine Threlkeld7b97a9f2014-06-24 13:47:14 -07002710 @label_decorator('touch_labels')
2711 def get_touch(self):
2712 """
2713 Determine whether board under test has a touchpad or touchscreen.
2714
2715 @return: A list of some combination of 'touchscreen' and 'touchpad',
2716 depending on what is present on the device.
2717 """
2718 labels = []
2719 input_cmd = '/opt/google/input/inputcontrol --names -t %s'
2720 for elt in ['touchpad', 'touchscreen']:
2721 if self.run(input_cmd % elt).stdout:
2722 labels.append(elt)
2723 return labels
2724
2725
mussa584b4462014-06-20 15:13:28 -07002726
Simran Basic6f1f7a2012-10-16 10:47:46 -07002727 def get_labels(self):
2728 """Return a list of labels for this given host.
2729
2730 This is the main way to retrieve all the automatic labels for a host
2731 as it will run through all the currently implemented label functions.
2732 """
2733 labels = []
Richard Barnette82c35912012-11-20 10:09:10 -08002734 for label_function in self._LABEL_FUNCTIONS:
J. Richard Barnetteb869b222014-09-03 17:55:44 -07002735 try:
2736 label = label_function(self)
2737 except Exception as e:
2738 logging.error('Label function %s failed; ignoring it.',
2739 label_function.__name__)
2740 logging.exception(e)
2741 label = None
Simran Basic6f1f7a2012-10-16 10:47:46 -07002742 if label:
Dan Shi5beba472014-05-28 22:46:07 -07002743 if type(label) is str:
2744 labels.append(label)
2745 elif type(label) is list:
2746 labels.extend(label)
Simran Basic6f1f7a2012-10-16 10:47:46 -07002747 return labels
Dan Shi85276d42014-04-08 22:11:45 -07002748
2749
2750 def is_boot_from_usb(self):
2751 """Check if DUT is boot from USB.
2752
2753 @return: True if DUT is boot from usb.
2754 """
2755 device = self.run('rootdev -s -d').stdout.strip()
2756 removable = int(self.run('cat /sys/block/%s/removable' %
2757 os.path.basename(device)).stdout.strip())
2758 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002759
2760
2761 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002762 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002763
2764 @param key: meminfo requested
2765
2766 @return the memory value as a string
2767
2768 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002769 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2770 logging.debug('%s', meminfo)
2771 return int(re.search(r'\d+', meminfo).group(0))