blob: f302c6598fa4ab34de0d4719466fd5b61b13b5c9 [file] [log] [blame]
J. Richard Barnette24adbf42012-04-11 15:04:53 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Dale Curtisaa5eedb2011-08-23 16:18:52 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
J. Richard Barnette1d78b012012-05-15 13:56:30 -07005import logging
Dan Shi0f466e82013-02-22 15:44:58 -08006import os
Simran Basid5e5e272012-09-24 15:23:59 -07007import re
Vincent Palatindf2372c2016-10-07 17:03:00 +02008import sys
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07009import time
10
mussa584b4462014-06-20 15:13:28 -070011import common
J. Richard Barnette45e93de2012-04-11 17:24:15 -070012from autotest_lib.client.bin import utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070013from autotest_lib.client.common_lib import autotemp
Richard Barnette0c73ffc2012-11-19 15:21:18 -080014from autotest_lib.client.common_lib import error
15from autotest_lib.client.common_lib import global_config
J. Richard Barnette91137f02016-03-10 16:52:26 -080016from autotest_lib.client.common_lib import hosts
Dan Shi549fb822015-03-24 18:01:11 -070017from autotest_lib.client.common_lib import lsbrelease_utils
J. Richard Barnette45e93de2012-04-11 17:24:15 -070018from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette03a0c132012-11-05 12:40:35 -080019from autotest_lib.client.common_lib.cros import dev_server
Gabe Blackb72f4fb2015-01-20 16:47:13 -080020from autotest_lib.client.common_lib.cros.graphite import autotest_es
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000021from autotest_lib.client.cros import constants as client_constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080022from autotest_lib.client.cros import cros_ui
Kevin Chenga328da62016-03-31 10:49:04 -070023from autotest_lib.client.cros.audio import cras_utils
24from autotest_lib.client.cros.input_playback import input_playback
25from autotest_lib.client.cros.video import constants as video_test_constants
Simran Basi5ace6f22016-01-06 17:30:44 -080026from autotest_lib.server import afe_utils
MK Ryu35d661e2014-09-25 17:44:10 -070027from autotest_lib.server import autoserv_parser
28from autotest_lib.server import autotest
29from autotest_lib.server import constants
Dan Shia1ecd5c2013-06-06 11:21:31 -070030from autotest_lib.server import utils as server_utils
Dan Shi9cb0eec2014-06-03 09:04:50 -070031from autotest_lib.server.cros import provision
Scott Zawalski89c44dd2013-02-26 09:28:02 -050032from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070033from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
Dan Shi9cb0eec2014-06-03 09:04:50 -070034from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
Scottfe06ed82015-11-05 17:15:01 -080035from autotest_lib.server.cros.servo import plankton
Fang Deng96667ca2013-08-01 17:46:18 -070036from autotest_lib.server.hosts import abstract_ssh
Kevin Chenga2619dc2016-03-28 11:42:08 -070037from autotest_lib.server.hosts import base_label
38from autotest_lib.server.hosts import cros_label
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +080039from autotest_lib.server.hosts import chameleon_host
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -080040from autotest_lib.server.hosts import cros_repair
Scottfe06ed82015-11-05 17:15:01 -080041from autotest_lib.server.hosts import plankton_host
Fang Deng5d518f42013-08-02 14:04:32 -070042from autotest_lib.server.hosts import servo_host
Simran Basidcff4252012-11-20 16:13:20 -080043from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070044
Simran Basi382506b2016-09-13 14:58:15 -070045# In case cros_host is being ran via SSP on an older Moblab version with an
46# older chromite version.
47try:
48 from chromite.lib import metrics
Dan Shi5e2efb72017-02-07 11:40:23 -080049except ImportError:
50 metrics = utils.metrics_mock
51
Simran Basid5e5e272012-09-24 15:23:59 -070052
Dan Shib8540a52015-07-16 14:18:23 -070053CONFIG = global_config.global_config
xixuanec801e32016-08-25 10:20:22 -070054ENABLE_DEVSERVER_TRIGGER_AUTO_UPDATE = CONFIG.get_config_value(
55 'CROS', 'enable_devserver_trigger_auto_update', type=bool,
56 default=False)
Dan Shib8540a52015-07-16 14:18:23 -070057
Kevin Chenga328da62016-03-31 10:49:04 -070058LUCID_SLEEP_BOARDS = ['samus', 'lulu']
59
Dan Shid07ee2e2015-09-24 14:49:25 -070060
beepsc87ff602013-07-31 21:53:00 -070061class FactoryImageCheckerException(error.AutoservError):
62 """Exception raised when an image is a factory image."""
63 pass
64
65
Fang Deng0ca40e22013-08-27 17:47:44 -070066class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070067 """Chromium OS specific subclass of Host."""
68
Simran Basi5ace6f22016-01-06 17:30:44 -080069 VERSION_PREFIX = provision.CROS_VERSION_PREFIX
70
J. Richard Barnette45e93de2012-04-11 17:24:15 -070071 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -050072 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
xixuanec801e32016-08-25 10:20:22 -070073 support_devserver_provision = ENABLE_DEVSERVER_TRIGGER_AUTO_UPDATE
J. Richard Barnette45e93de2012-04-11 17:24:15 -070074
Richard Barnette03a0c132012-11-05 12:40:35 -080075 # Timeout values (in seconds) associated with various Chrome OS
76 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070077 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080078 # In general, a good rule of thumb is that the timeout can be up
79 # to twice the typical measured value on the slowest platform.
80 # The times here have not necessarily been empirically tested to
81 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070082 #
83 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080084 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
85 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080086 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070087 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080088 # other things, this must account for the 30 second dev-mode
J. Richard Barnette417cc792015-10-01 09:56:36 -070089 # screen delay, time to start the network on the DUT, and the
90 # ssh timeout of 120 seconds.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070091 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080092 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080093 # network.
beepsf079cfb2013-09-18 17:49:51 -070094 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080095 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
96 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070097
98 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080099 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +0800100 SHUTDOWN_TIMEOUT = 10
J. Richard Barnette417cc792015-10-01 09:56:36 -0700101 BOOT_TIMEOUT = 150
J. Richard Barnette5bab5f52015-08-03 13:14:38 -0700102 USB_BOOT_TIMEOUT = 300
J. Richard Barnette7817b052014-08-28 09:47:29 -0700103 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -0800104 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -0700105
Dan Shica503482015-03-30 17:23:25 -0700106 # Minimum OS version that supports server side packaging. Older builds may
107 # not have server side package built or with Autotest code change to support
108 # server-side packaging.
Dan Shib8540a52015-07-16 14:18:23 -0700109 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value(
Dan Shiced09e42015-04-17 16:09:34 -0700110 'AUTOSERV', 'min_version_support_ssp', type=int)
Dan Shica503482015-03-30 17:23:25 -0700111
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800112 # REBOOT_TIMEOUT: How long to wait for a reboot.
113 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700114 # We have a long timeout to ensure we don't flakily fail due to other
115 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700116 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
117 # return from reboot' bug is solved.
118 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700119
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800120 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
121 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
122 _USB_POWER_TIMEOUT = 5
123 _POWER_CYCLE_TIMEOUT = 10
124
Dan Shib8540a52015-07-16 14:18:23 -0700125 _RPM_RECOVERY_BOARDS = CONFIG.get_config_value('CROS',
Richard Barnette82c35912012-11-20 10:09:10 -0800126 'rpm_recovery_boards', type=str).split(',')
127
Richard Barnette82c35912012-11-20 10:09:10 -0800128 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Fang Dengdeba14f2014-11-14 11:54:09 -0800129 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
130 '-host(\d+)')
Kevin Chenga328da62016-03-31 10:49:04 -0700131 _LIGHTSENSOR_FILES = [ "in_illuminance0_input",
132 "in_illuminance_input",
133 "in_illuminance0_raw",
134 "in_illuminance_raw",
135 "illuminance0_input"]
136 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700137
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800138 # Constants used in ping_wait_up() and ping_wait_down().
139 #
140 # _PING_WAIT_COUNT is the approximate number of polling
141 # cycles to use when waiting for a host state change.
142 #
143 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
144 # for arguments to the internal _ping_wait_for_status()
145 # method.
146 _PING_WAIT_COUNT = 40
147 _PING_STATUS_DOWN = False
148 _PING_STATUS_UP = True
149
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800150 # Allowed values for the power_method argument.
151
152 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
153 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
154 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
155 POWER_CONTROL_RPM = 'RPM'
156 POWER_CONTROL_SERVO = 'servoj10'
157 POWER_CONTROL_MANUAL = 'manual'
158
159 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
160 POWER_CONTROL_SERVO,
161 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800162
Simran Basi5e6339a2013-03-21 11:34:32 -0700163 _RPM_OUTLET_CHANGED = 'outlet_changed'
164
Dan Shi9cb0eec2014-06-03 09:04:50 -0700165 # URL pattern to download firmware image.
Dan Shib8540a52015-07-16 14:18:23 -0700166 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value(
Dan Shi9cb0eec2014-06-03 09:04:50 -0700167 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700168
J. Richard Barnette91137f02016-03-10 16:52:26 -0800169
170 # A flag file to indicate provision failures. The file is created
171 # at the start of any AU procedure (see `machine_install()`). The
172 # file's location in stateful means that on successul update it will
173 # be removed. Thus, if this file exists, it indicates that we've
174 # tried and failed in a previous attempt to update.
175 PROVISION_FAILED = '/var/tmp/provision_failed'
MK Ryu35d661e2014-09-25 17:44:10 -0700176
MK Ryu35d661e2014-09-25 17:44:10 -0700177
J. Richard Barnette964fba02012-10-24 17:34:29 -0700178 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800179 def check_host(host, timeout=10):
180 """
181 Check if the given host is a chrome-os host.
182
183 @param host: An ssh host representing a device.
184 @param timeout: The timeout for the run command.
185
186 @return: True if the host device is chromeos.
187
beeps46dadc92013-11-07 14:07:10 -0800188 """
189 try:
Simran Basi933c8af2015-04-29 14:05:07 -0700190 result = host.run(
191 'grep -q CHROMEOS /etc/lsb-release && '
192 '! test -f /mnt/stateful_partition/.android_tester && '
193 '! grep -q moblab /etc/lsb-release',
194 ignore_status=True, timeout=timeout)
Laurence Goodby468de252017-06-08 17:22:53 -0700195 if result.exit_status == 0:
196 lsb_release_content = host.run(
197 'grep CHROMEOS_RELEASE_BOARD /etc/lsb-release',
198 timeout=timeout).stdout
199 return not lsbrelease_utils.is_jetstream(
200 lsb_release_content=lsb_release_content)
beeps46dadc92013-11-07 14:07:10 -0800201 except (error.AutoservRunError, error.AutoservSSHTimeout):
202 return False
Laurence Goodby468de252017-06-08 17:22:53 -0700203
204 return False
beeps46dadc92013-11-07 14:07:10 -0800205
206
207 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800208 def get_chameleon_arguments(args_dict):
209 """Extract chameleon options from `args_dict` and return the result.
210
211 Recommended usage:
212 ~~~~~~~~
213 args_dict = utils.args_to_dict(args)
214 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
215 host = hosts.create_host(machine, chameleon_args=chameleon_args)
216 ~~~~~~~~
217
218 @param args_dict Dictionary from which to extract the chameleon
219 arguments.
220 """
Allen Li083866b2016-08-18 10:07:10 -0700221 return {key: args_dict[key]
222 for key in ('chameleon_host', 'chameleon_port')
223 if key in args_dict}
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800224
225
226 @staticmethod
Scottfe06ed82015-11-05 17:15:01 -0800227 def get_plankton_arguments(args_dict):
228 """Extract chameleon options from `args_dict` and return the result.
229
230 Recommended usage:
231 ~~~~~~~~
232 args_dict = utils.args_to_dict(args)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700233 plankton_args = hosts.CrosHost.get_plankton_arguments(args_dict)
234 host = hosts.create_host(machine, plankton_args=plankton_args)
Scottfe06ed82015-11-05 17:15:01 -0800235 ~~~~~~~~
236
237 @param args_dict Dictionary from which to extract the plankton
238 arguments.
239 """
Allen Li083866b2016-08-18 10:07:10 -0700240 return {key: args_dict[key]
241 for key in ('plankton_host', 'plankton_port')
242 if key in args_dict}
Scottfe06ed82015-11-05 17:15:01 -0800243
244
245 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800246 def get_servo_arguments(args_dict):
247 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800248
249 Recommended usage:
250 ~~~~~~~~
251 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700252 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800253 host = hosts.create_host(machine, servo_args=servo_args)
254 ~~~~~~~~
255
256 @param args_dict Dictionary from which to extract the servo
257 arguments.
258 """
Richard Barnettee519dcd2016-08-15 17:37:17 -0700259 servo_attrs = (servo_host.SERVO_HOST_ATTR,
260 servo_host.SERVO_PORT_ATTR,
261 servo_host.SERVO_BOARD_ATTR)
Allen Li083866b2016-08-18 10:07:10 -0700262 return {key: args_dict[key]
263 for key in servo_attrs
264 if key in args_dict}
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700265
J. Richard Barnette964fba02012-10-24 17:34:29 -0700266
J. Richard Barnette91137f02016-03-10 16:52:26 -0800267 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
268 plankton_args=None, try_lab_servo=False,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700269 try_servo_repair=False,
J. Richard Barnette91137f02016-03-10 16:52:26 -0800270 ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700271 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800272 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700273
Fang Denge545abb2014-12-30 18:43:47 -0800274 This method will attempt to create the test-assistant object
275 (chameleon/servo) when it is needed by the test. Check
276 the docstring of chameleon_host.create_chameleon_host and
277 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700278
Fang Denge545abb2014-12-30 18:43:47 -0800279 @param hostname: Hostname of the dut.
280 @param chameleon_args: A dictionary that contains args for creating
281 a ChameleonHost. See chameleon_host for details.
282 @param servo_args: A dictionary that contains args for creating
283 a ServoHost object. See servo_host for details.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700284 @param try_lab_servo: When true, indicates that an attempt should
285 be made to create a ServoHost for a DUT in
286 the test lab, even if not required by
287 `servo_args`. See servo_host for details.
288 @param try_servo_repair: If a servo host is created, check it
289 with `repair()` rather than `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800290 See servo_host for details.
291 @param ssh_verbosity_flag: String, to pass to the ssh command to control
292 verbosity.
293 @param ssh_options: String, other ssh options to pass to the ssh
294 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700295 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700296 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700297 *args, **dargs)
J. Richard Barnette91137f02016-03-10 16:52:26 -0800298 self._repair_strategy = cros_repair.create_cros_repair_strategy()
Kevin Chenga2619dc2016-03-28 11:42:08 -0700299 self.labels = base_label.LabelRetriever(cros_label.CROS_LABELS)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700300 # self.env is a dictionary of environment variable settings
301 # to be exported for commands run on the host.
302 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
303 # errors that might happen.
304 self.env['LIBC_FATAL_STDERR_'] = '1'
Fang Dengd1c2b732013-08-20 12:59:46 -0700305 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700306 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700307 # TODO(fdeng): We need to simplify the
308 # process of servo and servo_host initialization.
309 # crbug.com/298432
Moja Hsu57d93a12017-01-13 17:57:52 +0800310 self._servo_host = servo_host.create_servo_host(
Richard Barnetteea3e4602016-06-10 12:36:41 -0700311 dut=self, servo_args=servo_args,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700312 try_lab_servo=try_lab_servo,
313 try_servo_repair=try_servo_repair)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700314 if self._servo_host is not None:
315 self.servo = self._servo_host.get_servo()
316 else:
317 self.servo = None
318
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)
Scottfe06ed82015-11-05 17:15:01 -0800322 # Add plankton host if plankton args were added on command line
323 self._plankton_host = plankton_host.create_plankton_host(plankton_args)
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800324
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800325 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800326 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800327 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800328 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700329
Scottfe06ed82015-11-05 17:15:01 -0800330 if self._plankton_host:
331 self.plankton_servo = self._plankton_host.get_servo()
332 logging.info('plankton_servo: %r', self.plankton_servo)
333 # Create the plankton object used to access the ec uart
Scott07a848f2016-01-12 15:04:52 -0800334 self.plankton = plankton.Plankton(self.plankton_servo,
335 self._plankton_host.get_servod_server_proxy())
Scottfe06ed82015-11-05 17:15:01 -0800336 else:
Scott07a848f2016-01-12 15:04:52 -0800337 self.plankton = None
Scottfe06ed82015-11-05 17:15:01 -0800338
Fang Deng5d518f42013-08-02 14:04:32 -0700339
Richard Barnette260cbd02016-10-06 12:23:28 -0700340 def _get_cros_repair_image_name(self):
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800341 info = self.host_info_store.get()
342 if info.board is None:
343 raise error.AutoservError('Cannot obtain repair image name. '
344 'No board label value found')
345
346 return afe_utils.get_stable_cros_image_name(info.board)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500347
348
beepsdae65fd2013-07-26 16:24:41 -0700349 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700350 """
351 Make sure job_repo_url of this host is valid.
352
joychen03eaad92013-06-26 09:55:21 -0700353 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700354 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
355 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
356 download and extract it. If the devserver embedded in the url is
357 unresponsive, update the job_repo_url of the host after staging it on
358 another devserver.
359
360 @param job_repo_url: A url pointing to the devserver where the autotest
361 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700362 @param tag: The tag from the server job, in the format
363 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700364
365 @raises DevServerException: If we could not resolve a devserver.
366 @raises AutoservError: If we're unable to save the new job_repo_url as
367 a result of choosing a new devserver because the old one failed to
368 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700369 @raises urllib2.URLError: If the devserver embedded in job_repo_url
370 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700371 """
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800372 info = self.host_info_store.get()
373 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
beepscb6f1e22013-06-28 19:14:10 -0700374 if not job_repo_url:
375 logging.warning('No job repo url set on host %s', self.hostname)
376 return
377
378 logging.info('Verifying job repo url %s', job_repo_url)
379 devserver_url, image_name = tools.get_devserver_build_from_package_url(
380 job_repo_url)
381
beeps0c865032013-07-30 11:37:06 -0700382 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700383
384 logging.info('Staging autotest artifacts for %s on devserver %s',
385 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700386
387 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700388 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700389 stage_time = time.time() - start_time
390
391 # Record how much of the verification time comes from a devserver
392 # restage. If we're doing things right we should not see multiple
393 # devservers for a given board/build/branch path.
394 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800395 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700396 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800397 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700398 pass
399 else:
beeps0c865032013-07-30 11:37:06 -0700400 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700401 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700402 stats_key = {
403 'board': board,
404 'build_type': build_type,
405 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700406 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700407 }
Dan Shi5e2efb72017-02-07 11:40:23 -0800408
409 monarch_fields = {
410 'board': board,
411 'build_type': build_type,
412 # TODO(akeshet): To be consistent with most other metrics,
413 # consider changing the following field to be named
414 # 'milestone'.
415 'branch': branch,
416 'dev_server': devserver,
417 }
418 metrics.Counter(
419 'chromeos/autotest/provision/verify_url'
420 ).increment(fields=monarch_fields)
421 metrics.SecondsDistribution(
422 'chromeos/autotest/provision/verify_url_duration'
423 ).add(stage_time, fields=monarch_fields)
424
425
Dan Shicf4d2032015-03-12 15:04:21 -0700426 def stage_server_side_package(self, image=None):
427 """Stage autotest server-side package on devserver.
428
429 @param image: Full path of an OS image to install or a build name.
430
431 @return: A url to the autotest server-side package.
Dan Shi14de7622016-08-22 11:09:06 -0700432
433 @raise: error.AutoservError if fail to locate the build to test with, or
434 fail to stage server-side package.
Dan Shicf4d2032015-03-12 15:04:21 -0700435 """
Dan Shid37736b2016-07-06 15:10:29 -0700436 # If enable_drone_in_restricted_subnet is False, do not set hostname
437 # in devserver.resolve call, so a devserver in non-restricted subnet
438 # is picked to stage autotest server package for drone to download.
439 hostname = self.hostname
440 if not server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
441 hostname = None
Dan Shicf4d2032015-03-12 15:04:21 -0700442 if image:
443 image_name = tools.get_build_from_image(image)
444 if not image_name:
445 raise error.AutoservError(
446 'Failed to parse build name from %s' % image)
Dan Shid37736b2016-07-06 15:10:29 -0700447 ds = dev_server.ImageServer.resolve(image_name, hostname)
Dan Shicf4d2032015-03-12 15:04:21 -0700448 else:
Prathmesh Prabhu9235e4c2017-03-28 13:16:06 -0700449 info = self.host_info_store.get()
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800450 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
Dan Shicf4d2032015-03-12 15:04:21 -0700451 if job_repo_url:
452 devserver_url, image_name = (
453 tools.get_devserver_build_from_package_url(job_repo_url))
Dan Shid37736b2016-07-06 15:10:29 -0700454 # If enable_drone_in_restricted_subnet is True, use the
455 # existing devserver. Otherwise, resolve a new one in
456 # non-restricted subnet.
457 if server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
458 ds = dev_server.ImageServer(devserver_url)
459 else:
460 ds = dev_server.ImageServer.resolve(image_name)
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800461 elif info.build is not None:
462 ds = dev_server.ImageServer.resolve(info.build, hostname)
Prathmesh Prabhu0c1dd4d2017-06-07 13:01:53 -0700463 image_name = info.build
Dan Shicf4d2032015-03-12 15:04:21 -0700464 else:
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800465 raise error.AutoservError(
466 'Failed to stage server-side package. The host has '
467 'no job_report_url attribute or version label.')
Dan Shica503482015-03-30 17:23:25 -0700468
469 # Get the OS version of the build, for any build older than
470 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
471 match = re.match('.*/R\d+-(\d+)\.', image_name)
472 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
Dan Shi14de7622016-08-22 11:09:06 -0700473 raise error.AutoservError(
474 'Build %s is older than %s. Server side packaging is '
475 'disabled.' % (image_name, self.MIN_VERSION_SUPPORT_SSP))
Dan Shica503482015-03-30 17:23:25 -0700476
Dan Shicf4d2032015-03-12 15:04:21 -0700477 ds.stage_artifacts(image_name, ['autotest_server_package'])
478 return '%s/static/%s/%s' % (ds.url(), image_name,
479 'autotest_server_package.tar.bz2')
480
481
Dan Shi0f466e82013-02-22 15:44:58 -0800482 def _try_stateful_update(self, update_url, force_update, updater):
483 """Try to use stateful update to initialize DUT.
484
485 When DUT is already running the same version that machine_install
486 tries to install, stateful update is a much faster way to clean up
487 the DUT for testing, compared to a full reimage. It is implemeted
488 by calling autoupdater.run_update, but skipping updating root, as
489 updating the kernel is time consuming and not necessary.
490
491 @param update_url: url of the image.
492 @param force_update: Set to True to update the image even if the DUT
493 is running the same version.
494 @param updater: ChromiumOSUpdater instance used to update the DUT.
495 @returns: True if the DUT was updated with stateful update.
496
497 """
Laurence Goodby778c9a42017-05-24 19:24:07 -0700498 self.prepare_for_update()
Dan Shi10b98482016-02-02 14:38:50 -0800499
J. Richard Barnette3f731032014-04-07 17:42:59 -0700500 # TODO(jrbarnette): Yes, I hate this re.match() test case.
501 # It's better than the alternative: see crbug.com/360944.
502 image_name = autoupdater.url_to_image_name(update_url)
503 release_pattern = r'^.*-release/R[0-9]+-[0-9]+\.[0-9]+\.0$'
504 if not re.match(release_pattern, image_name):
505 return False
Dan Shi0f466e82013-02-22 15:44:58 -0800506 if not updater.check_version():
507 return False
508 if not force_update:
509 logging.info('Canceling stateful update because the new and '
510 'old versions are the same.')
511 return False
512 # Following folders should be rebuilt after stateful update.
513 # A test file is used to confirm each folder gets rebuilt after
514 # the stateful update.
515 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
516 test_file = '.test_file_to_be_deleted'
517 for folder in folders_to_check:
518 touch_path = os.path.join(folder, test_file)
519 self.run('touch %s' % touch_path)
520
Chris Sosae92399e2015-04-24 11:32:59 -0700521 updater.run_update(update_root=False)
Dan Shi0f466e82013-02-22 15:44:58 -0800522
523 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700524 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
xixuan908b0752016-12-08 18:54:23 -0800525
526 # After stateful update and a reboot, all of the test_files shouldn't
527 # exist any more. Otherwise the stateful update is failed.
528 return not any(
529 self.path_exists(os.path.join(folder, test_file))
530 for folder in folders_to_check)
Dan Shi0f466e82013-02-22 15:44:58 -0800531
532
J. Richard Barnette7275b612013-06-04 18:13:11 -0700533 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800534 """After the DUT is updated, confirm machine_install succeeded.
535
536 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700537 @param expected_kernel: kernel expected to be active after reboot,
538 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800539
540 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700541 # Touch the lab machine file to leave a marker that
542 # distinguishes this image from other test images.
543 # Afterwards, we must re-run the autoreboot script because
544 # it depends on the _LAB_MACHINE_FILE.
J. Richard Barnette71cc1862015-12-02 10:32:38 -0800545 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
546 '( touch "$FILE" ; start autoreboot )')
547 self.run(autoreboot_cmd % self._LAB_MACHINE_FILE)
Chris Sosa65425082013-10-16 13:26:22 -0700548 updater.verify_boot_expectations(
549 expected_kernel, rollback_message=
Gilad Arnoldc26ae1f2015-10-22 16:09:41 -0700550 'Build %s failed to boot on %s; system rolled back to previous '
Chris Sosa65425082013-10-16 13:26:22 -0700551 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700552 # Check that we've got the build we meant to install.
553 if not updater.check_version_to_confirm_install():
554 raise autoupdater.ChromiumOSError(
555 'Failed to update %s to build %s; found build '
556 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700557 updater.update_version,
Dan Shi0942b1d2015-03-31 11:07:00 -0700558 self.get_release_version()))
Dan Shi0f466e82013-02-22 15:44:58 -0800559
Chris Sosae92399e2015-04-24 11:32:59 -0700560 logging.debug('Cleaning up old autotest directories.')
561 try:
562 installed_autodir = autotest.Autotest.get_installed_autodir(self)
563 self.run('rm -rf ' + installed_autodir)
564 except autotest.AutodirNotFoundError:
565 logging.debug('No autotest installed directory found.')
566
Dan Shi0f466e82013-02-22 15:44:58 -0800567
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700568 def _stage_image_for_update(self, image_name=None):
Chris Sosae92399e2015-04-24 11:32:59 -0700569 """Stage a build on a devserver and return the update_url and devserver.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400570
571 @param image_name: a name like lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700572 @returns a tuple with an update URL like:
Scott Zawalskieadbf702013-03-14 09:23:06 -0400573 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700574 and the devserver instance.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400575 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700576 if not image_name:
Richard Barnette260cbd02016-10-06 12:23:28 -0700577 image_name = self._get_cros_repair_image_name()
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700578 logging.info('Staging build for AU: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800579 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400580 devserver.trigger_download(image_name, synchronous=False)
Chris Sosae92399e2015-04-24 11:32:59 -0700581 return (tools.image_url_pattern() % (devserver.url(), image_name),
582 devserver)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400583
584
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700585 def stage_image_for_servo(self, image_name=None):
586 """Stage a build on a devserver and return the update_url.
587
588 @param image_name: a name like lumpy-release/R27-3837.0.0
589 @returns an update URL like:
590 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
591 """
592 if not image_name:
Richard Barnette260cbd02016-10-06 12:23:28 -0700593 image_name = self._get_cros_repair_image_name()
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700594 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800595 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700596 devserver.stage_artifacts(image_name, ['test_image'])
597 return devserver.get_test_image_url(image_name)
598
599
beepse539be02013-07-31 21:57:39 -0700600 def stage_factory_image_for_servo(self, image_name):
601 """Stage a build on a devserver and return the update_url.
602
603 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700604
beepse539be02013-07-31 21:57:39 -0700605 @return: An update URL, eg:
606 http://<devserver>/static/canary-channel/\
607 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700608
609 @raises: ValueError if the factory artifact name is missing from
610 the config.
611
beepse539be02013-07-31 21:57:39 -0700612 """
613 if not image_name:
614 logging.error('Need an image_name to stage a factory image.')
615 return
616
Dan Shib8540a52015-07-16 14:18:23 -0700617 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700618 'CROS', 'factory_artifact', type=str, default='')
619 if not factory_artifact:
620 raise ValueError('Cannot retrieve the factory artifact name from '
621 'autotest config, and hence cannot stage factory '
622 'artifacts.')
623
beepse539be02013-07-31 21:57:39 -0700624 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800625 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
beepse539be02013-07-31 21:57:39 -0700626 devserver.stage_artifacts(
627 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700628 [factory_artifact],
629 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700630
631 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
632
633
xixuan016d95b2017-02-27 14:37:47 -0800634 def _get_au_monarch_fields(self, devserver, build):
635 """Form monarch fields by given devserve & build for auto-update.
636
637 @param devserver: the devserver (ImageServer instance) for auto-update.
638 @param build: the build to be updated.
639
640 @return A dictionary of monach fields.
641 """
642 try:
643 board, build_type, milestone, _ = server_utils.ParseBuildName(build)
644 except server_utils.ParseBuildNameException:
645 logging.warning('Unable to parse build name %s. Something is '
646 'likely broken, but will continue anyway.',
647 build)
648 board, build_type, milestone = ('', '', '')
649
650 monarch_fields = {
651 'dev_server': devserver.hostname,
652 'board': board,
653 'build_type': build_type,
654 'milestone': milestone
655 }
656 return monarch_fields
657
658
xixuanc30d7e92017-05-11 12:06:39 -0700659 def _retry_auto_update_with_new_devserver(self, build, last_devserver,
xixuana3bbc422017-05-04 15:57:21 -0700660 force_update, force_full_update,
661 force_original):
xixuanc30d7e92017-05-11 12:06:39 -0700662 """Kick off auto-update by devserver and send metrics.
663
664 @param build: the build to update.
665 @param last_devserver: the last devserver that failed to provision.
666 @param force_update: see |machine_install_by_devserver|'s force_udpate
667 for details.
668 @param force_full_update: see |machine_install_by_devserver|'s
669 force_full_update for details.
xixuana3bbc422017-05-04 15:57:21 -0700670 @param force_original: Whether to force stateful update with the
671 original payload.
xixuanc30d7e92017-05-11 12:06:39 -0700672
673 @return the result of |auto_update| in dev_server.
674 """
675 devserver = dev_server.resolve(
676 build, self.hostname, ban_list=[last_devserver.url()])
677 devserver.trigger_download(build, synchronous=False)
678 monarch_fields = self._get_au_monarch_fields(devserver, build)
679 logging.debug('Retry auto_update: resolved devserver for '
680 'auto-update: %s', devserver.url())
681
682 # Add metrics
683 install_with_dev_counter = metrics.Counter(
684 'chromeos/autotest/provision/install_with_devserver')
685 install_with_dev_counter.increment(fields=monarch_fields)
686 c = metrics.Counter(
687 'chromeos/autotest/provision/retry_by_devserver')
688 monarch_fields['last_devserver'] = last_devserver.hostname
689 monarch_fields['host'] = self.hostname
690 c.increment(fields=monarch_fields)
691
692 return devserver.auto_update(
693 self.hostname, build,
694 original_board=self.get_board().replace(
695 ds_constants.BOARD_PREFIX, ''),
696 original_release_version=self.get_release_version(),
697 log_dir=self.job.resultdir,
698 force_update=force_update,
xixuana3bbc422017-05-04 15:57:21 -0700699 full_update=force_full_update,
700 force_original=force_original)
xixuanc30d7e92017-05-11 12:06:39 -0700701
702
xixuanec801e32016-08-25 10:20:22 -0700703 def machine_install_by_devserver(self, update_url=None, force_update=False,
xixuan5dc64ea2016-05-20 17:27:51 -0700704 local_devserver=False, repair=False,
705 force_full_update=False):
706 """Ultiize devserver to install the DUT.
707
708 (TODO) crbugs.com/627269: The logic in this function has some overlap
709 with those in function machine_install. The merge will be done later,
710 not in the same CL.
711
xixuanec801e32016-08-25 10:20:22 -0700712 @param update_url: The update_url or build for the host to update.
xixuan5dc64ea2016-05-20 17:27:51 -0700713 @param force_update: Force an update even if the version installed
714 is the same. Default:False
715 @param local_devserver: Used by test_that to allow people to
716 use their local devserver. Default: False
717 @param repair: Forces update to repair image. Implies force_update.
718 @param force_full_update: If True, do not attempt to run stateful
719 update, force a full reimage. If False, try stateful update
720 first when the dut is already installed with the same version.
721 @raises autoupdater.ChromiumOSError
722
723 @returns A tuple of (image_name, host_attributes).
724 image_name is the name of image installed, e.g.,
725 veyron_jerry-release/R50-7871.0.0
726 host_attributes is a dictionary of (attribute, value), which
727 can be saved to afe_host_attributes table in database. This
728 method returns a dictionary with a single entry of
729 `job_repo_url`: repo_url, where repo_url is a devserver url to
730 autotest packages.
731 """
xixuan5dc64ea2016-05-20 17:27:51 -0700732 if repair:
Richard Barnette260cbd02016-10-06 12:23:28 -0700733 update_url = self._get_cros_repair_image_name()
xixuan5dc64ea2016-05-20 17:27:51 -0700734 force_update = True
735
xixuanec801e32016-08-25 10:20:22 -0700736 if not update_url and not self._parser.options.image:
xixuan5dc64ea2016-05-20 17:27:51 -0700737 raise error.AutoservError(
738 'There is no update URL, nor a method to get one.')
739
xixuanec801e32016-08-25 10:20:22 -0700740 if not update_url and self._parser.options.image:
741 update_url = self._parser.options.image
xixuan5dc64ea2016-05-20 17:27:51 -0700742
xixuan5dc64ea2016-05-20 17:27:51 -0700743
744 # Get build from parameter or AFE.
745 # If the build is not a URL, let devserver to stage it first.
746 # Otherwise, choose a devserver to trigger auto-update.
xixuanec801e32016-08-25 10:20:22 -0700747 build = None
Aviv Keshetf9375482016-10-26 02:09:03 -0700748 devserver = None
749 logging.debug('Resolving a devserver for auto-update')
Aviv Keshet3a76f762016-10-27 17:41:51 -0700750 previously_resolved = False
xixuanec801e32016-08-25 10:20:22 -0700751 if update_url.startswith('http://'):
752 build = autoupdater.url_to_image_name(update_url)
Aviv Keshet3a76f762016-10-27 17:41:51 -0700753 previously_resolved = True
754 else:
755 build = update_url
756 devserver = dev_server.resolve(build, self.hostname)
Allen Lia5cfb972016-12-27 17:17:22 -0800757 server_name = devserver.hostname
Aviv Keshet3a76f762016-10-27 17:41:51 -0700758
xixuan016d95b2017-02-27 14:37:47 -0800759 monarch_fields = self._get_au_monarch_fields(devserver, build)
Aviv Keshet3a76f762016-10-27 17:41:51 -0700760
761 if previously_resolved:
xixuan2e62d162016-09-29 19:19:19 -0700762 # Make sure devserver for Auto-Update has staged the build.
xixuan2e62d162016-09-29 19:19:19 -0700763 if server_name not in update_url:
Aviv Keshet3a76f762016-10-27 17:41:51 -0700764 logging.debug('Resolved to devserver that does not match '
765 'update_url. The previously resolved devserver '
766 'must be unhealthy. Switching to use devserver %s,'
767 ' and re-staging.',
xixuan2e62d162016-09-29 19:19:19 -0700768 server_name)
Aviv Keshetf9375482016-10-26 02:09:03 -0700769 logging.info('Staging build for AU: %s', update_url)
xixuan2e62d162016-09-29 19:19:19 -0700770 devserver.trigger_download(build, synchronous=False)
Dan Shi5e2efb72017-02-07 11:40:23 -0800771 c = metrics.Counter(
772 'chromeos/autotest/provision/failover_download')
773 c.increment(fields=monarch_fields)
xixuan5dc64ea2016-05-20 17:27:51 -0700774 else:
Aviv Keshetf9375482016-10-26 02:09:03 -0700775 logging.info('Staging build for AU: %s', update_url)
xixuan5dc64ea2016-05-20 17:27:51 -0700776 devserver.trigger_download(build, synchronous=False)
Dan Shi5e2efb72017-02-07 11:40:23 -0800777 c = metrics.Counter('chromeos/autotest/provision/trigger_download')
778 c.increment(fields=monarch_fields)
xixuan5dc64ea2016-05-20 17:27:51 -0700779
780 # Report provision stats.
xixuan016d95b2017-02-27 14:37:47 -0800781 install_with_dev_counter = metrics.Counter(
782 'chromeos/autotest/provision/install_with_devserver')
783 install_with_dev_counter.increment(fields=monarch_fields)
xixuanec801e32016-08-25 10:20:22 -0700784 logging.debug('Resolved devserver for auto-update: %s', devserver.url())
xixuan5dc64ea2016-05-20 17:27:51 -0700785
Aviv Keshetf9375482016-10-26 02:09:03 -0700786 # and other metrics from this function.
Dan Shi5e2efb72017-02-07 11:40:23 -0800787 metrics.Counter('chromeos/autotest/provision/resolve'
788 ).increment(fields=monarch_fields)
Aviv Keshetf9375482016-10-26 02:09:03 -0700789
xixuana3bbc422017-05-04 15:57:21 -0700790 force_original = self.get_chromeos_release_milestone() is None
791
xixuan016d95b2017-02-27 14:37:47 -0800792 success, retryable = devserver.auto_update(
xixuanf5ee7862016-11-30 16:53:05 -0800793 self.hostname, build,
794 original_board=self.get_board().replace(
795 ds_constants.BOARD_PREFIX, ''),
796 original_release_version=self.get_release_version(),
797 log_dir=self.job.resultdir,
798 force_update=force_update,
xixuana3bbc422017-05-04 15:57:21 -0700799 full_update=force_full_update,
800 force_original=force_original)
xixuan016d95b2017-02-27 14:37:47 -0800801 if not success and retryable:
802 # It indicates that last provision failed due to devserver load
803 # issue, so another devserver is resolved to kick off provision
804 # job once again and only once.
805 logging.debug('Provision failed due to devserver issue,'
806 'retry it with another devserver.')
807
808 # Check first whether this DUT is completely offline. If so, skip
809 # the following provision tries.
810 logging.debug('Checking whether host %s is online.', self.hostname)
811 if utils.ping(self.hostname, tries=1, deadline=1) == 0:
xixuanc30d7e92017-05-11 12:06:39 -0700812 self._retry_auto_update_with_new_devserver(
xixuana3bbc422017-05-04 15:57:21 -0700813 build, devserver, force_update, force_full_update,
814 force_original)
xixuanc30d7e92017-05-11 12:06:39 -0700815 else:
816 raise error.AutoservError(
817 'No answer to ping from %s' % self.hostname)
xixuan5dc64ea2016-05-20 17:27:51 -0700818
819 # The reason to resolve a new devserver in function machine_install
820 # is mostly because that the update_url there may has a strange format,
821 # and it's hard to parse the devserver url from it.
822 # Since we already resolve a devserver to trigger auto-update, the same
823 # devserver is used to form JOB_REPO_URL here. Verified in local test.
824 repo_url = tools.get_package_url(devserver.url(), build)
825 return build, {ds_constants.JOB_REPO_URL: repo_url}
826
827
Laurence Goodby778c9a42017-05-24 19:24:07 -0700828 def prepare_for_update(self):
829 """Prepares the DUT for an update.
830
831 Subclasses may override this to perform any special actions
832 required before updating.
833 """
Laurence Goodby468de252017-06-08 17:22:53 -0700834 pass
Laurence Goodby778c9a42017-05-24 19:24:07 -0700835
836
Chris Sosaa3ac2152012-05-23 22:23:13 -0700837 def machine_install(self, update_url=None, force_update=False,
Richard Barnette0b023a72015-04-24 16:07:30 +0000838 local_devserver=False, repair=False,
839 force_full_update=False):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500840 """Install the DUT.
841
Dan Shi0f466e82013-02-22 15:44:58 -0800842 Use stateful update if the DUT is already running the same build.
843 Stateful update does not update kernel and tends to run much faster
844 than a full reimage. If the DUT is running a different build, or it
845 failed to do a stateful update, full update, including kernel update,
846 will be applied to the DUT.
847
Simran Basi5ace6f22016-01-06 17:30:44 -0800848 Once a host enters machine_install its host attribute job_repo_url
849 (used for package install) will be removed and then updated.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400850
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500851 @param update_url: The url to use for the update
852 pattern: http://$devserver:###/update/$build
853 If update_url is None and repair is True we will install the
Dan Shi6964fa52014-12-18 11:04:27 -0800854 stable image listed in afe_stable_versions table. If the table
855 is not setup, global_config value under CROS.stable_cros_version
856 will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500857 @param force_update: Force an update even if the version installed
858 is the same. Default:False
Christopher Wiley6a4ff932015-05-15 14:00:47 -0700859 @param local_devserver: Used by test_that to allow people to
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500860 use their local devserver. Default: False
Chris Sosae92399e2015-04-24 11:32:59 -0700861 @param repair: Forces update to repair image. Implies force_update.
Fang Deng3d3b9272014-12-22 12:20:28 -0800862 @param force_full_update: If True, do not attempt to run stateful
863 update, force a full reimage. If False, try stateful update
864 first when the dut is already installed with the same version.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500865 @raises autoupdater.ChromiumOSError
866
Dan Shibe3636a2016-02-14 22:48:01 -0800867 @returns A tuple of (image_name, host_attributes).
868 image_name is the name of image installed, e.g.,
869 veyron_jerry-release/R50-7871.0.0
870 host_attributes is a dictionary of (attribute, value), which
871 can be saved to afe_host_attributes table in database. This
872 method returns a dictionary with a single entry of
873 `job_repo_url`: repo_url, where repo_url is a devserver url to
874 autotest packages.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500875 """
Chris Sosae92399e2015-04-24 11:32:59 -0700876 devserver = None
Richard Barnette0b023a72015-04-24 16:07:30 +0000877 if repair:
Chris Sosae92399e2015-04-24 11:32:59 -0700878 update_url, devserver = self._stage_image_for_update()
Richard Barnette0b023a72015-04-24 16:07:30 +0000879 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800880
Chris Sosae92399e2015-04-24 11:32:59 -0700881 if not update_url and not self._parser.options.image:
882 raise error.AutoservError(
Dan Shid07ee2e2015-09-24 14:49:25 -0700883 'There is no update URL, nor a method to get one.')
Chris Sosae92399e2015-04-24 11:32:59 -0700884
885 if not update_url and self._parser.options.image:
886 # This is the base case where we have no given update URL i.e.
887 # dynamic suites logic etc. This is the most flexible case where we
888 # can serve an update from any of our fleet of devservers.
889 requested_build = self._parser.options.image
890 if not requested_build.startswith('http://'):
891 logging.debug('Update will be staged for this installation')
892 update_url, devserver = self._stage_image_for_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700893 requested_build)
Chris Sosae92399e2015-04-24 11:32:59 -0700894 else:
895 update_url = requested_build
896
897 logging.debug('Update URL is %s', update_url)
898
Dan Shif48f8132016-02-18 10:34:30 -0800899 # Report provision stats.
Allen Lia5cfb972016-12-27 17:17:22 -0800900 server_name = dev_server.get_hostname(update_url)
Allen Li48a13fe2016-11-22 14:10:40 -0800901 (metrics.Counter('chromeos/autotest/provision/install')
902 .increment(fields={'devserver': server_name}))
Dan Shif48f8132016-02-18 10:34:30 -0800903
Dan Shid07ee2e2015-09-24 14:49:25 -0700904 # Create a file to indicate if provision fails. The file will be removed
905 # by stateful update or full install.
J. Richard Barnette91137f02016-03-10 16:52:26 -0800906 self.run('touch %s' % self.PROVISION_FAILED)
Dan Shid07ee2e2015-09-24 14:49:25 -0700907
Chris Sosae92399e2015-04-24 11:32:59 -0700908 update_complete = False
909 updater = autoupdater.ChromiumOSUpdater(
910 update_url, host=self, local_devserver=local_devserver)
Fang Deng3d3b9272014-12-22 12:20:28 -0800911 if not force_full_update:
912 try:
Chris Sosae92399e2015-04-24 11:32:59 -0700913 # If the DUT is already running the same build, try stateful
914 # update first as it's much quicker than a full re-image.
915 update_complete = self._try_stateful_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700916 update_url, force_update, updater)
Fang Deng3d3b9272014-12-22 12:20:28 -0800917 except Exception as e:
918 logging.exception(e)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700919
Dan Shi0f466e82013-02-22 15:44:58 -0800920 inactive_kernel = None
Chris Sosae92399e2015-04-24 11:32:59 -0700921 if update_complete or (not force_update and updater.check_version()):
922 logging.info('Install complete without full update')
923 else:
924 logging.info('DUT requires full update.')
925 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Laurence Goodby778c9a42017-05-24 19:24:07 -0700926 self.prepare_for_update()
Dan Shi10b98482016-02-02 14:38:50 -0800927
Chris Sosae92399e2015-04-24 11:32:59 -0700928 num_of_attempts = provision.FLAKY_DEVSERVER_ATTEMPTS
Chris Sosab7612bc2013-03-21 10:32:37 -0700929
Chris Sosae92399e2015-04-24 11:32:59 -0700930 while num_of_attempts > 0:
931 num_of_attempts -= 1
932 try:
933 updater.run_update()
934 except Exception:
935 logging.warn('Autoupdate did not complete.')
936 # Do additional check for the devserver health. Ideally,
937 # the autoupdater.py could raise an exception when it
938 # detected network flake but that would require
939 # instrumenting the update engine and parsing it log.
940 if (num_of_attempts <= 0 or
941 devserver is None or
xixuan9e2c98d2016-02-26 19:04:53 -0800942 dev_server.ImageServer.devserver_healthy(
Chris Sosae92399e2015-04-24 11:32:59 -0700943 devserver.url())):
Dan Shid07ee2e2015-09-24 14:49:25 -0700944 raise
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700945
Chris Sosae92399e2015-04-24 11:32:59 -0700946 logging.warn('Devserver looks unhealthy. Trying another')
947 update_url, devserver = self._stage_image_for_update(
948 requested_build)
949 logging.debug('New Update URL is %s', update_url)
950 updater = autoupdater.ChromiumOSUpdater(
951 update_url, host=self,
952 local_devserver=local_devserver)
953 else:
954 break
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700955
Chris Sosae92399e2015-04-24 11:32:59 -0700956 # Give it some time in case of IO issues.
957 time.sleep(10)
Dan Shi5699ac22014-12-19 10:55:49 -0800958
Chris Sosae92399e2015-04-24 11:32:59 -0700959 # Figure out active and inactive kernel.
960 active_kernel, inactive_kernel = updater.get_kernel_state()
Simran Basi13fa1ba2013-03-04 10:56:47 -0800961
Chris Sosae92399e2015-04-24 11:32:59 -0700962 # Ensure inactive kernel has higher priority than active.
963 if (updater.get_kernel_priority(inactive_kernel)
964 < updater.get_kernel_priority(active_kernel)):
965 raise autoupdater.ChromiumOSError(
966 'Update failed. The priority of the inactive kernel'
967 ' partition is less than that of the active kernel'
968 ' partition.')
969
970 # Updater has returned successfully; reboot the host.
Richard Barnettefc3d7432016-07-07 10:21:31 -0700971 #
972 # Regarding the 'crossystem' command: In some cases, the
973 # TPM gets into a state such that it fails verification.
974 # We don't know why. However, this call papers over the
975 # problem by clearing the TPM during the reboot.
976 #
977 # We ignore failures from 'crossystem'. Although failure
978 # here is unexpected, and could signal a bug, the point
979 # of the exercise is to paper over problems; allowing
980 # this to fail would defeat the purpose.
981 self.run('crossystem clear_tpm_owner_request=1',
982 ignore_status=True)
Chris Sosae92399e2015-04-24 11:32:59 -0700983 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
984
985 self._post_update_processing(updater, inactive_kernel)
Simran Basi5ace6f22016-01-06 17:30:44 -0800986 image_name = autoupdater.url_to_image_name(update_url)
Dan Shibe3636a2016-02-14 22:48:01 -0800987 # update_url is different from devserver url needed to stage autotest
988 # packages, therefore, resolve a new devserver url here.
989 devserver_url = dev_server.ImageServer.resolve(image_name,
990 self.hostname).url()
991 repo_url = tools.get_package_url(devserver_url, image_name)
992 return image_name, {ds_constants.JOB_REPO_URL: repo_url}
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700993
994
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800995 def _clear_fw_version_labels(self, rw_only):
996 """Clear firmware version labels from the machine.
997
998 @param rw_only: True to only clear fwrw_version; otherewise, clear
999 both fwro_version and fwrw_version.
1000 """
Dan Shi9cb0eec2014-06-03 09:04:50 -07001001 labels = self._AFE.get_labels(
Dan Shi0723bf52015-06-24 10:52:38 -07001002 name__startswith=provision.FW_RW_VERSION_PREFIX,
Dan Shi9cb0eec2014-06-03 09:04:50 -07001003 host__hostname=self.hostname)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001004 if not rw_only:
1005 labels = labels + self._AFE.get_labels(
1006 name__startswith=provision.FW_RO_VERSION_PREFIX,
1007 host__hostname=self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001008 for label in labels:
1009 label.remove_hosts(hosts=[self.hostname])
1010
1011
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001012 def _add_fw_version_label(self, build, rw_only):
Dan Shi9cb0eec2014-06-03 09:04:50 -07001013 """Add firmware version label to the machine.
1014
1015 @param build: Build of firmware.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001016 @param rw_only: True to only add fwrw_version; otherwise, add both
1017 fwro_version and fwrw_version.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001018
1019 """
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +00001020 fw_label = provision.fwrw_version_to_label(build)
MK Ryu73be9862015-07-06 12:25:00 -07001021 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001022 if not rw_only:
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +00001023 fw_label = provision.fwro_version_to_label(build)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001024 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Dan Shi9cb0eec2014-06-03 09:04:50 -07001025
1026
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001027 def firmware_install(self, build=None, rw_only=False):
Dan Shi9cb0eec2014-06-03 09:04:50 -07001028 """Install firmware to the DUT.
1029
1030 Use stateful update if the DUT is already running the same build.
1031 Stateful update does not update kernel and tends to run much faster
1032 than a full reimage. If the DUT is running a different build, or it
1033 failed to do a stateful update, full update, including kernel update,
1034 will be applied to the DUT.
1035
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001036 Once a host enters firmware_install its fw[ro|rw]_version label will
1037 be removed. After the firmware is updated successfully, a new
1038 fw[ro|rw]_version label will be added to the host.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001039
1040 @param build: The build version to which we want to provision the
1041 firmware of the machine,
1042 e.g. 'link-firmware/R22-2695.1.144'.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001043 @param rw_only: True to only install firmware to its RW portions. Keep
1044 the RO portions unchanged.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001045
1046 TODO(dshi): After bug 381718 is fixed, update here with corresponding
1047 exceptions that could be raised.
1048
1049 """
1050 if not self.servo:
1051 raise error.TestError('Host %s does not have servo.' %
1052 self.hostname)
1053
Wai-Hong Tam64cf7912017-05-23 16:41:40 -07001054 # Get the DUT board name from servod.
1055 board = self.servo.get_board()
Dan Shi9cb0eec2014-06-03 09:04:50 -07001056
Chris Sosae92399e2015-04-24 11:32:59 -07001057 # If build is not set, try to install firmware from stable CrOS.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001058 if not build:
Richard Barnette260cbd02016-10-06 12:23:28 -07001059 build = afe_utils.get_stable_faft_version(board)
Dan Shi3d7a0e12015-10-12 11:55:45 -07001060 if not build:
1061 raise error.TestError(
1062 'Failed to find stable firmware build for %s.',
1063 self.hostname)
1064 logging.info('Will install firmware from build %s.', build)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001065
1066 config = FAFTConfig(board)
1067 if config.use_u_boot:
1068 ap_image = 'image-%s.bin' % board
1069 else: # Depthcharge platform
1070 ap_image = 'image.bin'
1071 ec_image = 'ec.bin'
Dan Shi216389c2015-12-22 11:03:06 -08001072 ds = dev_server.ImageServer.resolve(build, self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001073 ds.stage_artifacts(build, ['firmware'])
1074
1075 tmpd = autotemp.tempdir(unique_id='fwimage')
1076 try:
1077 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
1078 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
xixuan4e116822016-11-17 15:32:10 -08001079 ds.download_file(fwurl, local_tarball)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001080
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001081 self._clear_fw_version_labels(rw_only)
Tom Wai-Hong Tam552fee12016-05-11 06:27:58 +08001082 if config.chrome_ec:
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +08001083 logging.info('Will re-program EC %snow', 'RW ' if rw_only else '')
1084 server_utils.system('tar xf %s -C %s %s' %
1085 (local_tarball, tmpd.name, ec_image),
1086 timeout=60)
1087 self.servo.program_ec(os.path.join(tmpd.name, ec_image), rw_only)
1088 else:
1089 logging.info('Not a Chrome EC, ignore re-programing it')
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001090 logging.info('Will re-program BIOS %snow', 'RW ' if rw_only else '')
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +08001091 server_utils.system('tar xf %s -C %s %s' %
1092 (local_tarball, tmpd.name, ap_image),
1093 timeout=60)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001094 self.servo.program_bios(os.path.join(tmpd.name, ap_image), rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001095 self.servo.get_power_state_controller().reset()
1096 time.sleep(self.servo.BOOT_DELAY)
Wai-Hong Tamb5f66ce2016-11-10 15:45:30 -08001097 if utils.host_is_in_lab_zone(self.hostname):
1098 self._add_fw_version_label(build, rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001099 finally:
1100 tmpd.clean()
1101
1102
Dan Shi10e992b2013-08-30 11:02:59 -07001103 def show_update_engine_log(self):
1104 """Output update engine log."""
MK Ryu35d661e2014-09-25 17:44:10 -07001105 logging.debug('Dumping %s', client_constants.UPDATE_ENGINE_LOG)
1106 self.run('cat %s' % client_constants.UPDATE_ENGINE_LOG)
Dan Shi10e992b2013-08-30 11:02:59 -07001107
1108
Richard Barnette82c35912012-11-20 10:09:10 -08001109 def _get_board_from_afe(self):
Kevin Cheng84a71ba2016-07-14 11:03:57 -07001110 """Retrieve this host's board from its labels stored locally.
Richard Barnette82c35912012-11-20 10:09:10 -08001111
1112 Looks for a host label of the form "board:<board>", and
1113 returns the "<board>" part of the label. `None` is returned
1114 if there is not a single, unique label matching the pattern.
1115
1116 @returns board from label, or `None`.
1117 """
Richard Barnette260cbd02016-10-06 12:23:28 -07001118 board = afe_utils.get_board(self)
1119 if board is None:
1120 raise error.AutoservError('DUT cannot be repaired, '
1121 'there is no board attribute.')
1122 return board
Simran Basi833814b2013-01-29 13:13:43 -08001123
1124
beepsf079cfb2013-09-18 17:49:51 -07001125 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
1126 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001127 """
1128 Re-install the OS on the DUT by:
1129 1) installing a test image on a USB storage device attached to the Servo
1130 board,
Richard Barnette03a0c132012-11-05 12:40:35 -08001131 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001132 3) installing the image with chromeos-install.
1133
Scott Zawalski62bacae2013-03-05 10:40:32 -05001134 @param image_url: If specified use as the url to install on the DUT.
1135 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -07001136 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
1137 Factory images need a longer usb_boot_timeout than regular
1138 cros images.
1139 @param install_timeout: The timeout to use when installing the chromeos
1140 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -08001141
Scott Zawalski62bacae2013-03-05 10:40:32 -05001142 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -07001143
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001144 """
beepsf079cfb2013-09-18 17:49:51 -07001145 logging.info('Downloading image to USB, then booting from it. Usb boot '
1146 'timeout = %s', usb_boot_timeout)
Allen Li48a13fe2016-11-22 14:10:40 -08001147 with metrics.SecondsTimer(
1148 'chromeos/autotest/provision/servo_install/boot_duration'):
1149 self.servo.install_recovery_image(image_url)
1150 if not self.wait_up(timeout=usb_boot_timeout):
1151 raise hosts.AutoservRepairError(
1152 'DUT failed to boot from USB after %d seconds' %
1153 usb_boot_timeout)
Scott Zawalski62bacae2013-03-05 10:40:32 -05001154
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001155 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
1156 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001157 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001158 try:
1159 self.run('chromeos-tpm-recovery')
1160 except error.AutoservRunError:
1161 logging.warn('chromeos-tpm-recovery is too old.')
1162
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001163
Allen Li48a13fe2016-11-22 14:10:40 -08001164 with metrics.SecondsTimer(
1165 'chromeos/autotest/provision/servo_install/install_duration'):
1166 logging.info('Installing image through chromeos-install.')
1167 self.run('chromeos-install --yes', timeout=install_timeout)
1168 self.halt()
beepsf079cfb2013-09-18 17:49:51 -07001169
1170 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001171 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -07001172 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001173 # N.B. The Servo API requires that we use power_on() here
1174 # for two reasons:
1175 # 1) After turning on a DUT in recovery mode, you must turn
1176 # it off and then on with power_on() once more to
1177 # disable recovery mode (this is a Parrot specific
1178 # requirement).
1179 # 2) After power_off(), the only way to turn on is with
1180 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -07001181 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001182
1183 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001184 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
1185 raise error.AutoservError('DUT failed to reboot installed '
1186 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -05001187 self.BOOT_TIMEOUT)
1188
1189
Richard Barnette9a26ad62016-06-10 12:03:08 -07001190 def repair_servo(self):
Dan Shi90466352015-09-22 15:01:05 -07001191 """
Richard Barnette9a26ad62016-06-10 12:03:08 -07001192 Confirm that servo is initialized and verified.
Dan Shi90466352015-09-22 15:01:05 -07001193
Richard Barnette9a26ad62016-06-10 12:03:08 -07001194 If the servo object is missing, attempt to repair the servo
1195 host. Repair failures are passed back to the caller.
1196
1197 @raise AutoservError: If there is no servo host for this CrOS
1198 host.
1199 """
1200 if self.servo:
1201 return
1202 if not self._servo_host:
1203 raise error.AutoservError('No servo host for %s.' %
1204 self.hostname)
1205 self._servo_host.repair()
1206 self.servo = self._servo_host.get_servo()
Dan Shi90466352015-09-22 15:01:05 -07001207
1208
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -08001209 def repair(self):
1210 """Attempt to get the DUT to pass `self.verify()`.
Richard Barnette82c35912012-11-20 10:09:10 -08001211
1212 This overrides the base class function for repair; it does
J. Richard Barnette91137f02016-03-10 16:52:26 -08001213 not call back to the parent class, but instead relies on
1214 `self._repair_strategy` to coordinate the verification and
1215 repair steps needed to get the DUT working.
Richard Barnette82c35912012-11-20 10:09:10 -08001216 """
J. Richard Barnette91137f02016-03-10 16:52:26 -08001217 self._repair_strategy.repair(self)
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001218
Richard Barnette82c35912012-11-20 10:09:10 -08001219
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001220 def close(self):
Fang Deng0ca40e22013-08-27 17:47:44 -07001221 super(CrosHost, self).close()
xixuand6011f12016-12-08 15:01:58 -08001222 if self._chameleon_host:
1223 self._chameleon_host.close()
1224
1225 if self._servo_host:
1226 self._servo_host.close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001227
1228
Dan Shi49ca0932014-11-14 11:22:27 -08001229 def get_power_supply_info(self):
1230 """Get the output of power_supply_info.
1231
1232 power_supply_info outputs the info of each power supply, e.g.,
1233 Device: Line Power
1234 online: no
1235 type: Mains
1236 voltage (V): 0
1237 current (A): 0
1238 Device: Battery
1239 state: Discharging
1240 percentage: 95.9276
1241 technology: Li-ion
1242
1243 Above output shows two devices, Line Power and Battery, with details of
1244 each device listed. This function parses the output into a dictionary,
1245 with key being the device name, and value being a dictionary of details
1246 of the device info.
1247
1248 @return: The dictionary of power_supply_info, e.g.,
1249 {'Line Power': {'online': 'yes', 'type': 'main'},
1250 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001251 @raise error.AutoservRunError if power_supply_info tool is not found in
1252 the DUT. Caller should handle this error to avoid false failure
1253 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001254 """
1255 result = self.run('power_supply_info').stdout.strip()
1256 info = {}
1257 device_name = None
1258 device_info = {}
1259 for line in result.split('\n'):
1260 pair = [v.strip() for v in line.split(':')]
1261 if len(pair) != 2:
1262 continue
1263 if pair[0] == 'Device':
1264 if device_name:
1265 info[device_name] = device_info
1266 device_name = pair[1]
1267 device_info = {}
1268 else:
1269 device_info[pair[0]] = pair[1]
1270 if device_name and not device_name in info:
1271 info[device_name] = device_info
1272 return info
1273
1274
1275 def get_battery_percentage(self):
1276 """Get the battery percentage.
1277
1278 @return: The percentage of battery level, value range from 0-100. Return
1279 None if the battery info cannot be retrieved.
1280 """
1281 try:
1282 info = self.get_power_supply_info()
1283 logging.info(info)
1284 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001285 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001286 return None
1287
1288
1289 def is_ac_connected(self):
1290 """Check if the dut has power adapter connected and charging.
1291
1292 @return: True if power adapter is connected and charging.
1293 """
1294 try:
1295 info = self.get_power_supply_info()
1296 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001297 except (KeyError, error.AutoservRunError):
1298 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001299
1300
Simran Basi5e6339a2013-03-21 11:34:32 -07001301 def _cleanup_poweron(self):
1302 """Special cleanup method to make sure hosts always get power back."""
1303 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1304 hosts = afe.get_hosts(hostname=self.hostname)
1305 if not hosts or not (self._RPM_OUTLET_CHANGED in
1306 hosts[0].attributes):
1307 return
1308 logging.debug('This host has recently interacted with the RPM'
1309 ' Infrastructure. Ensuring power is on.')
1310 try:
1311 self.power_on()
Dan Shi7dca56e2014-11-11 17:07:56 -08001312 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1313 hostname=self.hostname)
Simran Basi5e6339a2013-03-21 11:34:32 -07001314 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001315 logging.error('Failed to turn Power On for this host after '
1316 'cleanup through the RPM Infrastructure.')
Gabe Blackb72f4fb2015-01-20 16:47:13 -08001317 autotest_es.post(
Dan Shi7dca56e2014-11-11 17:07:56 -08001318 type_str='RPM_poweron_failure',
1319 metadata={'hostname': self.hostname})
Dan Shi49ca0932014-11-14 11:22:27 -08001320
1321 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -08001322 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -08001323 raise
1324 elif self.is_ac_connected():
1325 logging.info('The device has power adapter connected and '
1326 'charging. No need to try to turn RPM on '
1327 'again.')
1328 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1329 hostname=self.hostname)
1330 logging.info('Battery level is now at %s%%. The device may '
1331 'still have enough power to run test, so no '
1332 'exception will be raised.', battery_percentage)
1333
Simran Basi5e6339a2013-03-21 11:34:32 -07001334
beepsc87ff602013-07-31 21:53:00 -07001335 def _is_factory_image(self):
1336 """Checks if the image on the DUT is a factory image.
1337
1338 @return: True if the image on the DUT is a factory image.
1339 False otherwise.
1340 """
1341 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1342 return result.exit_status == 0
1343
1344
1345 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001346 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001347
1348 @raises: FactoryImageCheckerException for factory images, since
1349 we cannot attempt to restart ui on them.
1350 error.AutoservRunError for any other type of error that
1351 occurs while restarting ui.
1352 """
1353 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001354 raise FactoryImageCheckerException('Cannot restart ui on factory '
1355 'images')
beepsc87ff602013-07-31 21:53:00 -07001356
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001357 # TODO(jrbarnette): The command to stop/start the ui job
1358 # should live inside cros_ui, too. However that would seem
1359 # to imply interface changes to the existing start()/restart()
1360 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -07001361 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001362 self.run('stop ui; start ui')
1363 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001364
1365
xixuana3bbc422017-05-04 15:57:21 -07001366 def _get_lsb_release_content(self):
1367 """Return the content of lsb-release file of host."""
1368 return self.run(
1369 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1370
1371
Dan Shi549fb822015-03-24 18:01:11 -07001372 def get_release_version(self):
1373 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1374
1375 @returns The version string in lsb-release, under attribute
1376 CHROMEOS_RELEASE_VERSION.
1377 """
Dan Shi549fb822015-03-24 18:01:11 -07001378 return lsbrelease_utils.get_chromeos_release_version(
xixuana3bbc422017-05-04 15:57:21 -07001379 lsb_release_content=self._get_lsb_release_content())
1380
1381
1382 def get_chromeos_release_milestone(self):
1383 """Get the value of attribute CHROMEOS_RELEASE_BUILD_TYPE
1384 from lsb-release.
1385
1386 @returns The version string in lsb-release, under attribute
1387 CHROMEOS_RELEASE_BUILD_TYPE.
1388 """
1389 return lsbrelease_utils.get_chromeos_release_milestone(
1390 lsb_release_content=self._get_lsb_release_content())
Dan Shi549fb822015-03-24 18:01:11 -07001391
1392
1393 def verify_cros_version_label(self):
1394 """ Make sure host's cros-version label match the actual image in dut.
1395
1396 Remove any cros-version: label that doesn't match that installed in
1397 the dut.
1398
1399 @param raise_error: Set to True to raise exception if any mismatch found
1400
1401 @raise error.AutoservError: If any mismatch between cros-version label
1402 and the build installed in dut is found.
1403 """
1404 labels = self._AFE.get_labels(
1405 name__startswith=ds_constants.VERSION_PREFIX,
1406 host__hostname=self.hostname)
1407 mismatch_found = False
1408 if labels:
1409 # Get CHROMEOS_RELEASE_VERSION from lsb-release, e.g., 6908.0.0.
1410 # Note that it's different from cros-version label, which has
1411 # builder and branch info, e.g.,
1412 # cros-version:peppy-release/R43-6908.0.0
1413 release_version = self.get_release_version()
1414 host_list = [self.hostname]
1415 for label in labels:
1416 # Remove any cros-version label that does not match
1417 # release_version.
1418 build_version = label.name[len(ds_constants.VERSION_PREFIX):]
1419 if not utils.version_match(build_version, release_version):
1420 logging.warn('cros-version label "%s" does not match '
1421 'release version %s. Removing the label.',
1422 label.name, release_version)
1423 label.remove_hosts(hosts=host_list)
1424 mismatch_found = True
1425 if mismatch_found:
Dan Shi1057bae2015-03-30 11:35:09 -07001426 autotest_es.post(use_http=True,
1427 type_str='cros_version_label_mismatch',
1428 metadata={'hostname': self.hostname})
Dan Shi549fb822015-03-24 18:01:11 -07001429 raise error.AutoservError('The host has wrong cros-version label.')
1430
1431
Laurence Goodby778c9a42017-05-24 19:24:07 -07001432 def cleanup_services(self):
1433 """Reinitializes the device for cleanup.
1434
1435 Subclasses may override this to customize the cleanup method.
1436
1437 To indicate failure of the reset, the implementation may raise
1438 any of:
1439 error.AutoservRunError
1440 error.AutotestRunError
1441 FactoryImageCheckerException
1442
1443 @raises error.AutoservRunError
1444 @raises error.AutotestRunError
1445 @raises error.FactoryImageCheckerException
1446 """
1447 self._restart_ui()
1448
1449
beepsc87ff602013-07-31 21:53:00 -07001450 def cleanup(self):
MK Ryu35d661e2014-09-25 17:44:10 -07001451 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001452 try:
Laurence Goodby778c9a42017-05-24 19:24:07 -07001453 self.cleanup_services()
beepsc87ff602013-07-31 21:53:00 -07001454 except (error.AutotestRunError, error.AutoservRunError,
1455 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001456 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001457 # Since restarting the UI fails fall back to normal Autotest
1458 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001459 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001460 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001461 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001462 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001463 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001464
1465
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001466 def reboot(self, **dargs):
1467 """
1468 This function reboots the site host. The more generic
1469 RemoteHost.reboot() performs sync and sleeps for 5
1470 seconds. This is not necessary for Chrome OS devices as the
1471 sync should be finished in a short time during the reboot
1472 command.
1473 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001474 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001475 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001476 dargs['reboot_cmd'] = ('sleep 1; '
1477 'reboot & sleep %d; '
1478 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001479 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001480 if 'fastsync' not in dargs:
1481 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001482
Charlie Mooneya8e6dab2014-05-29 14:37:55 -07001483 # For purposes of logging reboot times:
1484 # Get the board name i.e. 'daisy_spring'
Michael Liangca4f5a62014-07-10 15:45:13 -07001485 board_fullname = self.get_board()
1486
1487 # Strip the prefix and add it to dargs.
1488 dargs['board'] = board_fullname[board_fullname.find(':')+1:]
Vincent Palatindf2372c2016-10-07 17:03:00 +02001489 # Record who called us
1490 orig = sys._getframe(1).f_code
Vincent Palatin80780b22016-07-27 16:02:37 +02001491 metric_fields = {'board' : dargs['board'],
Vincent Palatindf2372c2016-10-07 17:03:00 +02001492 'dut_host_name' : self.hostname,
1493 'success' : True}
1494 metric_debug_fields = {'board' : dargs['board'],
1495 'caller' : "%s:%s" % (orig.co_filename, orig.co_name),
1496 'success' : True,
1497 'error' : ''}
1498
Vincent Palatin80780b22016-07-27 16:02:37 +02001499 t0 = time.time()
1500 try:
1501 super(CrosHost, self).reboot(**dargs)
1502 except Exception as e:
1503 metric_fields['success'] = False
Vincent Palatindf2372c2016-10-07 17:03:00 +02001504 metric_debug_fields['success'] = False
1505 metric_debug_fields['error'] = type(e).__name__
Vincent Palatin80780b22016-07-27 16:02:37 +02001506 raise
1507 finally:
1508 duration = int(time.time() - t0)
Dan Shi5e2efb72017-02-07 11:40:23 -08001509 metrics.Counter(
1510 'chromeos/autotest/autoserv/reboot_count').increment(
1511 fields=metric_fields)
1512 metrics.Counter(
1513 'chromeos/autotest/autoserv/reboot_debug').increment(
1514 fields=metric_debug_fields)
1515 metrics.SecondsDistribution(
1516 'chromeos/autotest/autoserv/reboot_duration').add(
1517 duration, fields=metric_fields)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001518
1519
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001520 def suspend(self, **dargs):
1521 """
1522 This function suspends the site host.
1523 """
1524 suspend_time = dargs.get('suspend_time', 60)
1525 dargs['timeout'] = suspend_time
1526 if 'suspend_cmd' not in dargs:
J. Richard Barnette9af19632015-09-25 12:18:03 -07001527 dargs['suspend_cmd'] = ' && '.join([
1528 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001529 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
J. Richard Barnette9af19632015-09-25 12:18:03 -07001530 'powerd_dbus_suspend --delay=0'])
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001531 super(CrosHost, self).suspend(**dargs)
1532
1533
Simran Basiec564392014-08-25 16:48:09 -07001534 def upstart_status(self, service_name):
1535 """Check the status of an upstart init script.
1536
1537 @param service_name: Service to look up.
1538
1539 @returns True if the service is running, False otherwise.
1540 """
1541 return self.run('status %s | grep start/running' %
1542 service_name).stdout.strip() != ''
1543
1544
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001545 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001546 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001547
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001548 Tests for the following conditions:
1549 1. All conditions tested by the parent version of this
1550 function.
1551 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001552 3. Sufficient space in /mnt/stateful_partition/encrypted.
1553 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001554
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001555 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001556 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001557 default_kilo_inodes_required = CONFIG.get_config_value(
1558 'SERVER', 'kilo_inodes_required', type=int, default=100)
1559 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1560 kilo_inodes_required = CONFIG.get_config_value(
1561 'SERVER', 'kilo_inodes_required_%s' % board,
1562 type=int, default=default_kilo_inodes_required)
1563 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001564 self.check_diskspace(
1565 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001566 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001567 'SERVER', 'gb_diskspace_required', type=float,
1568 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001569 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1570 # Not all targets build with encrypted stateful support.
1571 if self.path_exists(encrypted_stateful_path):
1572 self.check_diskspace(
1573 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001574 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001575 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1576 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001577
Simran Basiec564392014-08-25 16:48:09 -07001578 if not self.upstart_status('system-services'):
Prashanth B5d0a0512014-04-25 12:26:08 -07001579 raise error.AutoservError('Chrome failed to reach login. '
1580 'System services not running.')
1581
beepsc87ff602013-07-31 21:53:00 -07001582 # Factory images don't run update engine,
1583 # goofy controls dbus on these DUTs.
1584 if not self._is_factory_image():
1585 self.run('update_engine_client --status')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001586
Dan Shi549fb822015-03-24 18:01:11 -07001587 self.verify_cros_version_label()
1588
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001589
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001590 def verify(self):
1591 self._repair_strategy.verify(self)
1592
1593
Fang Deng96667ca2013-08-01 17:46:18 -07001594 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
1595 connect_timeout=None, alive_interval=None):
1596 """Override default make_ssh_command to use options tuned for Chrome OS.
1597
1598 Tuning changes:
1599 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1600 connection failure. Consistency with remote_access.sh.
1601
Samuel Tan2ce155b2015-06-23 18:24:38 -07001602 - ServerAliveInterval=900; which causes SSH to ping connection every
1603 900 seconds. In conjunction with ServerAliveCountMax ensures
1604 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001605 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001606 the test completed successfully. Later increased from 180 seconds to
1607 900 seconds to account for tests where the DUT is suspended for
1608 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001609
1610 - ServerAliveCountMax=3; consistency with remote_access.sh.
1611
1612 - ConnectAttempts=4; reduce flakiness in connection errors;
1613 consistency with remote_access.sh.
1614
1615 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1616 Host keys change with every new installation, don't waste
1617 memory/space saving them.
1618
1619 - SSH protocol forced to 2; needed for ServerAliveInterval.
1620
1621 @param user User name to use for the ssh connection.
1622 @param port Port on the target host to use for ssh connection.
1623 @param opts Additional options to the ssh command.
1624 @param hosts_file Ignored.
1625 @param connect_timeout Ignored.
1626 @param alive_interval Ignored.
1627 """
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001628 base_command = ('/usr/bin/ssh -a -x %s %s %s'
1629 ' -o StrictHostKeyChecking=no'
Fang Deng96667ca2013-08-01 17:46:18 -07001630 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Samuel Tan2ce155b2015-06-23 18:24:38 -07001631 ' -o ConnectTimeout=30 -o ServerAliveInterval=900'
Fang Deng96667ca2013-08-01 17:46:18 -07001632 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
1633 ' -o Protocol=2 -l %s -p %d')
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001634 return base_command % (self._ssh_verbosity_flag, self._ssh_options,
1635 opts, user, port)
Jason Abeleb6f924f2013-11-13 16:01:54 -08001636 def syslog(self, message, tag='autotest'):
1637 """Logs a message to syslog on host.
1638
1639 @param message String message to log into syslog
1640 @param tag String tag prefix for syslog
1641
1642 """
1643 self.run('logger -t "%s" "%s"' % (tag, message))
1644
1645
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001646 def _ping_check_status(self, status):
1647 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001648
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001649 @param status Check the ping status against this value.
1650 @return True iff `status` and the result of ping are the same
1651 (i.e. both True or both False).
1652
1653 """
1654 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1655 return not (status ^ (ping_val == 0))
1656
1657 def _ping_wait_for_status(self, status, timeout):
1658 """Wait for the host to have a given status (UP or DOWN).
1659
1660 Status is checked by polling. Polling will not last longer
1661 than the number of seconds in `timeout`. The polling
1662 interval will be long enough that only approximately
1663 _PING_WAIT_COUNT polling cycles will be executed, subject
1664 to a maximum interval of about one minute.
1665
1666 @param status Waiting will stop immediately if `ping` of the
1667 host returns this status.
1668 @param timeout Poll for at most this many seconds.
1669 @return True iff the host status from `ping` matched the
1670 requested status at the time of return.
1671
1672 """
1673 # _ping_check_status() takes about 1 second, hence the
1674 # "- 1" in the formula below.
Nathan Ciobanu38480a32016-10-25 15:26:45 -07001675 # FIXME: if the ping command errors then _ping_check_status()
1676 # returns instantly. If timeout is also smaller than twice
1677 # _PING_WAIT_COUNT then the while loop below forks many
1678 # thousands of ping commands (see /tmp/test_that_results_XXXXX/
1679 # /results-1-logging_YYY.ZZZ/debug/autoserv.DEBUG) and hogs one
1680 # CPU core for 60 seconds.
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001681 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1682 end_time = time.time() + timeout
1683 while time.time() <= end_time:
1684 if self._ping_check_status(status):
1685 return True
1686 if poll_interval > 0:
1687 time.sleep(poll_interval)
1688
1689 # The last thing we did was sleep(poll_interval), so it may
1690 # have been too long since the last `ping`. Check one more
1691 # time, just to be sure.
1692 return self._ping_check_status(status)
1693
1694 def ping_wait_up(self, timeout):
1695 """Wait for the host to respond to `ping`.
1696
1697 N.B. This method is not a reliable substitute for
1698 `wait_up()`, because a host that responds to ping will not
1699 necessarily respond to ssh. This method should only be used
1700 if the target DUT can be considered functional even if it
1701 can't be reached via ssh.
1702
1703 @param timeout Minimum time to allow before declaring the
1704 host to be non-responsive.
1705 @return True iff the host answered to ping before the timeout.
1706
1707 """
1708 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001709
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001710 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001711 """Wait until the host no longer responds to `ping`.
1712
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001713 This function can be used as a slightly faster version of
1714 `wait_down()`, by avoiding potentially long ssh timeouts.
1715
1716 @param timeout Minimum time to allow for the host to become
1717 non-responsive.
1718 @return True iff the host quit answering ping before the
1719 timeout.
1720
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001721 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001722 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001723
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001724 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001725 """Wait for the client to enter low-power sleep mode.
1726
1727 The test for "is asleep" can't distinguish a system that is
1728 powered off; to confirm that the unit was asleep, it is
1729 necessary to force resume, and then call
1730 `test_wait_for_resume()`.
1731
1732 This function is expected to be called from a test as part
1733 of a sequence like the following:
1734
1735 ~~~~~~~~
1736 boot_id = host.get_boot_id()
1737 # trigger sleep on the host
1738 host.test_wait_for_sleep()
1739 # trigger resume on the host
1740 host.test_wait_for_resume(boot_id)
1741 ~~~~~~~~
1742
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001743 @param sleep_timeout time limit in seconds to allow the host sleep.
1744
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001745 @exception TestFail The host did not go to sleep within
1746 the allowed time.
1747 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001748 if sleep_timeout is None:
1749 sleep_timeout = self.SLEEP_TIMEOUT
1750
1751 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001752 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001753 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001754
1755
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001756 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001757 """Wait for the client to resume from low-power sleep mode.
1758
1759 The `old_boot_id` parameter should be the value from
1760 `get_boot_id()` obtained prior to entering sleep mode. A
1761 `TestFail` exception is raised if the boot id changes.
1762
1763 See @ref test_wait_for_sleep for more on this function's
1764 usage.
1765
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001766 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001767 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001768 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001769
1770 @exception TestFail The host did not respond within the
1771 allowed time.
1772 @exception TestFail The host responded, but the boot id test
1773 indicated a reboot rather than a sleep
1774 cycle.
1775 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001776 if resume_timeout is None:
1777 resume_timeout = self.RESUME_TIMEOUT
1778
1779 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001780 raise error.TestFail(
1781 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001782 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001783 else:
1784 new_boot_id = self.get_boot_id()
1785 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001786 logging.error('client rebooted (old boot %s, new boot %s)',
1787 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001788 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001789 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001790
1791
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001792 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001793 """Wait for the client to shut down.
1794
1795 The test for "has shut down" can't distinguish a system that
1796 is merely asleep; to confirm that the unit was down, it is
1797 necessary to force boot, and then call test_wait_for_boot().
1798
1799 This function is expected to be called from a test as part
1800 of a sequence like the following:
1801
1802 ~~~~~~~~
1803 boot_id = host.get_boot_id()
1804 # trigger shutdown on the host
1805 host.test_wait_for_shutdown()
1806 # trigger boot on the host
1807 host.test_wait_for_boot(boot_id)
1808 ~~~~~~~~
1809
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001810 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001811 @exception TestFail The host did not shut down within the
1812 allowed time.
1813 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001814 if shutdown_timeout is None:
1815 shutdown_timeout = self.SHUTDOWN_TIMEOUT
1816
1817 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001818 raise error.TestFail(
1819 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001820 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001821
1822
1823 def test_wait_for_boot(self, old_boot_id=None):
1824 """Wait for the client to boot from cold power.
1825
1826 The `old_boot_id` parameter should be the value from
1827 `get_boot_id()` obtained prior to shutting down. A
1828 `TestFail` exception is raised if the boot id does not
1829 change. The boot id test is omitted if `old_boot_id` is not
1830 specified.
1831
1832 See @ref test_wait_for_shutdown for more on this function's
1833 usage.
1834
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001835 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001836 shut down.
1837
1838 @exception TestFail The host did not respond within the
1839 allowed time.
1840 @exception TestFail The host responded, but the boot id test
1841 indicated that there was no reboot.
1842 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001843 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001844 raise error.TestFail(
1845 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001846 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001847 elif old_boot_id:
1848 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001849 logging.error('client not rebooted (boot %s)',
1850 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001851 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001852 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07001853
1854
1855 @staticmethod
1856 def check_for_rpm_support(hostname):
1857 """For a given hostname, return whether or not it is powered by an RPM.
1858
Simran Basi1df55112013-09-06 11:25:09 -07001859 @param hostname: hostname to check for rpm support.
1860
Simran Basid5e5e272012-09-24 15:23:59 -07001861 @return None if this host does not follows the defined naming format
1862 for RPM powered DUT's in the lab. If it does follow the format,
1863 it returns a regular expression MatchObject instead.
1864 """
Fang Dengbaff9082015-01-06 13:46:15 -08001865 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001866
1867
1868 def has_power(self):
1869 """For this host, return whether or not it is powered by an RPM.
1870
1871 @return True if this host is in the CROS lab and follows the defined
1872 naming format.
1873 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001874 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001875
1876
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001877 def _set_power(self, state, power_method):
1878 """Sets the power to the host via RPM, Servo or manual.
1879
1880 @param state Specifies which power state to set to DUT
1881 @param power_method Specifies which method of power control to
1882 use. By default "RPM" will be used. Valid values
1883 are the strings "RPM", "manual", "servoj10".
1884
1885 """
1886 ACCEPTABLE_STATES = ['ON', 'OFF']
1887
1888 if state.upper() not in ACCEPTABLE_STATES:
1889 raise error.TestError('State must be one of: %s.'
1890 % (ACCEPTABLE_STATES,))
1891
1892 if power_method == self.POWER_CONTROL_SERVO:
1893 logging.info('Setting servo port J10 to %s', state)
1894 self.servo.set('prtctl3_pwren', state.lower())
1895 time.sleep(self._USB_POWER_TIMEOUT)
1896 elif power_method == self.POWER_CONTROL_MANUAL:
1897 logging.info('You have %d seconds to set the AC power to %s.',
1898 self._POWER_CYCLE_TIMEOUT, state)
1899 time.sleep(self._POWER_CYCLE_TIMEOUT)
1900 else:
1901 if not self.has_power():
1902 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07001903 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1904 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
1905 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07001906 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07001907
1908
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001909 def power_off(self, power_method=POWER_CONTROL_RPM):
1910 """Turn off power to this host via RPM, Servo or manual.
1911
1912 @param power_method Specifies which method of power control to
1913 use. By default "RPM" will be used. Valid values
1914 are the strings "RPM", "manual", "servoj10".
1915
1916 """
1917 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001918
1919
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001920 def power_on(self, power_method=POWER_CONTROL_RPM):
1921 """Turn on power to this host via RPM, Servo or manual.
1922
1923 @param power_method Specifies which method of power control to
1924 use. By default "RPM" will be used. Valid values
1925 are the strings "RPM", "manual", "servoj10".
1926
1927 """
1928 self._set_power('ON', power_method)
1929
1930
1931 def power_cycle(self, power_method=POWER_CONTROL_RPM):
1932 """Cycle power to this host by turning it OFF, then ON.
1933
1934 @param power_method Specifies which method of power control to
1935 use. By default "RPM" will be used. Valid values
1936 are the strings "RPM", "manual", "servoj10".
1937
1938 """
1939 if power_method in (self.POWER_CONTROL_SERVO,
1940 self.POWER_CONTROL_MANUAL):
1941 self.power_off(power_method=power_method)
1942 time.sleep(self._POWER_CYCLE_TIMEOUT)
1943 self.power_on(power_method=power_method)
1944 else:
1945 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001946
1947
1948 def get_platform(self):
1949 """Determine the correct platform label for this host.
1950
1951 @returns a string representing this host's platform.
1952 """
1953 crossystem = utils.Crossystem(self)
1954 crossystem.init()
1955 # Extract fwid value and use the leading part as the platform id.
1956 # fwid generally follow the format of {platform}.{firmware version}
1957 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1958 platform = crossystem.fwid().split('.')[0].lower()
1959 # Newer platforms start with 'Google_' while the older ones do not.
1960 return platform.replace('google_', '')
1961
1962
Hung-ying Tyanb1328032014-04-01 14:18:54 +08001963 def get_architecture(self):
1964 """Determine the correct architecture label for this host.
1965
1966 @returns a string representing this host's architecture.
1967 """
1968 crossystem = utils.Crossystem(self)
1969 crossystem.init()
1970 return crossystem.arch()
1971
1972
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001973 def get_chrome_version(self):
1974 """Gets the Chrome version number and milestone as strings.
1975
1976 Invokes "chrome --version" to get the version number and milestone.
1977
1978 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
1979 current Chrome version number as a string (in the form "W.X.Y.Z")
1980 and "milestone" is the first component of the version number
1981 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
1982 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
1983 of "chrome --version" and the milestone will be the empty string.
1984
1985 """
MK Ryu35d661e2014-09-25 17:44:10 -07001986 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001987 return utils.parse_chrome_version(version_string)
1988
J. Richard Barnetted2af5852016-02-05 15:03:10 -08001989
Kevin Chenga2619dc2016-03-28 11:42:08 -07001990 # TODO(kevcheng): change this to just return the board without the
1991 # 'board:' prefix and fix up all the callers. Also look into removing the
1992 # need for this method.
Simran Basic6f1f7a2012-10-16 10:47:46 -07001993 def get_board(self):
1994 """Determine the correct board label for this host.
1995
1996 @returns a string representing this host's board.
1997 """
1998 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1999 run_method=self.run)
J. Richard Barnetted2af5852016-02-05 15:03:10 -08002000 return (ds_constants.BOARD_PREFIX +
2001 release_info['CHROMEOS_RELEASE_BOARD'])
Simran Basic6f1f7a2012-10-16 10:47:46 -07002002
2003
Kevin Chenga328da62016-03-31 10:49:04 -07002004
2005 def has_lightsensor(self):
2006 """Determine the correct board label for this host.
2007
2008 @returns the string 'lightsensor' if this host has a lightsensor or
2009 None if it does not.
2010 """
2011 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
2012 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
2013 try:
2014 # Run the search cmd following the symlinks. Stderr_tee is set to
2015 # None as there can be a symlink loop, but the command will still
2016 # execute correctly with a few messages printed to stderr.
2017 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
2018 return 'lightsensor'
2019 except error.AutoservRunError:
2020 # egrep exited with a return code of 1 meaning none of the possible
2021 # lightsensor files existed.
2022 return None
2023
2024
2025 def has_bluetooth(self):
2026 """Determine the correct board label for this host.
2027
2028 @returns the string 'bluetooth' if this host has bluetooth or
2029 None if it does not.
2030 """
2031 try:
2032 self.run('test -d /sys/class/bluetooth/hci0')
2033 # test exited with a return code of 0.
2034 return 'bluetooth'
2035 except error.AutoservRunError:
2036 # test exited with a return code 1 meaning the directory did not
2037 # exist.
2038 return None
2039
2040
Kevin Chenga328da62016-03-31 10:49:04 -07002041 def get_accels(self):
2042 """
2043 Determine the type of accelerometers on this host.
2044
2045 @returns a string representing this host's accelerometer type.
2046 At present, it only returns "accel:cros-ec", for accelerometers
2047 attached to a Chrome OS EC, or none, if no accelerometers.
2048 """
2049 # Check to make sure we have ectool
2050 rv = self.run('which ectool', ignore_status=True)
2051 if rv.exit_status:
2052 logging.info("No ectool cmd found, assuming no EC accelerometers")
2053 return None
2054
2055 # Check that the EC supports the motionsense command
2056 rv = self.run('ectool motionsense', ignore_status=True)
2057 if rv.exit_status:
2058 logging.info("EC does not support motionsense command "
2059 "assuming no EC accelerometers")
2060 return None
2061
2062 # Check that EC motion sensors are active
2063 active = self.run('ectool motionsense active').stdout.split('\n')
2064 if active[0] == "0":
2065 logging.info("Motion sense inactive, assuming no EC accelerometers")
2066 return None
2067
2068 logging.info("EC accelerometers found")
2069 return 'accel:cros-ec'
2070
2071
2072 def has_chameleon(self):
2073 """Determine if a Chameleon connected to this host.
2074
2075 @returns a list containing two strings ('chameleon' and
2076 'chameleon:' + label, e.g. 'chameleon:hdmi') if this host
2077 has a Chameleon or None if it has not.
2078 """
2079 if self._chameleon_host:
2080 return ['chameleon', 'chameleon:' + self.chameleon.get_label()]
2081 else:
2082 return None
2083
2084
2085 def has_loopback_dongle(self):
2086 """Determine if an audio loopback dongle is plugged to this host.
2087
2088 @returns 'audio_loopback_dongle' when there is an audio loopback dongle
2089 plugged to this host.
2090 None when there is no audio loopback dongle
2091 plugged to this host.
2092 """
2093 nodes_info = self.run(command=cras_utils.get_cras_nodes_cmd(),
2094 ignore_status=True).stdout
2095 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
2096 cras_utils.node_type_is_plugged('MIC', nodes_info)):
2097 return 'audio_loopback_dongle'
2098 else:
2099 return None
2100
2101
2102 def get_power_supply(self):
2103 """
2104 Determine what type of power supply the host has
2105
2106 @returns a string representing this host's power supply.
2107 'power:battery' when the device has a battery intended for
2108 extended use
2109 'power:AC_primary' when the device has a battery not intended
2110 for extended use (for moving the machine, etc)
2111 'power:AC_only' when the device has no battery at all.
2112 """
2113 psu = self.run(command='mosys psu type', ignore_status=True)
2114 if psu.exit_status:
2115 # The psu command for mosys is not included for all platforms. The
2116 # assumption is that the device will have a battery if the command
2117 # is not found.
2118 return 'power:battery'
2119
2120 psu_str = psu.stdout.strip()
2121 if psu_str == 'unknown':
2122 return None
2123
2124 return 'power:%s' % psu_str
2125
2126
2127 def get_storage(self):
2128 """
2129 Determine the type of boot device for this host.
2130
2131 Determine if the internal device is SCSI or dw_mmc device.
2132 Then check that it is SSD or HDD or eMMC or something else.
2133
2134 @returns a string representing this host's internal device type.
2135 'storage:ssd' when internal device is solid state drive
2136 'storage:hdd' when internal device is hard disk drive
2137 'storage:mmc' when internal device is mmc drive
2138 None When internal device is something else or
2139 when we are unable to determine the type
2140 """
2141 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
2142 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2143 '. /usr/share/misc/chromeos-common.sh;',
2144 'load_base_vars;',
2145 'get_fixed_dst_drive'])
2146 rootdev = self.run(command=rootdev_cmd, ignore_status=True)
2147 if rootdev.exit_status:
2148 logging.info("Fail to run %s", rootdev_cmd)
2149 return None
2150 rootdev_str = rootdev.stdout.strip()
2151
2152 if not rootdev_str:
2153 return None
2154
2155 rootdev_base = os.path.basename(rootdev_str)
2156
2157 mmc_pattern = '/dev/mmcblk[0-9]'
2158 if re.match(mmc_pattern, rootdev_str):
2159 # Use type to determine if the internal device is eMMC or somthing
2160 # else. We can assume that MMC is always an internal device.
2161 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
2162 type = self.run(command=type_cmd, ignore_status=True)
2163 if type.exit_status:
2164 logging.info("Fail to run %s", type_cmd)
2165 return None
2166 type_str = type.stdout.strip()
2167
2168 if type_str == 'MMC':
2169 return 'storage:mmc'
2170
2171 scsi_pattern = '/dev/sd[a-z]+'
2172 if re.match(scsi_pattern, rootdev.stdout):
2173 # Read symlink for /sys/block/sd* to determine if the internal
2174 # device is connected via ata or usb.
2175 link_cmd = 'readlink /sys/block/%s' % rootdev_base
2176 link = self.run(command=link_cmd, ignore_status=True)
2177 if link.exit_status:
2178 logging.info("Fail to run %s", link_cmd)
2179 return None
2180 link_str = link.stdout.strip()
2181 if 'usb' in link_str:
2182 return None
2183
2184 # Read rotation to determine if the internal device is ssd or hdd.
2185 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
2186 % rootdev_base)
2187 rotate = self.run(command=rotate_cmd, ignore_status=True)
2188 if rotate.exit_status:
2189 logging.info("Fail to run %s", rotate_cmd)
2190 return None
2191 rotate_str = rotate.stdout.strip()
2192
2193 rotate_dict = {'0':'storage:ssd', '1':'storage:hdd'}
2194 return rotate_dict.get(rotate_str)
2195
2196 # All other internal device / error case will always fall here
2197 return None
2198
2199
2200 def get_servo(self):
2201 """Determine if the host has a servo attached.
2202
2203 If the host has a working servo attached, it should have a servo label.
2204
2205 @return: string 'servo' if the host has servo attached. Otherwise,
2206 returns None.
2207 """
2208 return 'servo' if self._servo_host else None
2209
2210
2211 def get_video_labels(self):
2212 """Run /usr/local/bin/avtest_label_detect to get a list of video labels.
2213
2214 Sample output of avtest_label_detect:
2215 Detected label: hw_video_acc_vp8
2216 Detected label: webcam
2217
2218 @return: A list of labels detected by tool avtest_label_detect.
2219 """
2220 try:
2221 result = self.run('/usr/local/bin/avtest_label_detect').stdout
2222 return re.findall('^Detected label: (\w+)$', result, re.M)
2223 except error.AutoservRunError:
2224 # The tool is not installed.
2225 return []
2226
2227
2228 def is_video_glitch_detection_supported(self):
2229 """ Determine if a board under test is supported for video glitch
2230 detection tests.
2231
2232 @return: 'video_glitch_detection' if board is supported, None otherwise.
2233 """
2234 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2235
2236 if board in video_test_constants.SUPPORTED_BOARDS:
2237 return 'video_glitch_detection'
2238
2239 return None
2240
2241
2242 def get_touch(self):
2243 """
2244 Determine whether board under test has a touchpad or touchscreen.
2245
2246 @return: A list of some combination of 'touchscreen' and 'touchpad',
2247 depending on what is present on the device.
2248
2249 """
2250 labels = []
2251 looking_for = ['touchpad', 'touchscreen']
2252 player = input_playback.InputPlayback()
2253 input_events = self.run('ls /dev/input/event*').stdout.strip().split()
2254 filename = '/tmp/touch_labels'
2255 for event in input_events:
2256 self.run('evtest %s > %s' % (event, filename), timeout=1,
2257 ignore_timeout=True)
2258 properties = self.run('cat %s' % filename).stdout
2259 input_type = player._determine_input_type(properties)
2260 if input_type in looking_for:
2261 labels.append(input_type)
2262 looking_for.remove(input_type)
2263 if len(looking_for) == 0:
2264 break
2265 self.run('rm %s' % filename)
2266
2267 return labels
2268
2269
2270 def has_internal_display(self):
2271 """Determine if the device under test is equipped with an internal
2272 display.
2273
2274 @return: 'internal_display' if one is present; None otherwise.
2275 """
2276 from autotest_lib.client.cros.graphics import graphics_utils
2277 from autotest_lib.client.common_lib import utils as common_utils
2278
2279 def __system_output(cmd):
2280 return self.run(cmd).stdout
2281
2282 def __read_file(remote_path):
2283 return self.run('cat %s' % remote_path).stdout
2284
2285 # Hijack the necessary client functions so that we can take advantage
2286 # of the client lib here.
2287 # FIXME: find a less hacky way than this
2288 original_system_output = utils.system_output
2289 original_read_file = common_utils.read_file
2290 utils.system_output = __system_output
2291 common_utils.read_file = __read_file
2292 try:
2293 return ('internal_display' if graphics_utils.has_internal_display()
2294 else None)
2295 finally:
2296 utils.system_output = original_system_output
2297 common_utils.read_file = original_read_file
2298
2299
2300 def has_lucid_sleep_support(self):
2301 """Determine if the device under test has support for lucid sleep.
2302
2303 @return 'lucidsleep' if this board supports lucid sleep; None otherwise
2304 """
2305 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2306 return 'lucidsleep' if board in LUCID_SLEEP_BOARDS else None
2307
2308
Dan Shi85276d42014-04-08 22:11:45 -07002309 def is_boot_from_usb(self):
2310 """Check if DUT is boot from USB.
2311
2312 @return: True if DUT is boot from usb.
2313 """
2314 device = self.run('rootdev -s -d').stdout.strip()
2315 removable = int(self.run('cat /sys/block/%s/removable' %
2316 os.path.basename(device)).stdout.strip())
2317 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002318
2319
2320 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002321 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002322
2323 @param key: meminfo requested
2324
2325 @return the memory value as a string
2326
2327 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002328 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2329 logging.debug('%s', meminfo)
2330 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002331
2332
Rohit Makasana98e696f2016-06-03 18:48:10 -07002333 def get_cpu_arch(self):
2334 """Returns CPU arch of the device.
2335
2336 @return CPU architecture of the DUT.
2337 """
Allen Li2c32d6b2017-02-03 15:28:10 -08002338 # Add CPUs by following logic in client/bin/utils.py.
Rohit Makasana98e696f2016-06-03 18:48:10 -07002339 if self.run("grep '^flags.*:.* lm .*' /proc/cpuinfo",
2340 ignore_status=True).stdout:
2341 return 'x86_64'
2342 if self.run("grep -Ei 'ARM|CPU implementer' /proc/cpuinfo",
2343 ignore_status=True).stdout:
2344 return 'arm'
2345 return 'i386'
2346
2347
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002348 def get_board_type(self):
2349 """
2350 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002351 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2352
2353 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002354 """
Danny Chan471a8d12015-08-18 14:57:41 -07002355 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2356 ignore_status=True).stdout
2357 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002358 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002359 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002360
2361
2362 def get_os_type(self):
2363 return 'cros'
Simran Basia5522a32015-10-06 11:01:24 -07002364
2365
2366 def enable_adb_testing(self):
2367 """Mark this host as an adb tester."""
Dan Shia2872172015-10-31 01:16:51 -07002368 self.run('touch %s' % constants.ANDROID_TESTER_FILEFLAG)
Kevin Chenga2619dc2016-03-28 11:42:08 -07002369
2370
2371 def get_labels(self):
Kevin Chengf8660142016-08-12 10:17:41 -07002372 """Return the detected labels on the host."""
Kevin Chenga2619dc2016-03-28 11:42:08 -07002373 return self.labels.get_labels(self)
Kevin Chengf8660142016-08-12 10:17:41 -07002374
2375
2376 def update_labels(self):
2377 """Update the labels on the host."""
2378 self.labels.update_labels(self)