blob: f492785c1b50d5c0946992f3b734dff4a10f9c09 [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
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -070020from autotest_lib.client.common_lib.cros import retry
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
Dan Shid07ee2e2015-09-24 14:49:25 -070058
beepsc87ff602013-07-31 21:53:00 -070059class FactoryImageCheckerException(error.AutoservError):
60 """Exception raised when an image is a factory image."""
61 pass
62
63
Fang Deng0ca40e22013-08-27 17:47:44 -070064class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070065 """Chromium OS specific subclass of Host."""
66
Simran Basi5ace6f22016-01-06 17:30:44 -080067 VERSION_PREFIX = provision.CROS_VERSION_PREFIX
68
J. Richard Barnette45e93de2012-04-11 17:24:15 -070069 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -050070 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
xixuanec801e32016-08-25 10:20:22 -070071 support_devserver_provision = ENABLE_DEVSERVER_TRIGGER_AUTO_UPDATE
J. Richard Barnette45e93de2012-04-11 17:24:15 -070072
Richard Barnette03a0c132012-11-05 12:40:35 -080073 # Timeout values (in seconds) associated with various Chrome OS
74 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070075 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080076 # In general, a good rule of thumb is that the timeout can be up
77 # to twice the typical measured value on the slowest platform.
78 # The times here have not necessarily been empirically tested to
79 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070080 #
81 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080082 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
83 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080084 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070085 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080086 # other things, this must account for the 30 second dev-mode
J. Richard Barnette417cc792015-10-01 09:56:36 -070087 # screen delay, time to start the network on the DUT, and the
88 # ssh timeout of 120 seconds.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070089 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080090 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080091 # network.
beepsf079cfb2013-09-18 17:49:51 -070092 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080093 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
94 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070095
96 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080097 RESUME_TIMEOUT = 10
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +080098 SHUTDOWN_TIMEOUT = 10
J. Richard Barnette417cc792015-10-01 09:56:36 -070099 BOOT_TIMEOUT = 150
J. Richard Barnette5bab5f52015-08-03 13:14:38 -0700100 USB_BOOT_TIMEOUT = 300
J. Richard Barnette7817b052014-08-28 09:47:29 -0700101 INSTALL_TIMEOUT = 480
Dan Shi2c88eed2013-11-12 10:18:38 -0800102 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -0700103
Dan Shica503482015-03-30 17:23:25 -0700104 # Minimum OS version that supports server side packaging. Older builds may
105 # not have server side package built or with Autotest code change to support
106 # server-side packaging.
Dan Shib8540a52015-07-16 14:18:23 -0700107 MIN_VERSION_SUPPORT_SSP = CONFIG.get_config_value(
Dan Shiced09e42015-04-17 16:09:34 -0700108 'AUTOSERV', 'min_version_support_ssp', type=int)
Dan Shica503482015-03-30 17:23:25 -0700109
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800110 # REBOOT_TIMEOUT: How long to wait for a reboot.
111 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700112 # We have a long timeout to ensure we don't flakily fail due to other
113 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700114 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
115 # return from reboot' bug is solved.
116 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700117
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800118 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
119 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
120 _USB_POWER_TIMEOUT = 5
121 _POWER_CYCLE_TIMEOUT = 10
122
Richard Barnette82c35912012-11-20 10:09:10 -0800123 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Fang Dengdeba14f2014-11-14 11:54:09 -0800124 _RPM_HOSTNAME_REGEX = ('chromeos(\d+)(-row(\d+))?-rack(\d+[a-z]*)'
125 '-host(\d+)')
Kevin Chenga328da62016-03-31 10:49:04 -0700126 _LIGHTSENSOR_FILES = [ "in_illuminance0_input",
127 "in_illuminance_input",
128 "in_illuminance0_raw",
129 "in_illuminance_raw",
130 "illuminance0_input"]
131 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700132
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800133 # Constants used in ping_wait_up() and ping_wait_down().
134 #
135 # _PING_WAIT_COUNT is the approximate number of polling
136 # cycles to use when waiting for a host state change.
137 #
138 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
139 # for arguments to the internal _ping_wait_for_status()
140 # method.
141 _PING_WAIT_COUNT = 40
142 _PING_STATUS_DOWN = False
143 _PING_STATUS_UP = True
144
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800145 # Allowed values for the power_method argument.
146
147 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
148 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
149 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
150 POWER_CONTROL_RPM = 'RPM'
151 POWER_CONTROL_SERVO = 'servoj10'
152 POWER_CONTROL_MANUAL = 'manual'
153
154 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
155 POWER_CONTROL_SERVO,
156 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800157
Simran Basi5e6339a2013-03-21 11:34:32 -0700158 _RPM_OUTLET_CHANGED = 'outlet_changed'
159
Dan Shi9cb0eec2014-06-03 09:04:50 -0700160 # URL pattern to download firmware image.
Dan Shib8540a52015-07-16 14:18:23 -0700161 _FW_IMAGE_URL_PATTERN = CONFIG.get_config_value(
Dan Shi9cb0eec2014-06-03 09:04:50 -0700162 'CROS', 'firmware_url_pattern', type=str)
beeps687243d2013-07-18 15:29:27 -0700163
J. Richard Barnette91137f02016-03-10 16:52:26 -0800164
165 # A flag file to indicate provision failures. The file is created
166 # at the start of any AU procedure (see `machine_install()`). The
167 # file's location in stateful means that on successul update it will
168 # be removed. Thus, if this file exists, it indicates that we've
169 # tried and failed in a previous attempt to update.
170 PROVISION_FAILED = '/var/tmp/provision_failed'
MK Ryu35d661e2014-09-25 17:44:10 -0700171
MK Ryu35d661e2014-09-25 17:44:10 -0700172
J. Richard Barnette964fba02012-10-24 17:34:29 -0700173 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800174 def check_host(host, timeout=10):
175 """
176 Check if the given host is a chrome-os host.
177
178 @param host: An ssh host representing a device.
179 @param timeout: The timeout for the run command.
180
181 @return: True if the host device is chromeos.
182
beeps46dadc92013-11-07 14:07:10 -0800183 """
184 try:
Allen Liad719c12017-06-27 23:48:04 +0000185 result = host.run(
Simran Basi933c8af2015-04-29 14:05:07 -0700186 'grep -q CHROMEOS /etc/lsb-release && '
187 '! test -f /mnt/stateful_partition/.android_tester && '
188 '! grep -q moblab /etc/lsb-release',
189 ignore_status=True, timeout=timeout)
Laurence Goodby468de252017-06-08 17:22:53 -0700190 if result.exit_status == 0:
Allen Liad719c12017-06-27 23:48:04 +0000191 lsb_release_content = host.run(
Laurence Goodby468de252017-06-08 17:22:53 -0700192 'grep CHROMEOS_RELEASE_BOARD /etc/lsb-release',
193 timeout=timeout).stdout
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -0800194 return not (
195 lsbrelease_utils.is_jetstream(
196 lsb_release_content=lsb_release_content) or
197 lsbrelease_utils.is_gce_board(
198 lsb_release_content=lsb_release_content))
199
beeps46dadc92013-11-07 14:07:10 -0800200 except (error.AutoservRunError, error.AutoservSSHTimeout):
201 return False
Laurence Goodby468de252017-06-08 17:22:53 -0700202
203 return False
beeps46dadc92013-11-07 14:07:10 -0800204
205
206 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800207 def get_chameleon_arguments(args_dict):
208 """Extract chameleon options from `args_dict` and return the result.
209
210 Recommended usage:
211 ~~~~~~~~
212 args_dict = utils.args_to_dict(args)
213 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
214 host = hosts.create_host(machine, chameleon_args=chameleon_args)
215 ~~~~~~~~
216
217 @param args_dict Dictionary from which to extract the chameleon
218 arguments.
219 """
Allen Li083866b2016-08-18 10:07:10 -0700220 return {key: args_dict[key]
221 for key in ('chameleon_host', 'chameleon_port')
222 if key in args_dict}
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800223
224
225 @staticmethod
Scottfe06ed82015-11-05 17:15:01 -0800226 def get_plankton_arguments(args_dict):
227 """Extract chameleon options from `args_dict` and return the result.
228
229 Recommended usage:
230 ~~~~~~~~
231 args_dict = utils.args_to_dict(args)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700232 plankton_args = hosts.CrosHost.get_plankton_arguments(args_dict)
233 host = hosts.create_host(machine, plankton_args=plankton_args)
Scottfe06ed82015-11-05 17:15:01 -0800234 ~~~~~~~~
235
236 @param args_dict Dictionary from which to extract the plankton
237 arguments.
238 """
Allen Li083866b2016-08-18 10:07:10 -0700239 return {key: args_dict[key]
240 for key in ('plankton_host', 'plankton_port')
241 if key in args_dict}
Scottfe06ed82015-11-05 17:15:01 -0800242
243
244 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800245 def get_servo_arguments(args_dict):
246 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800247
248 Recommended usage:
249 ~~~~~~~~
250 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700251 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800252 host = hosts.create_host(machine, servo_args=servo_args)
253 ~~~~~~~~
254
255 @param args_dict Dictionary from which to extract the servo
256 arguments.
257 """
Richard Barnettee519dcd2016-08-15 17:37:17 -0700258 servo_attrs = (servo_host.SERVO_HOST_ATTR,
259 servo_host.SERVO_PORT_ATTR,
260 servo_host.SERVO_BOARD_ATTR)
Armando Miraglia2ac802e2017-08-15 14:54:47 +0200261 servo_args = {key: args_dict[key]
262 for key in servo_attrs
263 if key in args_dict}
264 return (
265 None
266 if servo_host.SERVO_HOST_ATTR in servo_args
267 and not servo_args[servo_host.SERVO_HOST_ATTR]
268 else servo_args)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700269
J. Richard Barnette964fba02012-10-24 17:34:29 -0700270
J. Richard Barnette91137f02016-03-10 16:52:26 -0800271 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
272 plankton_args=None, try_lab_servo=False,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700273 try_servo_repair=False,
J. Richard Barnette91137f02016-03-10 16:52:26 -0800274 ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700275 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800276 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700277
Fang Denge545abb2014-12-30 18:43:47 -0800278 This method will attempt to create the test-assistant object
279 (chameleon/servo) when it is needed by the test. Check
280 the docstring of chameleon_host.create_chameleon_host and
281 servo_host.create_servo_host for how this is determined.
Fang Deng5d518f42013-08-02 14:04:32 -0700282
Fang Denge545abb2014-12-30 18:43:47 -0800283 @param hostname: Hostname of the dut.
284 @param chameleon_args: A dictionary that contains args for creating
285 a ChameleonHost. See chameleon_host for details.
286 @param servo_args: A dictionary that contains args for creating
287 a ServoHost object. See servo_host for details.
Richard Barnette9a26ad62016-06-10 12:03:08 -0700288 @param try_lab_servo: When true, indicates that an attempt should
289 be made to create a ServoHost for a DUT in
290 the test lab, even if not required by
291 `servo_args`. See servo_host for details.
292 @param try_servo_repair: If a servo host is created, check it
293 with `repair()` rather than `verify()`.
Fang Denge545abb2014-12-30 18:43:47 -0800294 See servo_host for details.
295 @param ssh_verbosity_flag: String, to pass to the ssh command to control
296 verbosity.
297 @param ssh_options: String, other ssh options to pass to the ssh
298 command.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700299 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700300 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700301 *args, **dargs)
J. Richard Barnette91137f02016-03-10 16:52:26 -0800302 self._repair_strategy = cros_repair.create_cros_repair_strategy()
Kevin Chenga2619dc2016-03-28 11:42:08 -0700303 self.labels = base_label.LabelRetriever(cros_label.CROS_LABELS)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700304 # self.env is a dictionary of environment variable settings
305 # to be exported for commands run on the host.
306 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
307 # errors that might happen.
308 self.env['LIBC_FATAL_STDERR_'] = '1'
Fang Dengd1c2b732013-08-20 12:59:46 -0700309 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700310 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700311 # TODO(fdeng): We need to simplify the
312 # process of servo and servo_host initialization.
313 # crbug.com/298432
Moja Hsu57d93a12017-01-13 17:57:52 +0800314 self._servo_host = servo_host.create_servo_host(
Richard Barnetteea3e4602016-06-10 12:36:41 -0700315 dut=self, servo_args=servo_args,
Richard Barnette9a26ad62016-06-10 12:03:08 -0700316 try_lab_servo=try_lab_servo,
317 try_servo_repair=try_servo_repair)
Richard Barnettee519dcd2016-08-15 17:37:17 -0700318 if self._servo_host is not None:
319 self.servo = self._servo_host.get_servo()
320 else:
321 self.servo = None
322
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800323 # TODO(waihong): Do the simplication on Chameleon too.
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800324 self._chameleon_host = chameleon_host.create_chameleon_host(
325 dut=self.hostname, chameleon_args=chameleon_args)
Scottfe06ed82015-11-05 17:15:01 -0800326 # Add plankton host if plankton args were added on command line
327 self._plankton_host = plankton_host.create_plankton_host(plankton_args)
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800328
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800329 if self._chameleon_host:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800330 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800331 else:
Tom Wai-Hong Tam3d6790d2014-04-14 16:15:47 +0800332 self.chameleon = None
Fang Deng5d518f42013-08-02 14:04:32 -0700333
Scottfe06ed82015-11-05 17:15:01 -0800334 if self._plankton_host:
335 self.plankton_servo = self._plankton_host.get_servo()
336 logging.info('plankton_servo: %r', self.plankton_servo)
337 # Create the plankton object used to access the ec uart
Scott07a848f2016-01-12 15:04:52 -0800338 self.plankton = plankton.Plankton(self.plankton_servo,
339 self._plankton_host.get_servod_server_proxy())
Scottfe06ed82015-11-05 17:15:01 -0800340 else:
Scott07a848f2016-01-12 15:04:52 -0800341 self.plankton = None
Scottfe06ed82015-11-05 17:15:01 -0800342
Fang Deng5d518f42013-08-02 14:04:32 -0700343
Richard Barnette260cbd02016-10-06 12:23:28 -0700344 def _get_cros_repair_image_name(self):
Prathmesh Prabhu075fc922017-02-13 11:50:25 -0800345 info = self.host_info_store.get()
346 if info.board is None:
347 raise error.AutoservError('Cannot obtain repair image name. '
348 'No board label value found')
349
350 return afe_utils.get_stable_cros_image_name(info.board)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500351
352
Rohit Makasanadf0a3a32017-06-30 13:55:18 -0700353 def host_version_prefix(self, image):
354 """Return version label prefix.
355
356 In case the CrOS provisioning version is something other than the
357 standard CrOS version e.g. CrOS TH version, this function will
358 find the prefix from provision.py.
359
360 @param image: The image name to find its version prefix.
361 @returns: A prefix string for the image type.
362 """
363 return provision.get_version_label_prefix(image)
364
365
beepsdae65fd2013-07-26 16:24:41 -0700366 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700367 """
368 Make sure job_repo_url of this host is valid.
369
joychen03eaad92013-06-26 09:55:21 -0700370 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700371 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
372 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
373 download and extract it. If the devserver embedded in the url is
374 unresponsive, update the job_repo_url of the host after staging it on
375 another devserver.
376
377 @param job_repo_url: A url pointing to the devserver where the autotest
378 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700379 @param tag: The tag from the server job, in the format
380 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700381
382 @raises DevServerException: If we could not resolve a devserver.
383 @raises AutoservError: If we're unable to save the new job_repo_url as
384 a result of choosing a new devserver because the old one failed to
385 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700386 @raises urllib2.URLError: If the devserver embedded in job_repo_url
387 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700388 """
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800389 info = self.host_info_store.get()
390 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
beepscb6f1e22013-06-28 19:14:10 -0700391 if not job_repo_url:
392 logging.warning('No job repo url set on host %s', self.hostname)
393 return
394
395 logging.info('Verifying job repo url %s', job_repo_url)
396 devserver_url, image_name = tools.get_devserver_build_from_package_url(
397 job_repo_url)
398
beeps0c865032013-07-30 11:37:06 -0700399 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700400
401 logging.info('Staging autotest artifacts for %s on devserver %s',
402 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700403
404 start_time = time.time()
Simran Basi25e7a922014-10-31 11:56:10 -0700405 ds.stage_artifacts(image_name, ['autotest_packages'])
beeps687243d2013-07-18 15:29:27 -0700406 stage_time = time.time() - start_time
407
408 # Record how much of the verification time comes from a devserver
409 # restage. If we're doing things right we should not see multiple
410 # devservers for a given board/build/branch path.
411 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800412 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700413 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800414 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700415 pass
416 else:
beeps0c865032013-07-30 11:37:06 -0700417 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700418 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700419 stats_key = {
420 'board': board,
421 'build_type': build_type,
422 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700423 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700424 }
Dan Shi5e2efb72017-02-07 11:40:23 -0800425
426 monarch_fields = {
427 'board': board,
428 'build_type': build_type,
429 # TODO(akeshet): To be consistent with most other metrics,
430 # consider changing the following field to be named
431 # 'milestone'.
432 'branch': branch,
433 'dev_server': devserver,
434 }
435 metrics.Counter(
436 'chromeos/autotest/provision/verify_url'
437 ).increment(fields=monarch_fields)
438 metrics.SecondsDistribution(
439 'chromeos/autotest/provision/verify_url_duration'
440 ).add(stage_time, fields=monarch_fields)
441
442
Dan Shicf4d2032015-03-12 15:04:21 -0700443 def stage_server_side_package(self, image=None):
444 """Stage autotest server-side package on devserver.
445
446 @param image: Full path of an OS image to install or a build name.
447
448 @return: A url to the autotest server-side package.
Dan Shi14de7622016-08-22 11:09:06 -0700449
450 @raise: error.AutoservError if fail to locate the build to test with, or
451 fail to stage server-side package.
Dan Shicf4d2032015-03-12 15:04:21 -0700452 """
Dan Shid37736b2016-07-06 15:10:29 -0700453 # If enable_drone_in_restricted_subnet is False, do not set hostname
454 # in devserver.resolve call, so a devserver in non-restricted subnet
455 # is picked to stage autotest server package for drone to download.
456 hostname = self.hostname
457 if not server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
458 hostname = None
Dan Shicf4d2032015-03-12 15:04:21 -0700459 if image:
460 image_name = tools.get_build_from_image(image)
461 if not image_name:
462 raise error.AutoservError(
463 'Failed to parse build name from %s' % image)
Dan Shid37736b2016-07-06 15:10:29 -0700464 ds = dev_server.ImageServer.resolve(image_name, hostname)
Dan Shicf4d2032015-03-12 15:04:21 -0700465 else:
Prathmesh Prabhu9235e4c2017-03-28 13:16:06 -0700466 info = self.host_info_store.get()
Prathmesh Prabhu368abdf2017-02-14 11:23:47 -0800467 job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
Dan Shicf4d2032015-03-12 15:04:21 -0700468 if job_repo_url:
469 devserver_url, image_name = (
470 tools.get_devserver_build_from_package_url(job_repo_url))
Dan Shid37736b2016-07-06 15:10:29 -0700471 # If enable_drone_in_restricted_subnet is True, use the
472 # existing devserver. Otherwise, resolve a new one in
473 # non-restricted subnet.
474 if server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
475 ds = dev_server.ImageServer(devserver_url)
476 else:
477 ds = dev_server.ImageServer.resolve(image_name)
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800478 elif info.build is not None:
479 ds = dev_server.ImageServer.resolve(info.build, hostname)
Prathmesh Prabhu0c1dd4d2017-06-07 13:01:53 -0700480 image_name = info.build
Dan Shicf4d2032015-03-12 15:04:21 -0700481 else:
Prathmesh Prabhub6cea612017-02-09 15:41:19 -0800482 raise error.AutoservError(
483 'Failed to stage server-side package. The host has '
484 'no job_report_url attribute or version label.')
Dan Shica503482015-03-30 17:23:25 -0700485
486 # Get the OS version of the build, for any build older than
487 # MIN_VERSION_SUPPORT_SSP, server side packaging is not supported.
488 match = re.match('.*/R\d+-(\d+)\.', image_name)
489 if match and int(match.group(1)) < self.MIN_VERSION_SUPPORT_SSP:
Dan Shi14de7622016-08-22 11:09:06 -0700490 raise error.AutoservError(
491 'Build %s is older than %s. Server side packaging is '
492 'disabled.' % (image_name, self.MIN_VERSION_SUPPORT_SSP))
Dan Shica503482015-03-30 17:23:25 -0700493
Dan Shicf4d2032015-03-12 15:04:21 -0700494 ds.stage_artifacts(image_name, ['autotest_server_package'])
495 return '%s/static/%s/%s' % (ds.url(), image_name,
496 'autotest_server_package.tar.bz2')
497
498
Dan Shi0f466e82013-02-22 15:44:58 -0800499 def _try_stateful_update(self, update_url, force_update, updater):
500 """Try to use stateful update to initialize DUT.
501
502 When DUT is already running the same version that machine_install
503 tries to install, stateful update is a much faster way to clean up
504 the DUT for testing, compared to a full reimage. It is implemeted
505 by calling autoupdater.run_update, but skipping updating root, as
506 updating the kernel is time consuming and not necessary.
507
508 @param update_url: url of the image.
509 @param force_update: Set to True to update the image even if the DUT
510 is running the same version.
511 @param updater: ChromiumOSUpdater instance used to update the DUT.
512 @returns: True if the DUT was updated with stateful update.
513
514 """
Laurence Goodby778c9a42017-05-24 19:24:07 -0700515 self.prepare_for_update()
Dan Shi10b98482016-02-02 14:38:50 -0800516
J. Richard Barnette3f731032014-04-07 17:42:59 -0700517 # TODO(jrbarnette): Yes, I hate this re.match() test case.
518 # It's better than the alternative: see crbug.com/360944.
519 image_name = autoupdater.url_to_image_name(update_url)
520 release_pattern = r'^.*-release/R[0-9]+-[0-9]+\.[0-9]+\.0$'
521 if not re.match(release_pattern, image_name):
522 return False
Dan Shi0f466e82013-02-22 15:44:58 -0800523 if not updater.check_version():
524 return False
525 if not force_update:
526 logging.info('Canceling stateful update because the new and '
527 'old versions are the same.')
528 return False
529 # Following folders should be rebuilt after stateful update.
530 # A test file is used to confirm each folder gets rebuilt after
531 # the stateful update.
532 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
533 test_file = '.test_file_to_be_deleted'
Paul Hobbs38bcb702017-07-25 00:50:05 -0700534 paths = [os.path.join(folder, test_file) for folder in folders_to_check]
535 self.run('touch %s' % ' '.join(paths))
Dan Shi0f466e82013-02-22 15:44:58 -0800536
Chris Sosae92399e2015-04-24 11:32:59 -0700537 updater.run_update(update_root=False)
Dan Shi0f466e82013-02-22 15:44:58 -0800538
539 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700540 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
xixuan908b0752016-12-08 18:54:23 -0800541
542 # After stateful update and a reboot, all of the test_files shouldn't
543 # exist any more. Otherwise the stateful update is failed.
544 return not any(
545 self.path_exists(os.path.join(folder, test_file))
546 for folder in folders_to_check)
Dan Shi0f466e82013-02-22 15:44:58 -0800547
548
J. Richard Barnette7275b612013-06-04 18:13:11 -0700549 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800550 """After the DUT is updated, confirm machine_install succeeded.
551
552 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700553 @param expected_kernel: kernel expected to be active after reboot,
554 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800555
556 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700557 # Touch the lab machine file to leave a marker that
558 # distinguishes this image from other test images.
559 # Afterwards, we must re-run the autoreboot script because
560 # it depends on the _LAB_MACHINE_FILE.
J. Richard Barnette71cc1862015-12-02 10:32:38 -0800561 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || '
562 '( touch "$FILE" ; start autoreboot )')
563 self.run(autoreboot_cmd % self._LAB_MACHINE_FILE)
Chris Sosa65425082013-10-16 13:26:22 -0700564 updater.verify_boot_expectations(
565 expected_kernel, rollback_message=
Gilad Arnoldc26ae1f2015-10-22 16:09:41 -0700566 'Build %s failed to boot on %s; system rolled back to previous '
Chris Sosa65425082013-10-16 13:26:22 -0700567 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700568 # Check that we've got the build we meant to install.
569 if not updater.check_version_to_confirm_install():
570 raise autoupdater.ChromiumOSError(
571 'Failed to update %s to build %s; found build '
572 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700573 updater.update_version,
Dan Shi0942b1d2015-03-31 11:07:00 -0700574 self.get_release_version()))
Dan Shi0f466e82013-02-22 15:44:58 -0800575
Chris Sosae92399e2015-04-24 11:32:59 -0700576 logging.debug('Cleaning up old autotest directories.')
577 try:
578 installed_autodir = autotest.Autotest.get_installed_autodir(self)
579 self.run('rm -rf ' + installed_autodir)
580 except autotest.AutodirNotFoundError:
581 logging.debug('No autotest installed directory found.')
582
Dan Shi0f466e82013-02-22 15:44:58 -0800583
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700584 def _stage_image_for_update(self, image_name=None):
Chris Sosae92399e2015-04-24 11:32:59 -0700585 """Stage a build on a devserver and return the update_url and devserver.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400586
587 @param image_name: a name like lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700588 @returns a tuple with an update URL like:
Scott Zawalskieadbf702013-03-14 09:23:06 -0400589 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
Chris Sosae92399e2015-04-24 11:32:59 -0700590 and the devserver instance.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400591 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700592 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 AU: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800595 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400596 devserver.trigger_download(image_name, synchronous=False)
Chris Sosae92399e2015-04-24 11:32:59 -0700597 return (tools.image_url_pattern() % (devserver.url(), image_name),
598 devserver)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400599
600
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700601 def stage_image_for_servo(self, image_name=None):
602 """Stage a build on a devserver and return the update_url.
603
604 @param image_name: a name like lumpy-release/R27-3837.0.0
605 @returns an update URL like:
606 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
607 """
608 if not image_name:
Richard Barnette260cbd02016-10-06 12:23:28 -0700609 image_name = self._get_cros_repair_image_name()
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700610 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800611 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700612 devserver.stage_artifacts(image_name, ['test_image'])
613 return devserver.get_test_image_url(image_name)
614
615
beepse539be02013-07-31 21:57:39 -0700616 def stage_factory_image_for_servo(self, image_name):
617 """Stage a build on a devserver and return the update_url.
618
619 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700620
beepse539be02013-07-31 21:57:39 -0700621 @return: An update URL, eg:
622 http://<devserver>/static/canary-channel/\
623 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700624
625 @raises: ValueError if the factory artifact name is missing from
626 the config.
627
beepse539be02013-07-31 21:57:39 -0700628 """
629 if not image_name:
630 logging.error('Need an image_name to stage a factory image.')
631 return
632
Dan Shib8540a52015-07-16 14:18:23 -0700633 factory_artifact = CONFIG.get_config_value(
beeps12c0a3c2013-09-03 11:58:27 -0700634 'CROS', 'factory_artifact', type=str, default='')
635 if not factory_artifact:
636 raise ValueError('Cannot retrieve the factory artifact name from '
637 'autotest config, and hence cannot stage factory '
638 'artifacts.')
639
beepse539be02013-07-31 21:57:39 -0700640 logging.info('Staging build for servo install: %s', image_name)
Dan Shi216389c2015-12-22 11:03:06 -0800641 devserver = dev_server.ImageServer.resolve(image_name, self.hostname)
beepse539be02013-07-31 21:57:39 -0700642 devserver.stage_artifacts(
643 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700644 [factory_artifact],
645 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700646
647 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
648
649
xixuan016d95b2017-02-27 14:37:47 -0800650 def _get_au_monarch_fields(self, devserver, build):
651 """Form monarch fields by given devserve & build for auto-update.
652
653 @param devserver: the devserver (ImageServer instance) for auto-update.
654 @param build: the build to be updated.
655
656 @return A dictionary of monach fields.
657 """
658 try:
659 board, build_type, milestone, _ = server_utils.ParseBuildName(build)
660 except server_utils.ParseBuildNameException:
661 logging.warning('Unable to parse build name %s. Something is '
662 'likely broken, but will continue anyway.',
663 build)
664 board, build_type, milestone = ('', '', '')
665
666 monarch_fields = {
667 'dev_server': devserver.hostname,
668 'board': board,
669 'build_type': build_type,
670 'milestone': milestone
671 }
672 return monarch_fields
673
674
xixuanc30d7e92017-05-11 12:06:39 -0700675 def _retry_auto_update_with_new_devserver(self, build, last_devserver,
xixuana3bbc422017-05-04 15:57:21 -0700676 force_update, force_full_update,
David Riley5c32c2a2017-12-04 11:17:37 -0800677 force_original, quick_provision):
xixuanc30d7e92017-05-11 12:06:39 -0700678 """Kick off auto-update by devserver and send metrics.
679
680 @param build: the build to update.
681 @param last_devserver: the last devserver that failed to provision.
682 @param force_update: see |machine_install_by_devserver|'s force_udpate
683 for details.
684 @param force_full_update: see |machine_install_by_devserver|'s
685 force_full_update for details.
xixuana3bbc422017-05-04 15:57:21 -0700686 @param force_original: Whether to force stateful update with the
687 original payload.
David Riley5c32c2a2017-12-04 11:17:37 -0800688 @param quick_provision: Attempt to use quick provision path first.
xixuanc30d7e92017-05-11 12:06:39 -0700689
690 @return the result of |auto_update| in dev_server.
691 """
692 devserver = dev_server.resolve(
693 build, self.hostname, ban_list=[last_devserver.url()])
694 devserver.trigger_download(build, synchronous=False)
695 monarch_fields = self._get_au_monarch_fields(devserver, build)
696 logging.debug('Retry auto_update: resolved devserver for '
697 'auto-update: %s', devserver.url())
698
699 # Add metrics
700 install_with_dev_counter = metrics.Counter(
701 'chromeos/autotest/provision/install_with_devserver')
702 install_with_dev_counter.increment(fields=monarch_fields)
703 c = metrics.Counter(
704 'chromeos/autotest/provision/retry_by_devserver')
705 monarch_fields['last_devserver'] = last_devserver.hostname
706 monarch_fields['host'] = self.hostname
707 c.increment(fields=monarch_fields)
708
xixuan9f455342017-06-08 18:21:44 -0700709 # Won't retry auto_update in a retry of auto-update.
710 # In other words, we only retry auto-update once with a different
711 # devservers.
712 devserver.auto_update(
xixuanc30d7e92017-05-11 12:06:39 -0700713 self.hostname, build,
714 original_board=self.get_board().replace(
715 ds_constants.BOARD_PREFIX, ''),
716 original_release_version=self.get_release_version(),
717 log_dir=self.job.resultdir,
718 force_update=force_update,
xixuana3bbc422017-05-04 15:57:21 -0700719 full_update=force_full_update,
David Riley5c32c2a2017-12-04 11:17:37 -0800720 force_original=force_original,
721 quick_provision=quick_provision)
xixuanc30d7e92017-05-11 12:06:39 -0700722
723
xixuanec801e32016-08-25 10:20:22 -0700724 def machine_install_by_devserver(self, update_url=None, force_update=False,
xixuan5dc64ea2016-05-20 17:27:51 -0700725 local_devserver=False, repair=False,
726 force_full_update=False):
727 """Ultiize devserver to install the DUT.
728
729 (TODO) crbugs.com/627269: The logic in this function has some overlap
730 with those in function machine_install. The merge will be done later,
731 not in the same CL.
732
xixuanec801e32016-08-25 10:20:22 -0700733 @param update_url: The update_url or build for the host to update.
xixuan5dc64ea2016-05-20 17:27:51 -0700734 @param force_update: Force an update even if the version installed
735 is the same. Default:False
736 @param local_devserver: Used by test_that to allow people to
737 use their local devserver. Default: False
738 @param repair: Forces update to repair image. Implies force_update.
739 @param force_full_update: If True, do not attempt to run stateful
740 update, force a full reimage. If False, try stateful update
741 first when the dut is already installed with the same version.
742 @raises autoupdater.ChromiumOSError
743
744 @returns A tuple of (image_name, host_attributes).
745 image_name is the name of image installed, e.g.,
746 veyron_jerry-release/R50-7871.0.0
747 host_attributes is a dictionary of (attribute, value), which
748 can be saved to afe_host_attributes table in database. This
749 method returns a dictionary with a single entry of
750 `job_repo_url`: repo_url, where repo_url is a devserver url to
751 autotest packages.
752 """
xixuan5dc64ea2016-05-20 17:27:51 -0700753 if repair:
Richard Barnette260cbd02016-10-06 12:23:28 -0700754 update_url = self._get_cros_repair_image_name()
xixuan5dc64ea2016-05-20 17:27:51 -0700755 force_update = True
756
xixuanec801e32016-08-25 10:20:22 -0700757 if not update_url and not self._parser.options.image:
xixuan5dc64ea2016-05-20 17:27:51 -0700758 raise error.AutoservError(
759 'There is no update URL, nor a method to get one.')
760
xixuanec801e32016-08-25 10:20:22 -0700761 if not update_url and self._parser.options.image:
762 update_url = self._parser.options.image
xixuan5dc64ea2016-05-20 17:27:51 -0700763
xixuan5dc64ea2016-05-20 17:27:51 -0700764
765 # Get build from parameter or AFE.
766 # If the build is not a URL, let devserver to stage it first.
767 # Otherwise, choose a devserver to trigger auto-update.
xixuanec801e32016-08-25 10:20:22 -0700768 build = None
Aviv Keshetf9375482016-10-26 02:09:03 -0700769 devserver = None
770 logging.debug('Resolving a devserver for auto-update')
Aviv Keshet3a76f762016-10-27 17:41:51 -0700771 previously_resolved = False
xixuanec801e32016-08-25 10:20:22 -0700772 if update_url.startswith('http://'):
773 build = autoupdater.url_to_image_name(update_url)
Aviv Keshet3a76f762016-10-27 17:41:51 -0700774 previously_resolved = True
775 else:
776 build = update_url
777 devserver = dev_server.resolve(build, self.hostname)
Allen Lia5cfb972016-12-27 17:17:22 -0800778 server_name = devserver.hostname
Aviv Keshet3a76f762016-10-27 17:41:51 -0700779
xixuan016d95b2017-02-27 14:37:47 -0800780 monarch_fields = self._get_au_monarch_fields(devserver, build)
Aviv Keshet3a76f762016-10-27 17:41:51 -0700781
782 if previously_resolved:
xixuan2e62d162016-09-29 19:19:19 -0700783 # Make sure devserver for Auto-Update has staged the build.
xixuan2e62d162016-09-29 19:19:19 -0700784 if server_name not in update_url:
Aviv Keshet3a76f762016-10-27 17:41:51 -0700785 logging.debug('Resolved to devserver that does not match '
786 'update_url. The previously resolved devserver '
787 'must be unhealthy. Switching to use devserver %s,'
788 ' and re-staging.',
xixuan2e62d162016-09-29 19:19:19 -0700789 server_name)
Aviv Keshetf9375482016-10-26 02:09:03 -0700790 logging.info('Staging build for AU: %s', update_url)
xixuan2e62d162016-09-29 19:19:19 -0700791 devserver.trigger_download(build, synchronous=False)
Dan Shi5e2efb72017-02-07 11:40:23 -0800792 c = metrics.Counter(
793 'chromeos/autotest/provision/failover_download')
794 c.increment(fields=monarch_fields)
xixuan5dc64ea2016-05-20 17:27:51 -0700795 else:
Aviv Keshetf9375482016-10-26 02:09:03 -0700796 logging.info('Staging build for AU: %s', update_url)
xixuan5dc64ea2016-05-20 17:27:51 -0700797 devserver.trigger_download(build, synchronous=False)
Dan Shi5e2efb72017-02-07 11:40:23 -0800798 c = metrics.Counter('chromeos/autotest/provision/trigger_download')
799 c.increment(fields=monarch_fields)
xixuan5dc64ea2016-05-20 17:27:51 -0700800
801 # Report provision stats.
xixuan016d95b2017-02-27 14:37:47 -0800802 install_with_dev_counter = metrics.Counter(
803 'chromeos/autotest/provision/install_with_devserver')
804 install_with_dev_counter.increment(fields=monarch_fields)
xixuanec801e32016-08-25 10:20:22 -0700805 logging.debug('Resolved devserver for auto-update: %s', devserver.url())
xixuan5dc64ea2016-05-20 17:27:51 -0700806
Aviv Keshetf9375482016-10-26 02:09:03 -0700807 # and other metrics from this function.
Dan Shi5e2efb72017-02-07 11:40:23 -0800808 metrics.Counter('chromeos/autotest/provision/resolve'
809 ).increment(fields=monarch_fields)
Aviv Keshetf9375482016-10-26 02:09:03 -0700810
xixuana3bbc422017-05-04 15:57:21 -0700811 force_original = self.get_chromeos_release_milestone() is None
812
David Riley5c32c2a2017-12-04 11:17:37 -0800813 build_re = CONFIG.get_config_value(
814 'CROS', 'quick_provision_build_regex', type=str, default='')
815 quick_provision = (len(build_re) != 0 and
816 re.match(build_re, build) is not None)
817
xixuan9f455342017-06-08 18:21:44 -0700818 try:
819 devserver.auto_update(
820 self.hostname, build,
821 original_board=self.get_board().replace(
822 ds_constants.BOARD_PREFIX, ''),
823 original_release_version=self.get_release_version(),
824 log_dir=self.job.resultdir,
825 force_update=force_update,
826 full_update=force_full_update,
David Riley5c32c2a2017-12-04 11:17:37 -0800827 force_original=force_original,
828 quick_provision=quick_provision)
xixuan9f455342017-06-08 18:21:44 -0700829 except dev_server.RetryableProvisionException:
830 # It indicates that last provision failed due to devserver load
831 # issue, so another devserver is resolved to kick off provision
832 # job once again and only once.
833 logging.debug('Provision failed due to devserver issue,'
834 'retry it with another devserver.')
xixuan016d95b2017-02-27 14:37:47 -0800835
xixuan9f455342017-06-08 18:21:44 -0700836 # Check first whether this DUT is completely offline. If so, skip
837 # the following provision tries.
838 logging.debug('Checking whether host %s is online.', self.hostname)
839 if utils.ping(self.hostname, tries=1, deadline=1) == 0:
840 self._retry_auto_update_with_new_devserver(
841 build, devserver, force_update, force_full_update,
David Riley5c32c2a2017-12-04 11:17:37 -0800842 force_original, quick_provision)
xixuan9f455342017-06-08 18:21:44 -0700843 else:
844 raise error.AutoservError(
845 'No answer to ping from %s' % self.hostname)
xixuan5dc64ea2016-05-20 17:27:51 -0700846
847 # The reason to resolve a new devserver in function machine_install
848 # is mostly because that the update_url there may has a strange format,
849 # and it's hard to parse the devserver url from it.
850 # Since we already resolve a devserver to trigger auto-update, the same
851 # devserver is used to form JOB_REPO_URL here. Verified in local test.
852 repo_url = tools.get_package_url(devserver.url(), build)
853 return build, {ds_constants.JOB_REPO_URL: repo_url}
854
855
Laurence Goodby778c9a42017-05-24 19:24:07 -0700856 def prepare_for_update(self):
857 """Prepares the DUT for an update.
858
859 Subclasses may override this to perform any special actions
860 required before updating.
861 """
Laurence Goodby468de252017-06-08 17:22:53 -0700862 pass
Laurence Goodby778c9a42017-05-24 19:24:07 -0700863
864
Chris Sosaa3ac2152012-05-23 22:23:13 -0700865 def machine_install(self, update_url=None, force_update=False,
Richard Barnette0b023a72015-04-24 16:07:30 +0000866 local_devserver=False, repair=False,
867 force_full_update=False):
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500868 """Install the DUT.
869
Dan Shi0f466e82013-02-22 15:44:58 -0800870 Use stateful update if the DUT is already running the same build.
871 Stateful update does not update kernel and tends to run much faster
872 than a full reimage. If the DUT is running a different build, or it
873 failed to do a stateful update, full update, including kernel update,
874 will be applied to the DUT.
875
Simran Basi5ace6f22016-01-06 17:30:44 -0800876 Once a host enters machine_install its host attribute job_repo_url
877 (used for package install) will be removed and then updated.
Scott Zawalskieadbf702013-03-14 09:23:06 -0400878
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500879 @param update_url: The url to use for the update
880 pattern: http://$devserver:###/update/$build
881 If update_url is None and repair is True we will install the
Dan Shi6964fa52014-12-18 11:04:27 -0800882 stable image listed in afe_stable_versions table. If the table
883 is not setup, global_config value under CROS.stable_cros_version
884 will be used instead.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500885 @param force_update: Force an update even if the version installed
886 is the same. Default:False
Christopher Wiley6a4ff932015-05-15 14:00:47 -0700887 @param local_devserver: Used by test_that to allow people to
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500888 use their local devserver. Default: False
Chris Sosae92399e2015-04-24 11:32:59 -0700889 @param repair: Forces update to repair image. Implies force_update.
Fang Deng3d3b9272014-12-22 12:20:28 -0800890 @param force_full_update: If True, do not attempt to run stateful
891 update, force a full reimage. If False, try stateful update
892 first when the dut is already installed with the same version.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500893 @raises autoupdater.ChromiumOSError
894
Dan Shibe3636a2016-02-14 22:48:01 -0800895 @returns A tuple of (image_name, host_attributes).
896 image_name is the name of image installed, e.g.,
897 veyron_jerry-release/R50-7871.0.0
898 host_attributes is a dictionary of (attribute, value), which
899 can be saved to afe_host_attributes table in database. This
900 method returns a dictionary with a single entry of
901 `job_repo_url`: repo_url, where repo_url is a devserver url to
902 autotest packages.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500903 """
Chris Sosae92399e2015-04-24 11:32:59 -0700904 devserver = None
Richard Barnette0b023a72015-04-24 16:07:30 +0000905 if repair:
Chris Sosae92399e2015-04-24 11:32:59 -0700906 update_url, devserver = self._stage_image_for_update()
Richard Barnette0b023a72015-04-24 16:07:30 +0000907 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800908
Chris Sosae92399e2015-04-24 11:32:59 -0700909 if not update_url and not self._parser.options.image:
910 raise error.AutoservError(
Dan Shid07ee2e2015-09-24 14:49:25 -0700911 'There is no update URL, nor a method to get one.')
Chris Sosae92399e2015-04-24 11:32:59 -0700912
913 if not update_url and self._parser.options.image:
914 # This is the base case where we have no given update URL i.e.
915 # dynamic suites logic etc. This is the most flexible case where we
916 # can serve an update from any of our fleet of devservers.
917 requested_build = self._parser.options.image
918 if not requested_build.startswith('http://'):
919 logging.debug('Update will be staged for this installation')
920 update_url, devserver = self._stage_image_for_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700921 requested_build)
Chris Sosae92399e2015-04-24 11:32:59 -0700922 else:
923 update_url = requested_build
924
925 logging.debug('Update URL is %s', update_url)
926
Dan Shif48f8132016-02-18 10:34:30 -0800927 # Report provision stats.
Allen Lia5cfb972016-12-27 17:17:22 -0800928 server_name = dev_server.get_hostname(update_url)
Allen Li48a13fe2016-11-22 14:10:40 -0800929 (metrics.Counter('chromeos/autotest/provision/install')
930 .increment(fields={'devserver': server_name}))
Dan Shif48f8132016-02-18 10:34:30 -0800931
Dan Shid07ee2e2015-09-24 14:49:25 -0700932 # Create a file to indicate if provision fails. The file will be removed
933 # by stateful update or full install.
J. Richard Barnette91137f02016-03-10 16:52:26 -0800934 self.run('touch %s' % self.PROVISION_FAILED)
Dan Shid07ee2e2015-09-24 14:49:25 -0700935
Chris Sosae92399e2015-04-24 11:32:59 -0700936 update_complete = False
937 updater = autoupdater.ChromiumOSUpdater(
938 update_url, host=self, local_devserver=local_devserver)
Fang Deng3d3b9272014-12-22 12:20:28 -0800939 if not force_full_update:
940 try:
Chris Sosae92399e2015-04-24 11:32:59 -0700941 # If the DUT is already running the same build, try stateful
942 # update first as it's much quicker than a full re-image.
943 update_complete = self._try_stateful_update(
Dan Shid07ee2e2015-09-24 14:49:25 -0700944 update_url, force_update, updater)
Fang Deng3d3b9272014-12-22 12:20:28 -0800945 except Exception as e:
946 logging.exception(e)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700947
Dan Shi0f466e82013-02-22 15:44:58 -0800948 inactive_kernel = None
Chris Sosae92399e2015-04-24 11:32:59 -0700949 if update_complete or (not force_update and updater.check_version()):
950 logging.info('Install complete without full update')
951 else:
952 logging.info('DUT requires full update.')
953 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Laurence Goodby778c9a42017-05-24 19:24:07 -0700954 self.prepare_for_update()
Dan Shi10b98482016-02-02 14:38:50 -0800955
Chris Sosae92399e2015-04-24 11:32:59 -0700956 num_of_attempts = provision.FLAKY_DEVSERVER_ATTEMPTS
Chris Sosab7612bc2013-03-21 10:32:37 -0700957
Chris Sosae92399e2015-04-24 11:32:59 -0700958 while num_of_attempts > 0:
959 num_of_attempts -= 1
960 try:
961 updater.run_update()
962 except Exception:
963 logging.warn('Autoupdate did not complete.')
964 # Do additional check for the devserver health. Ideally,
965 # the autoupdater.py could raise an exception when it
966 # detected network flake but that would require
967 # instrumenting the update engine and parsing it log.
968 if (num_of_attempts <= 0 or
969 devserver is None or
xixuan9e2c98d2016-02-26 19:04:53 -0800970 dev_server.ImageServer.devserver_healthy(
Chris Sosae92399e2015-04-24 11:32:59 -0700971 devserver.url())):
Dan Shid07ee2e2015-09-24 14:49:25 -0700972 raise
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700973
Chris Sosae92399e2015-04-24 11:32:59 -0700974 logging.warn('Devserver looks unhealthy. Trying another')
975 update_url, devserver = self._stage_image_for_update(
976 requested_build)
977 logging.debug('New Update URL is %s', update_url)
978 updater = autoupdater.ChromiumOSUpdater(
979 update_url, host=self,
980 local_devserver=local_devserver)
981 else:
982 break
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700983
Chris Sosae92399e2015-04-24 11:32:59 -0700984 # Give it some time in case of IO issues.
985 time.sleep(10)
Dan Shi5699ac22014-12-19 10:55:49 -0800986
Chris Sosae92399e2015-04-24 11:32:59 -0700987 # Figure out active and inactive kernel.
988 active_kernel, inactive_kernel = updater.get_kernel_state()
Simran Basi13fa1ba2013-03-04 10:56:47 -0800989
Chris Sosae92399e2015-04-24 11:32:59 -0700990 # Ensure inactive kernel has higher priority than active.
991 if (updater.get_kernel_priority(inactive_kernel)
992 < updater.get_kernel_priority(active_kernel)):
993 raise autoupdater.ChromiumOSError(
994 'Update failed. The priority of the inactive kernel'
995 ' partition is less than that of the active kernel'
996 ' partition.')
997
998 # Updater has returned successfully; reboot the host.
Richard Barnettefc3d7432016-07-07 10:21:31 -0700999 #
1000 # Regarding the 'crossystem' command: In some cases, the
1001 # TPM gets into a state such that it fails verification.
1002 # We don't know why. However, this call papers over the
1003 # problem by clearing the TPM during the reboot.
1004 #
1005 # We ignore failures from 'crossystem'. Although failure
1006 # here is unexpected, and could signal a bug, the point
1007 # of the exercise is to paper over problems; allowing
1008 # this to fail would defeat the purpose.
1009 self.run('crossystem clear_tpm_owner_request=1',
1010 ignore_status=True)
Chris Sosae92399e2015-04-24 11:32:59 -07001011 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
1012
1013 self._post_update_processing(updater, inactive_kernel)
Simran Basi5ace6f22016-01-06 17:30:44 -08001014 image_name = autoupdater.url_to_image_name(update_url)
Dan Shibe3636a2016-02-14 22:48:01 -08001015 # update_url is different from devserver url needed to stage autotest
1016 # packages, therefore, resolve a new devserver url here.
1017 devserver_url = dev_server.ImageServer.resolve(image_name,
1018 self.hostname).url()
1019 repo_url = tools.get_package_url(devserver_url, image_name)
1020 return image_name, {ds_constants.JOB_REPO_URL: repo_url}
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001021
1022
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001023 def _clear_fw_version_labels(self, rw_only):
1024 """Clear firmware version labels from the machine.
1025
1026 @param rw_only: True to only clear fwrw_version; otherewise, clear
1027 both fwro_version and fwrw_version.
1028 """
Dan Shi9cb0eec2014-06-03 09:04:50 -07001029 labels = self._AFE.get_labels(
Dan Shi0723bf52015-06-24 10:52:38 -07001030 name__startswith=provision.FW_RW_VERSION_PREFIX,
Dan Shi9cb0eec2014-06-03 09:04:50 -07001031 host__hostname=self.hostname)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001032 if not rw_only:
1033 labels = labels + self._AFE.get_labels(
1034 name__startswith=provision.FW_RO_VERSION_PREFIX,
1035 host__hostname=self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001036 for label in labels:
1037 label.remove_hosts(hosts=[self.hostname])
1038
1039
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001040 def _add_fw_version_label(self, build, rw_only):
Dan Shi9cb0eec2014-06-03 09:04:50 -07001041 """Add firmware version label to the machine.
1042
1043 @param build: Build of firmware.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001044 @param rw_only: True to only add fwrw_version; otherwise, add both
1045 fwro_version and fwrw_version.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001046
1047 """
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +00001048 fw_label = provision.fwrw_version_to_label(build)
MK Ryu73be9862015-07-06 12:25:00 -07001049 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001050 if not rw_only:
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +00001051 fw_label = provision.fwro_version_to_label(build)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001052 self._AFE.run('label_add_hosts', id=fw_label, hosts=[self.hostname])
Dan Shi9cb0eec2014-06-03 09:04:50 -07001053
1054
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001055 def firmware_install(self, build=None, rw_only=False):
Dan Shi9cb0eec2014-06-03 09:04:50 -07001056 """Install firmware to the DUT.
1057
1058 Use stateful update if the DUT is already running the same build.
1059 Stateful update does not update kernel and tends to run much faster
1060 than a full reimage. If the DUT is running a different build, or it
1061 failed to do a stateful update, full update, including kernel update,
1062 will be applied to the DUT.
1063
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001064 Once a host enters firmware_install its fw[ro|rw]_version label will
1065 be removed. After the firmware is updated successfully, a new
1066 fw[ro|rw]_version label will be added to the host.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001067
1068 @param build: The build version to which we want to provision the
1069 firmware of the machine,
1070 e.g. 'link-firmware/R22-2695.1.144'.
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001071 @param rw_only: True to only install firmware to its RW portions. Keep
1072 the RO portions unchanged.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001073
1074 TODO(dshi): After bug 381718 is fixed, update here with corresponding
1075 exceptions that could be raised.
1076
1077 """
1078 if not self.servo:
1079 raise error.TestError('Host %s does not have servo.' %
1080 self.hostname)
1081
Wai-Hong Tam64cf7912017-05-23 16:41:40 -07001082 # Get the DUT board name from servod.
1083 board = self.servo.get_board()
Dan Shi9cb0eec2014-06-03 09:04:50 -07001084
Chris Sosae92399e2015-04-24 11:32:59 -07001085 # If build is not set, try to install firmware from stable CrOS.
Dan Shi9cb0eec2014-06-03 09:04:50 -07001086 if not build:
Richard Barnette260cbd02016-10-06 12:23:28 -07001087 build = afe_utils.get_stable_faft_version(board)
Dan Shi3d7a0e12015-10-12 11:55:45 -07001088 if not build:
1089 raise error.TestError(
1090 'Failed to find stable firmware build for %s.',
1091 self.hostname)
1092 logging.info('Will install firmware from build %s.', build)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001093
1094 config = FAFTConfig(board)
1095 if config.use_u_boot:
1096 ap_image = 'image-%s.bin' % board
1097 else: # Depthcharge platform
1098 ap_image = 'image.bin'
1099 ec_image = 'ec.bin'
Dan Shi216389c2015-12-22 11:03:06 -08001100 ds = dev_server.ImageServer.resolve(build, self.hostname)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001101 ds.stage_artifacts(build, ['firmware'])
1102
1103 tmpd = autotemp.tempdir(unique_id='fwimage')
1104 try:
1105 fwurl = self._FW_IMAGE_URL_PATTERN % (ds.url(), build)
1106 local_tarball = os.path.join(tmpd.name, os.path.basename(fwurl))
xixuan4e116822016-11-17 15:32:10 -08001107 ds.download_file(fwurl, local_tarball)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001108
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +08001109 self._clear_fw_version_labels(rw_only)
Tom Wai-Hong Tam552fee12016-05-11 06:27:58 +08001110 if config.chrome_ec:
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +08001111 logging.info('Will re-program EC %snow', 'RW ' if rw_only else '')
1112 server_utils.system('tar xf %s -C %s %s' %
1113 (local_tarball, tmpd.name, ec_image),
1114 timeout=60)
1115 self.servo.program_ec(os.path.join(tmpd.name, ec_image), rw_only)
1116 else:
1117 logging.info('Not a Chrome EC, ignore re-programing it')
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001118 logging.info('Will re-program BIOS %snow', 'RW ' if rw_only else '')
Tom Wai-Hong Tamf9db57a2016-03-17 05:32:22 +08001119 server_utils.system('tar xf %s -C %s %s' %
1120 (local_tarball, tmpd.name, ap_image),
1121 timeout=60)
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +08001122 self.servo.program_bios(os.path.join(tmpd.name, ap_image), rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001123 self.servo.get_power_state_controller().reset()
1124 time.sleep(self.servo.BOOT_DELAY)
Wai-Hong Tamb5f66ce2016-11-10 15:45:30 -08001125 if utils.host_is_in_lab_zone(self.hostname):
1126 self._add_fw_version_label(build, rw_only)
Dan Shi9cb0eec2014-06-03 09:04:50 -07001127 finally:
1128 tmpd.clean()
1129
1130
Dan Shi10e992b2013-08-30 11:02:59 -07001131 def show_update_engine_log(self):
1132 """Output update engine log."""
MK Ryu35d661e2014-09-25 17:44:10 -07001133 logging.debug('Dumping %s', client_constants.UPDATE_ENGINE_LOG)
1134 self.run('cat %s' % client_constants.UPDATE_ENGINE_LOG)
Dan Shi10e992b2013-08-30 11:02:59 -07001135
1136
beepsf079cfb2013-09-18 17:49:51 -07001137 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
1138 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -05001139 """
1140 Re-install the OS on the DUT by:
1141 1) installing a test image on a USB storage device attached to the Servo
1142 board,
Richard Barnette03a0c132012-11-05 12:40:35 -08001143 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -07001144 3) installing the image with chromeos-install.
1145
Scott Zawalski62bacae2013-03-05 10:40:32 -05001146 @param image_url: If specified use as the url to install on the DUT.
1147 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -07001148 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
1149 Factory images need a longer usb_boot_timeout than regular
1150 cros images.
1151 @param install_timeout: The timeout to use when installing the chromeos
1152 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -08001153
Scott Zawalski62bacae2013-03-05 10:40:32 -05001154 @raises AutoservError if the image fails to boot.
beepsf079cfb2013-09-18 17:49:51 -07001155
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001156 """
beepsf079cfb2013-09-18 17:49:51 -07001157 logging.info('Downloading image to USB, then booting from it. Usb boot '
1158 'timeout = %s', usb_boot_timeout)
Allen Li48a13fe2016-11-22 14:10:40 -08001159 with metrics.SecondsTimer(
1160 'chromeos/autotest/provision/servo_install/boot_duration'):
1161 self.servo.install_recovery_image(image_url)
1162 if not self.wait_up(timeout=usb_boot_timeout):
1163 raise hosts.AutoservRepairError(
1164 'DUT failed to boot from USB after %d seconds' %
1165 usb_boot_timeout)
Scott Zawalski62bacae2013-03-05 10:40:32 -05001166
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001167 # The new chromeos-tpm-recovery has been merged since R44-7073.0.0.
1168 # In old CrOS images, this command fails. Skip the error.
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001169 logging.info('Resetting the TPM status')
Tom Wai-Hong Tamf6b4f812015-08-08 04:14:59 +08001170 try:
1171 self.run('chromeos-tpm-recovery')
1172 except error.AutoservRunError:
1173 logging.warn('chromeos-tpm-recovery is too old.')
1174
Tom Wai-Hong Tam27af7332015-07-25 06:09:39 +08001175
Allen Li48a13fe2016-11-22 14:10:40 -08001176 with metrics.SecondsTimer(
1177 'chromeos/autotest/provision/servo_install/install_duration'):
1178 logging.info('Installing image through chromeos-install.')
1179 self.run('chromeos-install --yes', timeout=install_timeout)
1180 self.halt()
beepsf079cfb2013-09-18 17:49:51 -07001181
1182 logging.info('Power cycling DUT through servo.')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001183 self.servo.get_power_state_controller().power_off()
Fang Dengafb88142013-05-30 17:44:31 -07001184 self.servo.switch_usbkey('off')
J. Richard Barnette0199cc82014-12-05 17:08:40 -08001185 # N.B. The Servo API requires that we use power_on() here
1186 # for two reasons:
1187 # 1) After turning on a DUT in recovery mode, you must turn
1188 # it off and then on with power_on() once more to
1189 # disable recovery mode (this is a Parrot specific
1190 # requirement).
1191 # 2) After power_off(), the only way to turn on is with
1192 # power_on() (this is a Storm specific requirement).
J. Richard Barnettefbcc7122013-07-24 18:24:59 -07001193 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -07001194
1195 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -08001196 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
1197 raise error.AutoservError('DUT failed to reboot installed '
1198 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -05001199 self.BOOT_TIMEOUT)
1200
1201
Richard Barnette9a26ad62016-06-10 12:03:08 -07001202 def repair_servo(self):
Dan Shi90466352015-09-22 15:01:05 -07001203 """
Richard Barnette9a26ad62016-06-10 12:03:08 -07001204 Confirm that servo is initialized and verified.
Dan Shi90466352015-09-22 15:01:05 -07001205
Richard Barnette9a26ad62016-06-10 12:03:08 -07001206 If the servo object is missing, attempt to repair the servo
1207 host. Repair failures are passed back to the caller.
1208
1209 @raise AutoservError: If there is no servo host for this CrOS
1210 host.
1211 """
1212 if self.servo:
1213 return
1214 if not self._servo_host:
1215 raise error.AutoservError('No servo host for %s.' %
1216 self.hostname)
1217 self._servo_host.repair()
1218 self.servo = self._servo_host.get_servo()
Dan Shi90466352015-09-22 15:01:05 -07001219
1220
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -08001221 def repair(self):
1222 """Attempt to get the DUT to pass `self.verify()`.
Richard Barnette82c35912012-11-20 10:09:10 -08001223
1224 This overrides the base class function for repair; it does
J. Richard Barnette91137f02016-03-10 16:52:26 -08001225 not call back to the parent class, but instead relies on
1226 `self._repair_strategy` to coordinate the verification and
1227 repair steps needed to get the DUT working.
Richard Barnette82c35912012-11-20 10:09:10 -08001228 """
J. Richard Barnette91137f02016-03-10 16:52:26 -08001229 self._repair_strategy.repair(self)
Richard Barnettebfab8672017-08-10 14:48:28 -07001230 # Sometimes, hosts with certain ethernet dongles get stuck in a
1231 # bad network state where they're reachable from this code, but
1232 # not from the devservers during provisioning. Rebooting the
1233 # DUT fixes it.
1234 #
1235 # TODO(jrbarnette): Ideally, we'd get rid of the problem
1236 # dongles, and drop this code. Failing that, we could be smart
1237 # enough not to reboot if repair rebooted the DUT (e.g. by
1238 # looking at DUT uptime after repair completes).
1239
1240 self.reboot()
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001241
Richard Barnette82c35912012-11-20 10:09:10 -08001242
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001243 def close(self):
David Rileye2c6be12017-12-11 10:20:57 -08001244 """Close connection."""
Fang Deng0ca40e22013-08-27 17:47:44 -07001245 super(CrosHost, self).close()
xixuand6011f12016-12-08 15:01:58 -08001246 if self._chameleon_host:
1247 self._chameleon_host.close()
1248
1249 if self._servo_host:
1250 self._servo_host.close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001251
1252
Dan Shi49ca0932014-11-14 11:22:27 -08001253 def get_power_supply_info(self):
1254 """Get the output of power_supply_info.
1255
1256 power_supply_info outputs the info of each power supply, e.g.,
1257 Device: Line Power
1258 online: no
1259 type: Mains
1260 voltage (V): 0
1261 current (A): 0
1262 Device: Battery
1263 state: Discharging
1264 percentage: 95.9276
1265 technology: Li-ion
1266
1267 Above output shows two devices, Line Power and Battery, with details of
1268 each device listed. This function parses the output into a dictionary,
1269 with key being the device name, and value being a dictionary of details
1270 of the device info.
1271
1272 @return: The dictionary of power_supply_info, e.g.,
1273 {'Line Power': {'online': 'yes', 'type': 'main'},
1274 'Battery': {'vendor': 'xyz', 'percentage': '100'}}
Dan Shie9b765d2014-12-29 16:59:49 -08001275 @raise error.AutoservRunError if power_supply_info tool is not found in
1276 the DUT. Caller should handle this error to avoid false failure
1277 on verification.
Dan Shi49ca0932014-11-14 11:22:27 -08001278 """
1279 result = self.run('power_supply_info').stdout.strip()
1280 info = {}
1281 device_name = None
1282 device_info = {}
1283 for line in result.split('\n'):
1284 pair = [v.strip() for v in line.split(':')]
1285 if len(pair) != 2:
1286 continue
1287 if pair[0] == 'Device':
1288 if device_name:
1289 info[device_name] = device_info
1290 device_name = pair[1]
1291 device_info = {}
1292 else:
1293 device_info[pair[0]] = pair[1]
1294 if device_name and not device_name in info:
1295 info[device_name] = device_info
1296 return info
1297
1298
1299 def get_battery_percentage(self):
1300 """Get the battery percentage.
1301
1302 @return: The percentage of battery level, value range from 0-100. Return
1303 None if the battery info cannot be retrieved.
1304 """
1305 try:
1306 info = self.get_power_supply_info()
1307 logging.info(info)
1308 return float(info['Battery']['percentage'])
Dan Shie9b765d2014-12-29 16:59:49 -08001309 except (KeyError, ValueError, error.AutoservRunError):
Dan Shi49ca0932014-11-14 11:22:27 -08001310 return None
1311
1312
1313 def is_ac_connected(self):
1314 """Check if the dut has power adapter connected and charging.
1315
1316 @return: True if power adapter is connected and charging.
1317 """
1318 try:
1319 info = self.get_power_supply_info()
1320 return info['Line Power']['online'] == 'yes'
Dan Shie9b765d2014-12-29 16:59:49 -08001321 except (KeyError, error.AutoservRunError):
1322 return None
Dan Shi49ca0932014-11-14 11:22:27 -08001323
1324
Simran Basi5e6339a2013-03-21 11:34:32 -07001325 def _cleanup_poweron(self):
1326 """Special cleanup method to make sure hosts always get power back."""
1327 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1328 hosts = afe.get_hosts(hostname=self.hostname)
1329 if not hosts or not (self._RPM_OUTLET_CHANGED in
1330 hosts[0].attributes):
1331 return
1332 logging.debug('This host has recently interacted with the RPM'
1333 ' Infrastructure. Ensuring power is on.')
1334 try:
1335 self.power_on()
Dan Shi7dca56e2014-11-11 17:07:56 -08001336 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1337 hostname=self.hostname)
Simran Basi5e6339a2013-03-21 11:34:32 -07001338 except rpm_client.RemotePowerException:
Simran Basi5e6339a2013-03-21 11:34:32 -07001339 logging.error('Failed to turn Power On for this host after '
1340 'cleanup through the RPM Infrastructure.')
Dan Shi49ca0932014-11-14 11:22:27 -08001341
1342 battery_percentage = self.get_battery_percentage()
Dan Shif01ebe22014-12-05 13:10:57 -08001343 if battery_percentage and battery_percentage < 50:
Dan Shi49ca0932014-11-14 11:22:27 -08001344 raise
1345 elif self.is_ac_connected():
1346 logging.info('The device has power adapter connected and '
1347 'charging. No need to try to turn RPM on '
1348 'again.')
1349 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1350 hostname=self.hostname)
1351 logging.info('Battery level is now at %s%%. The device may '
1352 'still have enough power to run test, so no '
1353 'exception will be raised.', battery_percentage)
1354
Simran Basi5e6339a2013-03-21 11:34:32 -07001355
beepsc87ff602013-07-31 21:53:00 -07001356 def _is_factory_image(self):
1357 """Checks if the image on the DUT is a factory image.
1358
1359 @return: True if the image on the DUT is a factory image.
1360 False otherwise.
1361 """
1362 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1363 return result.exit_status == 0
1364
1365
1366 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001367 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001368
1369 @raises: FactoryImageCheckerException for factory images, since
1370 we cannot attempt to restart ui on them.
1371 error.AutoservRunError for any other type of error that
1372 occurs while restarting ui.
1373 """
1374 if self._is_factory_image():
Dan Shi549fb822015-03-24 18:01:11 -07001375 raise FactoryImageCheckerException('Cannot restart ui on factory '
1376 'images')
beepsc87ff602013-07-31 21:53:00 -07001377
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001378 # TODO(jrbarnette): The command to stop/start the ui job
1379 # should live inside cros_ui, too. However that would seem
1380 # to imply interface changes to the existing start()/restart()
1381 # functions, which is a bridge too far (for now).
J. Richard Barnette6069aa12015-06-08 09:10:24 -07001382 prompt = cros_ui.get_chrome_session_ident(self)
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001383 self.run('stop ui; start ui')
1384 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001385
1386
xixuana3bbc422017-05-04 15:57:21 -07001387 def _get_lsb_release_content(self):
1388 """Return the content of lsb-release file of host."""
1389 return self.run(
1390 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
1391
1392
Dan Shi549fb822015-03-24 18:01:11 -07001393 def get_release_version(self):
1394 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
1395
1396 @returns The version string in lsb-release, under attribute
1397 CHROMEOS_RELEASE_VERSION.
1398 """
Dan Shi549fb822015-03-24 18:01:11 -07001399 return lsbrelease_utils.get_chromeos_release_version(
xixuana3bbc422017-05-04 15:57:21 -07001400 lsb_release_content=self._get_lsb_release_content())
1401
1402
1403 def get_chromeos_release_milestone(self):
1404 """Get the value of attribute CHROMEOS_RELEASE_BUILD_TYPE
1405 from lsb-release.
1406
1407 @returns The version string in lsb-release, under attribute
1408 CHROMEOS_RELEASE_BUILD_TYPE.
1409 """
1410 return lsbrelease_utils.get_chromeos_release_milestone(
1411 lsb_release_content=self._get_lsb_release_content())
Dan Shi549fb822015-03-24 18:01:11 -07001412
1413
1414 def verify_cros_version_label(self):
1415 """ Make sure host's cros-version label match the actual image in dut.
1416
1417 Remove any cros-version: label that doesn't match that installed in
1418 the dut.
1419
1420 @param raise_error: Set to True to raise exception if any mismatch found
1421
1422 @raise error.AutoservError: If any mismatch between cros-version label
1423 and the build installed in dut is found.
1424 """
1425 labels = self._AFE.get_labels(
1426 name__startswith=ds_constants.VERSION_PREFIX,
1427 host__hostname=self.hostname)
1428 mismatch_found = False
1429 if labels:
1430 # Get CHROMEOS_RELEASE_VERSION from lsb-release, e.g., 6908.0.0.
1431 # Note that it's different from cros-version label, which has
1432 # builder and branch info, e.g.,
1433 # cros-version:peppy-release/R43-6908.0.0
1434 release_version = self.get_release_version()
1435 host_list = [self.hostname]
1436 for label in labels:
1437 # Remove any cros-version label that does not match
1438 # release_version.
1439 build_version = label.name[len(ds_constants.VERSION_PREFIX):]
1440 if not utils.version_match(build_version, release_version):
1441 logging.warn('cros-version label "%s" does not match '
1442 'release version %s. Removing the label.',
1443 label.name, release_version)
1444 label.remove_hosts(hosts=host_list)
1445 mismatch_found = True
1446 if mismatch_found:
1447 raise error.AutoservError('The host has wrong cros-version label.')
1448
1449
Laurence Goodby778c9a42017-05-24 19:24:07 -07001450 def cleanup_services(self):
1451 """Reinitializes the device for cleanup.
1452
1453 Subclasses may override this to customize the cleanup method.
1454
1455 To indicate failure of the reset, the implementation may raise
1456 any of:
1457 error.AutoservRunError
1458 error.AutotestRunError
1459 FactoryImageCheckerException
1460
1461 @raises error.AutoservRunError
1462 @raises error.AutotestRunError
1463 @raises error.FactoryImageCheckerException
1464 """
1465 self._restart_ui()
1466
1467
beepsc87ff602013-07-31 21:53:00 -07001468 def cleanup(self):
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -08001469 """Cleanup state on device."""
MK Ryu35d661e2014-09-25 17:44:10 -07001470 self.run('rm -f %s' % client_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001471 try:
Laurence Goodby778c9a42017-05-24 19:24:07 -07001472 self.cleanup_services()
beepsc87ff602013-07-31 21:53:00 -07001473 except (error.AutotestRunError, error.AutoservRunError,
1474 FactoryImageCheckerException):
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -07001475 logging.warning('Unable to restart ui, rebooting device.')
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001476 # Since restarting the UI fails fall back to normal Autotest
1477 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001478 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001479 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001480 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001481 self._cleanup_poweron()
Dan Shi549fb822015-03-24 18:01:11 -07001482 self.verify_cros_version_label()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001483
1484
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001485 def reboot(self, **dargs):
1486 """
1487 This function reboots the site host. The more generic
1488 RemoteHost.reboot() performs sync and sleeps for 5
1489 seconds. This is not necessary for Chrome OS devices as the
1490 sync should be finished in a short time during the reboot
1491 command.
1492 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001493 if 'reboot_cmd' not in dargs:
Doug Anderson7d5aeb22014-02-27 15:12:17 -08001494 reboot_timeout = dargs.get('reboot_timeout', 10)
J. Richard Barnette9af19632015-09-25 12:18:03 -07001495 dargs['reboot_cmd'] = ('sleep 1; '
1496 'reboot & sleep %d; '
1497 'reboot -f' % reboot_timeout)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001498 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001499 if 'fastsync' not in dargs:
1500 dargs['fastsync'] = True
Michael Liangda8c60a2014-06-03 13:24:51 -07001501
Charlie Mooneya8e6dab2014-05-29 14:37:55 -07001502 # For purposes of logging reboot times:
1503 # Get the board name i.e. 'daisy_spring'
Michael Liangca4f5a62014-07-10 15:45:13 -07001504 board_fullname = self.get_board()
1505
1506 # Strip the prefix and add it to dargs.
1507 dargs['board'] = board_fullname[board_fullname.find(':')+1:]
Vincent Palatindf2372c2016-10-07 17:03:00 +02001508 # Record who called us
1509 orig = sys._getframe(1).f_code
Vincent Palatin80780b22016-07-27 16:02:37 +02001510 metric_fields = {'board' : dargs['board'],
Vincent Palatindf2372c2016-10-07 17:03:00 +02001511 'dut_host_name' : self.hostname,
1512 'success' : True}
1513 metric_debug_fields = {'board' : dargs['board'],
1514 'caller' : "%s:%s" % (orig.co_filename, orig.co_name),
1515 'success' : True,
1516 'error' : ''}
1517
Vincent Palatin80780b22016-07-27 16:02:37 +02001518 t0 = time.time()
1519 try:
1520 super(CrosHost, self).reboot(**dargs)
1521 except Exception as e:
1522 metric_fields['success'] = False
Vincent Palatindf2372c2016-10-07 17:03:00 +02001523 metric_debug_fields['success'] = False
1524 metric_debug_fields['error'] = type(e).__name__
Vincent Palatin80780b22016-07-27 16:02:37 +02001525 raise
1526 finally:
1527 duration = int(time.time() - t0)
Dan Shi5e2efb72017-02-07 11:40:23 -08001528 metrics.Counter(
1529 'chromeos/autotest/autoserv/reboot_count').increment(
1530 fields=metric_fields)
1531 metrics.Counter(
1532 'chromeos/autotest/autoserv/reboot_debug').increment(
1533 fields=metric_debug_fields)
1534 metrics.SecondsDistribution(
1535 'chromeos/autotest/autoserv/reboot_duration').add(
1536 duration, fields=metric_fields)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001537
1538
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001539 def suspend(self, **dargs):
1540 """
1541 This function suspends the site host.
1542 """
1543 suspend_time = dargs.get('suspend_time', 60)
1544 dargs['timeout'] = suspend_time
1545 if 'suspend_cmd' not in dargs:
J. Richard Barnette9af19632015-09-25 12:18:03 -07001546 dargs['suspend_cmd'] = ' && '.join([
1547 'echo 0 > /sys/class/rtc/rtc0/wakealarm',
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001548 'echo +%d > /sys/class/rtc/rtc0/wakealarm' % suspend_time,
J. Richard Barnette9af19632015-09-25 12:18:03 -07001549 'powerd_dbus_suspend --delay=0'])
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -07001550 super(CrosHost, self).suspend(**dargs)
1551
1552
Simran Basiec564392014-08-25 16:48:09 -07001553 def upstart_status(self, service_name):
1554 """Check the status of an upstart init script.
1555
1556 @param service_name: Service to look up.
1557
1558 @returns True if the service is running, False otherwise.
1559 """
Richard Barnettee204dc52017-09-26 11:02:25 -07001560 return 'start/running' in self.run('status %s' % service_name,
1561 ignore_status=True).stdout
Simran Basiec564392014-08-25 16:48:09 -07001562
1563
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001564 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001565 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001566
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001567 Tests for the following conditions:
1568 1. All conditions tested by the parent version of this
1569 function.
1570 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001571 3. Sufficient space in /mnt/stateful_partition/encrypted.
1572 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001573
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001574 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001575 super(CrosHost, self).verify_software()
Dan Shib8540a52015-07-16 14:18:23 -07001576 default_kilo_inodes_required = CONFIG.get_config_value(
1577 'SERVER', 'kilo_inodes_required', type=int, default=100)
1578 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
1579 kilo_inodes_required = CONFIG.get_config_value(
1580 'SERVER', 'kilo_inodes_required_%s' % board,
1581 type=int, default=default_kilo_inodes_required)
1582 self.check_inodes('/mnt/stateful_partition', kilo_inodes_required)
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001583 self.check_diskspace(
1584 '/mnt/stateful_partition',
Dan Shib8540a52015-07-16 14:18:23 -07001585 CONFIG.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001586 'SERVER', 'gb_diskspace_required', type=float,
1587 default=20.0))
Gaurav Shahe448af82014-06-19 15:18:59 -07001588 encrypted_stateful_path = '/mnt/stateful_partition/encrypted'
1589 # Not all targets build with encrypted stateful support.
1590 if self.path_exists(encrypted_stateful_path):
1591 self.check_diskspace(
1592 encrypted_stateful_path,
Dan Shib8540a52015-07-16 14:18:23 -07001593 CONFIG.get_config_value(
Gaurav Shahe448af82014-06-19 15:18:59 -07001594 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1595 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001596
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -07001597 self.wait_for_system_services()
Prashanth B5d0a0512014-04-25 12:26:08 -07001598
beepsc87ff602013-07-31 21:53:00 -07001599 # Factory images don't run update engine,
1600 # goofy controls dbus on these DUTs.
1601 if not self._is_factory_image():
1602 self.run('update_engine_client --status')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001603
Dan Shi549fb822015-03-24 18:01:11 -07001604 self.verify_cros_version_label()
1605
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001606
Justin TerAvest3b0c5ba2017-11-07 09:59:11 -07001607 @retry.retry(error.AutoservError, timeout_min=5, delay_sec=10)
1608 def wait_for_system_services(self):
1609 """Waits for system-services to be running.
1610
1611 Sometimes, update_engine will take a while to update firmware, so we
1612 should give this some time to finish. See crbug.com/765686#c38 for
1613 details.
1614 """
1615 if not self.upstart_status('system-services'):
1616 raise error.AutoservError('Chrome failed to reach login. '
1617 'System services not running.')
1618
1619
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001620 def verify(self):
Pradeep Sawlaniaa013fa2017-11-09 06:34:18 -08001621 """Verify Chrome OS system is in good state."""
J. Richard Barnettea7e7fdc2016-02-12 12:35:36 -08001622 self._repair_strategy.verify(self)
1623
1624
Fang Deng96667ca2013-08-01 17:46:18 -07001625 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
Dean Liaoe3e75f62017-11-14 10:36:43 +08001626 connect_timeout=None, alive_interval=None,
1627 alive_count_max=None, connection_attempts=None):
Fang Deng96667ca2013-08-01 17:46:18 -07001628 """Override default make_ssh_command to use options tuned for Chrome OS.
1629
1630 Tuning changes:
1631 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1632 connection failure. Consistency with remote_access.sh.
1633
Samuel Tan2ce155b2015-06-23 18:24:38 -07001634 - ServerAliveInterval=900; which causes SSH to ping connection every
1635 900 seconds. In conjunction with ServerAliveCountMax ensures
1636 that if the connection dies, Autotest will bail out.
Fang Deng96667ca2013-08-01 17:46:18 -07001637 Originally tried 60 secs, but saw frequent job ABORTS where
Samuel Tan2ce155b2015-06-23 18:24:38 -07001638 the test completed successfully. Later increased from 180 seconds to
1639 900 seconds to account for tests where the DUT is suspended for
1640 longer periods of time.
Fang Deng96667ca2013-08-01 17:46:18 -07001641
1642 - ServerAliveCountMax=3; consistency with remote_access.sh.
1643
1644 - ConnectAttempts=4; reduce flakiness in connection errors;
1645 consistency with remote_access.sh.
1646
1647 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1648 Host keys change with every new installation, don't waste
1649 memory/space saving them.
1650
1651 - SSH protocol forced to 2; needed for ServerAliveInterval.
1652
1653 @param user User name to use for the ssh connection.
1654 @param port Port on the target host to use for ssh connection.
1655 @param opts Additional options to the ssh command.
1656 @param hosts_file Ignored.
1657 @param connect_timeout Ignored.
1658 @param alive_interval Ignored.
Dean Liaoe3e75f62017-11-14 10:36:43 +08001659 @param alive_count_max Ignored.
1660 @param connection_attempts Ignored.
Fang Deng96667ca2013-08-01 17:46:18 -07001661 """
Dean Liaoe3e75f62017-11-14 10:36:43 +08001662 options = ' '.join([opts, '-o Protocol=2'])
1663 return super(CrosHost, self).make_ssh_command(
1664 user=user, port=port, opts=options, hosts_file='/dev/null',
1665 connect_timeout=30, alive_interval=900, alive_count_max=3,
1666 connection_attempts=4)
1667
1668
Jason Abeleb6f924f2013-11-13 16:01:54 -08001669 def syslog(self, message, tag='autotest'):
1670 """Logs a message to syslog on host.
1671
1672 @param message String message to log into syslog
1673 @param tag String tag prefix for syslog
1674
1675 """
1676 self.run('logger -t "%s" "%s"' % (tag, message))
1677
1678
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001679 def _ping_check_status(self, status):
1680 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001681
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001682 @param status Check the ping status against this value.
1683 @return True iff `status` and the result of ping are the same
1684 (i.e. both True or both False).
1685
1686 """
1687 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1688 return not (status ^ (ping_val == 0))
1689
1690 def _ping_wait_for_status(self, status, timeout):
1691 """Wait for the host to have a given status (UP or DOWN).
1692
1693 Status is checked by polling. Polling will not last longer
1694 than the number of seconds in `timeout`. The polling
1695 interval will be long enough that only approximately
1696 _PING_WAIT_COUNT polling cycles will be executed, subject
1697 to a maximum interval of about one minute.
1698
1699 @param status Waiting will stop immediately if `ping` of the
1700 host returns this status.
1701 @param timeout Poll for at most this many seconds.
1702 @return True iff the host status from `ping` matched the
1703 requested status at the time of return.
1704
1705 """
1706 # _ping_check_status() takes about 1 second, hence the
1707 # "- 1" in the formula below.
Nathan Ciobanu38480a32016-10-25 15:26:45 -07001708 # FIXME: if the ping command errors then _ping_check_status()
1709 # returns instantly. If timeout is also smaller than twice
1710 # _PING_WAIT_COUNT then the while loop below forks many
1711 # thousands of ping commands (see /tmp/test_that_results_XXXXX/
1712 # /results-1-logging_YYY.ZZZ/debug/autoserv.DEBUG) and hogs one
1713 # CPU core for 60 seconds.
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001714 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1715 end_time = time.time() + timeout
1716 while time.time() <= end_time:
1717 if self._ping_check_status(status):
1718 return True
1719 if poll_interval > 0:
1720 time.sleep(poll_interval)
1721
1722 # The last thing we did was sleep(poll_interval), so it may
1723 # have been too long since the last `ping`. Check one more
1724 # time, just to be sure.
1725 return self._ping_check_status(status)
1726
1727 def ping_wait_up(self, timeout):
1728 """Wait for the host to respond to `ping`.
1729
1730 N.B. This method is not a reliable substitute for
1731 `wait_up()`, because a host that responds to ping will not
1732 necessarily respond to ssh. This method should only be used
1733 if the target DUT can be considered functional even if it
1734 can't be reached via ssh.
1735
1736 @param timeout Minimum time to allow before declaring the
1737 host to be non-responsive.
1738 @return True iff the host answered to ping before the timeout.
1739
1740 """
1741 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001742
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001743 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001744 """Wait until the host no longer responds to `ping`.
1745
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001746 This function can be used as a slightly faster version of
1747 `wait_down()`, by avoiding potentially long ssh timeouts.
1748
1749 @param timeout Minimum time to allow for the host to become
1750 non-responsive.
1751 @return True iff the host quit answering ping before the
1752 timeout.
1753
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001754 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001755 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001756
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001757 def test_wait_for_sleep(self, sleep_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001758 """Wait for the client to enter low-power sleep mode.
1759
1760 The test for "is asleep" can't distinguish a system that is
1761 powered off; to confirm that the unit was asleep, it is
1762 necessary to force resume, and then call
1763 `test_wait_for_resume()`.
1764
1765 This function is expected to be called from a test as part
1766 of a sequence like the following:
1767
1768 ~~~~~~~~
1769 boot_id = host.get_boot_id()
1770 # trigger sleep on the host
1771 host.test_wait_for_sleep()
1772 # trigger resume on the host
1773 host.test_wait_for_resume(boot_id)
1774 ~~~~~~~~
1775
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001776 @param sleep_timeout time limit in seconds to allow the host sleep.
1777
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001778 @exception TestFail The host did not go to sleep within
1779 the allowed time.
1780 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001781 if sleep_timeout is None:
1782 sleep_timeout = self.SLEEP_TIMEOUT
1783
1784 if not self.ping_wait_down(timeout=sleep_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001785 raise error.TestFail(
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001786 'client failed to sleep after %d seconds' % sleep_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001787
1788
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001789 def test_wait_for_resume(self, old_boot_id, resume_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001790 """Wait for the client to resume from low-power sleep mode.
1791
1792 The `old_boot_id` parameter should be the value from
1793 `get_boot_id()` obtained prior to entering sleep mode. A
1794 `TestFail` exception is raised if the boot id changes.
1795
1796 See @ref test_wait_for_sleep for more on this function's
1797 usage.
1798
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001799 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001800 target host went to sleep.
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001801 @param resume_timeout time limit in seconds to allow the host up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001802
1803 @exception TestFail The host did not respond within the
1804 allowed time.
1805 @exception TestFail The host responded, but the boot id test
1806 indicated a reboot rather than a sleep
1807 cycle.
1808 """
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001809 if resume_timeout is None:
1810 resume_timeout = self.RESUME_TIMEOUT
1811
1812 if not self.wait_up(timeout=resume_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001813 raise error.TestFail(
1814 'client failed to resume from sleep after %d seconds' %
Tom Wai-Hong Tamfced4f62014-04-17 10:56:30 +08001815 resume_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001816 else:
1817 new_boot_id = self.get_boot_id()
1818 if new_boot_id != old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001819 logging.error('client rebooted (old boot %s, new boot %s)',
1820 old_boot_id, new_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001821 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001822 'client rebooted, but sleep was expected')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001823
1824
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001825 def test_wait_for_shutdown(self, shutdown_timeout=None):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001826 """Wait for the client to shut down.
1827
1828 The test for "has shut down" can't distinguish a system that
1829 is merely asleep; to confirm that the unit was down, it is
1830 necessary to force boot, and then call test_wait_for_boot().
1831
1832 This function is expected to be called from a test as part
1833 of a sequence like the following:
1834
1835 ~~~~~~~~
1836 boot_id = host.get_boot_id()
1837 # trigger shutdown on the host
1838 host.test_wait_for_shutdown()
1839 # trigger boot on the host
1840 host.test_wait_for_boot(boot_id)
1841 ~~~~~~~~
1842
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001843 @param shutdown_timeout time limit in seconds to allow the host down.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001844 @exception TestFail The host did not shut down within the
1845 allowed time.
1846 """
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001847 if shutdown_timeout is None:
1848 shutdown_timeout = self.SHUTDOWN_TIMEOUT
1849
1850 if not self.ping_wait_down(timeout=shutdown_timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001851 raise error.TestFail(
1852 'client failed to shut down after %d seconds' %
Tom Wai-Hong Tamfe005c22014-12-03 09:25:44 +08001853 shutdown_timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001854
1855
1856 def test_wait_for_boot(self, old_boot_id=None):
1857 """Wait for the client to boot from cold power.
1858
1859 The `old_boot_id` parameter should be the value from
1860 `get_boot_id()` obtained prior to shutting down. A
1861 `TestFail` exception is raised if the boot id does not
1862 change. The boot id test is omitted if `old_boot_id` is not
1863 specified.
1864
1865 See @ref test_wait_for_shutdown for more on this function's
1866 usage.
1867
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001868 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001869 shut down.
1870
1871 @exception TestFail The host did not respond within the
1872 allowed time.
1873 @exception TestFail The host responded, but the boot id test
1874 indicated that there was no reboot.
1875 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001876 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001877 raise error.TestFail(
1878 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001879 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001880 elif old_boot_id:
1881 if self.get_boot_id() == old_boot_id:
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001882 logging.error('client not rebooted (boot %s)',
1883 old_boot_id)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001884 raise error.TestFail(
Tom Wai-Hong Tam01792682015-01-06 08:00:46 +08001885 'client is back up, but did not reboot')
Simran Basid5e5e272012-09-24 15:23:59 -07001886
1887
1888 @staticmethod
1889 def check_for_rpm_support(hostname):
1890 """For a given hostname, return whether or not it is powered by an RPM.
1891
Simran Basi1df55112013-09-06 11:25:09 -07001892 @param hostname: hostname to check for rpm support.
1893
Simran Basid5e5e272012-09-24 15:23:59 -07001894 @return None if this host does not follows the defined naming format
1895 for RPM powered DUT's in the lab. If it does follow the format,
1896 it returns a regular expression MatchObject instead.
1897 """
Fang Dengbaff9082015-01-06 13:46:15 -08001898 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001899
1900
1901 def has_power(self):
1902 """For this host, return whether or not it is powered by an RPM.
1903
1904 @return True if this host is in the CROS lab and follows the defined
1905 naming format.
1906 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001907 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001908
1909
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001910 def _set_power(self, state, power_method):
1911 """Sets the power to the host via RPM, Servo or manual.
1912
1913 @param state Specifies which power state to set to DUT
1914 @param power_method Specifies which method of power control to
1915 use. By default "RPM" will be used. Valid values
1916 are the strings "RPM", "manual", "servoj10".
1917
1918 """
1919 ACCEPTABLE_STATES = ['ON', 'OFF']
1920
1921 if state.upper() not in ACCEPTABLE_STATES:
1922 raise error.TestError('State must be one of: %s.'
1923 % (ACCEPTABLE_STATES,))
1924
1925 if power_method == self.POWER_CONTROL_SERVO:
1926 logging.info('Setting servo port J10 to %s', state)
1927 self.servo.set('prtctl3_pwren', state.lower())
1928 time.sleep(self._USB_POWER_TIMEOUT)
1929 elif power_method == self.POWER_CONTROL_MANUAL:
1930 logging.info('You have %d seconds to set the AC power to %s.',
1931 self._POWER_CYCLE_TIMEOUT, state)
1932 time.sleep(self._POWER_CYCLE_TIMEOUT)
1933 else:
1934 if not self.has_power():
1935 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07001936 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1937 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
1938 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07001939 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07001940
1941
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001942 def power_off(self, power_method=POWER_CONTROL_RPM):
1943 """Turn off power to this host via RPM, Servo or manual.
1944
1945 @param power_method Specifies which method of power control to
1946 use. By default "RPM" will be used. Valid values
1947 are the strings "RPM", "manual", "servoj10".
1948
1949 """
1950 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001951
1952
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001953 def power_on(self, power_method=POWER_CONTROL_RPM):
1954 """Turn on power to this host via RPM, Servo or manual.
1955
1956 @param power_method Specifies which method of power control to
1957 use. By default "RPM" will be used. Valid values
1958 are the strings "RPM", "manual", "servoj10".
1959
1960 """
1961 self._set_power('ON', power_method)
1962
1963
1964 def power_cycle(self, power_method=POWER_CONTROL_RPM):
1965 """Cycle power to this host by turning it OFF, then ON.
1966
1967 @param power_method Specifies which method of power control to
1968 use. By default "RPM" will be used. Valid values
1969 are the strings "RPM", "manual", "servoj10".
1970
1971 """
1972 if power_method in (self.POWER_CONTROL_SERVO,
1973 self.POWER_CONTROL_MANUAL):
1974 self.power_off(power_method=power_method)
1975 time.sleep(self._POWER_CYCLE_TIMEOUT)
1976 self.power_on(power_method=power_method)
1977 else:
1978 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001979
1980
1981 def get_platform(self):
1982 """Determine the correct platform label for this host.
1983
1984 @returns a string representing this host's platform.
1985 """
C Shapiro218e8752017-09-22 11:10:57 -06001986 cmd = 'mosys platform model'
1987 result = self.run(command=cmd, ignore_status=True)
1988 if result.exit_status == 0:
1989 return result.stdout.strip()
1990 else:
1991 # $(mosys platform model) should support all platforms, but it
1992 # currently doesn't, so this reverts to parsing the fw
1993 # for any unsupported mosys platforms.
1994 crossystem = utils.Crossystem(self)
1995 crossystem.init()
1996 # Extract fwid value and use the leading part as the platform id.
1997 # fwid generally follow the format of {platform}.{firmware version}
1998 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1999 platform = crossystem.fwid().split('.')[0].lower()
2000 # Newer platforms start with 'Google_' while the older ones do not.
2001 return platform.replace('google_', '')
Simran Basic6f1f7a2012-10-16 10:47:46 -07002002
2003
Hung-ying Tyanb1328032014-04-01 14:18:54 +08002004 def get_architecture(self):
2005 """Determine the correct architecture label for this host.
2006
2007 @returns a string representing this host's architecture.
2008 """
2009 crossystem = utils.Crossystem(self)
2010 crossystem.init()
2011 return crossystem.arch()
2012
2013
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002014 def get_chrome_version(self):
2015 """Gets the Chrome version number and milestone as strings.
2016
2017 Invokes "chrome --version" to get the version number and milestone.
2018
2019 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
2020 current Chrome version number as a string (in the form "W.X.Y.Z")
2021 and "milestone" is the first component of the version number
2022 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
2023 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
2024 of "chrome --version" and the milestone will be the empty string.
2025
2026 """
MK Ryu35d661e2014-09-25 17:44:10 -07002027 version_string = self.run(client_constants.CHROME_VERSION_COMMAND).stdout
Luis Lozano40b7d0d2014-01-17 15:12:06 -08002028 return utils.parse_chrome_version(version_string)
2029
J. Richard Barnetted2af5852016-02-05 15:03:10 -08002030
Niranjan Kumar34618872017-05-31 12:57:09 -07002031 def is_chrome_switch_present(self, switch):
David Haddock3ce538e2017-06-22 13:37:05 -07002032 """Returns True if the specified switch was provided to Chrome.
2033
2034 @param switch The chrome switch to search for.
2035 """
Niranjan Kumar34618872017-05-31 12:57:09 -07002036
Niranjan Kumar5f23fe92017-06-22 15:18:55 -07002037 command = 'pgrep -x -f -c "/opt/google/chrome/chrome.*%s.*"' % switch
2038 return self.run(command, ignore_status=True).exit_status == 0
Niranjan Kumar34618872017-05-31 12:57:09 -07002039
2040
2041 def oobe_triggers_update(self):
2042 """Returns True if this host has an OOBE flow during which
2043 it will perform an update check and perhaps an update.
2044 One example of such a flow is Hands-Off Zero-Touch Enrollment.
2045 As more such flows are developed, code handling them needs
2046 to be added here.
2047
2048 @return Boolean indicating whether this host's OOBE triggers an update.
2049 """
2050 return self.is_chrome_switch_present(
2051 '--enterprise-enable-zero-touch-enrollment=hands-off')
2052
2053
Kevin Chenga2619dc2016-03-28 11:42:08 -07002054 # TODO(kevcheng): change this to just return the board without the
2055 # 'board:' prefix and fix up all the callers. Also look into removing the
2056 # need for this method.
Simran Basic6f1f7a2012-10-16 10:47:46 -07002057 def get_board(self):
2058 """Determine the correct board label for this host.
2059
2060 @returns a string representing this host's board.
2061 """
2062 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
2063 run_method=self.run)
J. Richard Barnetted2af5852016-02-05 15:03:10 -08002064 return (ds_constants.BOARD_PREFIX +
2065 release_info['CHROMEOS_RELEASE_BOARD'])
Simran Basic6f1f7a2012-10-16 10:47:46 -07002066
Po-Hsien Wang4618adf2017-10-10 14:40:21 -07002067 def get_channel(self):
2068 """Determine the correct channel label for this host.
Simran Basic6f1f7a2012-10-16 10:47:46 -07002069
Po-Hsien Wang4618adf2017-10-10 14:40:21 -07002070 @returns: a string represeting this host's build channel.
2071 (stable, dev, beta). None on fail.
2072 """
2073 return lsbrelease_utils.get_chromeos_channel(
2074 lsb_release_content=self._get_lsb_release_content())
Kevin Chenga328da62016-03-31 10:49:04 -07002075
2076 def has_lightsensor(self):
2077 """Determine the correct board label for this host.
2078
2079 @returns the string 'lightsensor' if this host has a lightsensor or
2080 None if it does not.
2081 """
2082 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
2083 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
2084 try:
2085 # Run the search cmd following the symlinks. Stderr_tee is set to
2086 # None as there can be a symlink loop, but the command will still
2087 # execute correctly with a few messages printed to stderr.
2088 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
2089 return 'lightsensor'
2090 except error.AutoservRunError:
2091 # egrep exited with a return code of 1 meaning none of the possible
2092 # lightsensor files existed.
2093 return None
2094
2095
2096 def has_bluetooth(self):
2097 """Determine the correct board label for this host.
2098
2099 @returns the string 'bluetooth' if this host has bluetooth or
2100 None if it does not.
2101 """
2102 try:
2103 self.run('test -d /sys/class/bluetooth/hci0')
2104 # test exited with a return code of 0.
2105 return 'bluetooth'
2106 except error.AutoservRunError:
2107 # test exited with a return code 1 meaning the directory did not
2108 # exist.
2109 return None
2110
2111
Kevin Chenga328da62016-03-31 10:49:04 -07002112 def get_accels(self):
2113 """
2114 Determine the type of accelerometers on this host.
2115
2116 @returns a string representing this host's accelerometer type.
2117 At present, it only returns "accel:cros-ec", for accelerometers
2118 attached to a Chrome OS EC, or none, if no accelerometers.
2119 """
2120 # Check to make sure we have ectool
2121 rv = self.run('which ectool', ignore_status=True)
2122 if rv.exit_status:
2123 logging.info("No ectool cmd found, assuming no EC accelerometers")
2124 return None
2125
2126 # Check that the EC supports the motionsense command
2127 rv = self.run('ectool motionsense', ignore_status=True)
2128 if rv.exit_status:
2129 logging.info("EC does not support motionsense command "
2130 "assuming no EC accelerometers")
2131 return None
2132
2133 # Check that EC motion sensors are active
2134 active = self.run('ectool motionsense active').stdout.split('\n')
2135 if active[0] == "0":
2136 logging.info("Motion sense inactive, assuming no EC accelerometers")
2137 return None
2138
2139 logging.info("EC accelerometers found")
2140 return 'accel:cros-ec'
2141
2142
2143 def has_chameleon(self):
2144 """Determine if a Chameleon connected to this host.
2145
2146 @returns a list containing two strings ('chameleon' and
2147 'chameleon:' + label, e.g. 'chameleon:hdmi') if this host
2148 has a Chameleon or None if it has not.
2149 """
2150 if self._chameleon_host:
2151 return ['chameleon', 'chameleon:' + self.chameleon.get_label()]
2152 else:
2153 return None
2154
2155
2156 def has_loopback_dongle(self):
2157 """Determine if an audio loopback dongle is plugged to this host.
2158
2159 @returns 'audio_loopback_dongle' when there is an audio loopback dongle
2160 plugged to this host.
2161 None when there is no audio loopback dongle
2162 plugged to this host.
2163 """
2164 nodes_info = self.run(command=cras_utils.get_cras_nodes_cmd(),
2165 ignore_status=True).stdout
2166 if (cras_utils.node_type_is_plugged('HEADPHONE', nodes_info) and
2167 cras_utils.node_type_is_plugged('MIC', nodes_info)):
2168 return 'audio_loopback_dongle'
2169 else:
2170 return None
2171
2172
2173 def get_power_supply(self):
2174 """
2175 Determine what type of power supply the host has
2176
2177 @returns a string representing this host's power supply.
2178 'power:battery' when the device has a battery intended for
2179 extended use
2180 'power:AC_primary' when the device has a battery not intended
2181 for extended use (for moving the machine, etc)
2182 'power:AC_only' when the device has no battery at all.
2183 """
2184 psu = self.run(command='mosys psu type', ignore_status=True)
2185 if psu.exit_status:
2186 # The psu command for mosys is not included for all platforms. The
2187 # assumption is that the device will have a battery if the command
2188 # is not found.
2189 return 'power:battery'
2190
2191 psu_str = psu.stdout.strip()
2192 if psu_str == 'unknown':
2193 return None
2194
2195 return 'power:%s' % psu_str
2196
2197
2198 def get_storage(self):
2199 """
2200 Determine the type of boot device for this host.
2201
2202 Determine if the internal device is SCSI or dw_mmc device.
2203 Then check that it is SSD or HDD or eMMC or something else.
2204
2205 @returns a string representing this host's internal device type.
2206 'storage:ssd' when internal device is solid state drive
2207 'storage:hdd' when internal device is hard disk drive
2208 'storage:mmc' when internal device is mmc drive
2209 None When internal device is something else or
2210 when we are unable to determine the type
2211 """
2212 # The output should be /dev/mmcblk* for SD/eMMC or /dev/sd* for scsi
2213 rootdev_cmd = ' '.join(['. /usr/sbin/write_gpt.sh;',
2214 '. /usr/share/misc/chromeos-common.sh;',
2215 'load_base_vars;',
2216 'get_fixed_dst_drive'])
2217 rootdev = self.run(command=rootdev_cmd, ignore_status=True)
2218 if rootdev.exit_status:
2219 logging.info("Fail to run %s", rootdev_cmd)
2220 return None
2221 rootdev_str = rootdev.stdout.strip()
2222
2223 if not rootdev_str:
2224 return None
2225
2226 rootdev_base = os.path.basename(rootdev_str)
2227
2228 mmc_pattern = '/dev/mmcblk[0-9]'
2229 if re.match(mmc_pattern, rootdev_str):
2230 # Use type to determine if the internal device is eMMC or somthing
2231 # else. We can assume that MMC is always an internal device.
2232 type_cmd = 'cat /sys/block/%s/device/type' % rootdev_base
2233 type = self.run(command=type_cmd, ignore_status=True)
2234 if type.exit_status:
2235 logging.info("Fail to run %s", type_cmd)
2236 return None
2237 type_str = type.stdout.strip()
2238
2239 if type_str == 'MMC':
2240 return 'storage:mmc'
2241
2242 scsi_pattern = '/dev/sd[a-z]+'
2243 if re.match(scsi_pattern, rootdev.stdout):
2244 # Read symlink for /sys/block/sd* to determine if the internal
2245 # device is connected via ata or usb.
2246 link_cmd = 'readlink /sys/block/%s' % rootdev_base
2247 link = self.run(command=link_cmd, ignore_status=True)
2248 if link.exit_status:
2249 logging.info("Fail to run %s", link_cmd)
2250 return None
2251 link_str = link.stdout.strip()
2252 if 'usb' in link_str:
2253 return None
2254
2255 # Read rotation to determine if the internal device is ssd or hdd.
2256 rotate_cmd = str('cat /sys/block/%s/queue/rotational'
2257 % rootdev_base)
2258 rotate = self.run(command=rotate_cmd, ignore_status=True)
2259 if rotate.exit_status:
2260 logging.info("Fail to run %s", rotate_cmd)
2261 return None
2262 rotate_str = rotate.stdout.strip()
2263
2264 rotate_dict = {'0':'storage:ssd', '1':'storage:hdd'}
2265 return rotate_dict.get(rotate_str)
2266
2267 # All other internal device / error case will always fall here
2268 return None
2269
2270
2271 def get_servo(self):
2272 """Determine if the host has a servo attached.
2273
2274 If the host has a working servo attached, it should have a servo label.
2275
2276 @return: string 'servo' if the host has servo attached. Otherwise,
2277 returns None.
2278 """
2279 return 'servo' if self._servo_host else None
2280
2281
2282 def get_video_labels(self):
2283 """Run /usr/local/bin/avtest_label_detect to get a list of video labels.
2284
2285 Sample output of avtest_label_detect:
2286 Detected label: hw_video_acc_vp8
2287 Detected label: webcam
2288
2289 @return: A list of labels detected by tool avtest_label_detect.
2290 """
2291 try:
2292 result = self.run('/usr/local/bin/avtest_label_detect').stdout
2293 return re.findall('^Detected label: (\w+)$', result, re.M)
2294 except error.AutoservRunError:
2295 # The tool is not installed.
2296 return []
2297
2298
2299 def is_video_glitch_detection_supported(self):
2300 """ Determine if a board under test is supported for video glitch
2301 detection tests.
2302
2303 @return: 'video_glitch_detection' if board is supported, None otherwise.
2304 """
2305 board = self.get_board().replace(ds_constants.BOARD_PREFIX, '')
2306
2307 if board in video_test_constants.SUPPORTED_BOARDS:
2308 return 'video_glitch_detection'
2309
2310 return None
2311
2312
2313 def get_touch(self):
2314 """
2315 Determine whether board under test has a touchpad or touchscreen.
2316
2317 @return: A list of some combination of 'touchscreen' and 'touchpad',
2318 depending on what is present on the device.
2319
2320 """
2321 labels = []
2322 looking_for = ['touchpad', 'touchscreen']
2323 player = input_playback.InputPlayback()
2324 input_events = self.run('ls /dev/input/event*').stdout.strip().split()
2325 filename = '/tmp/touch_labels'
2326 for event in input_events:
2327 self.run('evtest %s > %s' % (event, filename), timeout=1,
2328 ignore_timeout=True)
2329 properties = self.run('cat %s' % filename).stdout
2330 input_type = player._determine_input_type(properties)
2331 if input_type in looking_for:
2332 labels.append(input_type)
2333 looking_for.remove(input_type)
2334 if len(looking_for) == 0:
2335 break
2336 self.run('rm %s' % filename)
2337
2338 return labels
2339
2340
2341 def has_internal_display(self):
2342 """Determine if the device under test is equipped with an internal
2343 display.
2344
2345 @return: 'internal_display' if one is present; None otherwise.
2346 """
2347 from autotest_lib.client.cros.graphics import graphics_utils
2348 from autotest_lib.client.common_lib import utils as common_utils
2349
2350 def __system_output(cmd):
2351 return self.run(cmd).stdout
2352
2353 def __read_file(remote_path):
2354 return self.run('cat %s' % remote_path).stdout
2355
2356 # Hijack the necessary client functions so that we can take advantage
2357 # of the client lib here.
2358 # FIXME: find a less hacky way than this
2359 original_system_output = utils.system_output
2360 original_read_file = common_utils.read_file
2361 utils.system_output = __system_output
2362 common_utils.read_file = __read_file
2363 try:
2364 return ('internal_display' if graphics_utils.has_internal_display()
2365 else None)
2366 finally:
2367 utils.system_output = original_system_output
2368 common_utils.read_file = original_read_file
2369
2370
Dan Shi85276d42014-04-08 22:11:45 -07002371 def is_boot_from_usb(self):
2372 """Check if DUT is boot from USB.
2373
2374 @return: True if DUT is boot from usb.
2375 """
2376 device = self.run('rootdev -s -d').stdout.strip()
2377 removable = int(self.run('cat /sys/block/%s/removable' %
2378 os.path.basename(device)).stdout.strip())
2379 return removable == 1
Helen Zhang17dae2b2014-11-11 09:25:52 -08002380
2381
2382 def read_from_meminfo(self, key):
Dan Shi49ca0932014-11-14 11:22:27 -08002383 """Return the memory info from /proc/meminfo
Helen Zhang17dae2b2014-11-11 09:25:52 -08002384
2385 @param key: meminfo requested
2386
2387 @return the memory value as a string
2388
2389 """
Helen Zhang17dae2b2014-11-11 09:25:52 -08002390 meminfo = self.run('grep %s /proc/meminfo' % key).stdout.strip()
2391 logging.debug('%s', meminfo)
2392 return int(re.search(r'\d+', meminfo).group(0))
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002393
2394
Rohit Makasana98e696f2016-06-03 18:48:10 -07002395 def get_cpu_arch(self):
2396 """Returns CPU arch of the device.
2397
2398 @return CPU architecture of the DUT.
2399 """
Allen Li2c32d6b2017-02-03 15:28:10 -08002400 # Add CPUs by following logic in client/bin/utils.py.
Rohit Makasana98e696f2016-06-03 18:48:10 -07002401 if self.run("grep '^flags.*:.* lm .*' /proc/cpuinfo",
2402 ignore_status=True).stdout:
2403 return 'x86_64'
2404 if self.run("grep -Ei 'ARM|CPU implementer' /proc/cpuinfo",
2405 ignore_status=True).stdout:
2406 return 'arm'
2407 return 'i386'
2408
2409
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002410 def get_board_type(self):
2411 """
2412 Get the DUT's device type from /etc/lsb-release.
Danny Chan471a8d12015-08-18 14:57:41 -07002413 DEVICETYPE can be one of CHROMEBOX, CHROMEBASE, CHROMEBOOK or more.
2414
2415 @return value of DEVICETYPE param from lsb-release.
Rohit Makasana8a4923c2015-08-13 17:04:26 -07002416 """
Danny Chan471a8d12015-08-18 14:57:41 -07002417 device_type = self.run('grep DEVICETYPE /etc/lsb-release',
2418 ignore_status=True).stdout
2419 if device_type:
Kalin Stoyanov524310b2015-08-21 16:24:04 -07002420 return device_type.split('=')[-1].strip()
Danny Chan471a8d12015-08-18 14:57:41 -07002421 return ''
Gilad Arnolda76bef02015-09-29 13:55:15 -07002422
2423
Rohit Makasanadf0a3a32017-06-30 13:55:18 -07002424 def get_arc_version(self):
2425 """Return ARC version installed on the DUT.
2426
2427 @returns ARC version as string if the CrOS build has ARC, else None.
2428 """
2429 arc_version = self.run('grep CHROMEOS_ARC_VERSION /etc/lsb-release',
2430 ignore_status=True).stdout
2431 if arc_version:
2432 return arc_version.split('=')[-1].strip()
2433 return None
2434
2435
Gilad Arnolda76bef02015-09-29 13:55:15 -07002436 def get_os_type(self):
2437 return 'cros'
Simran Basia5522a32015-10-06 11:01:24 -07002438
2439
2440 def enable_adb_testing(self):
2441 """Mark this host as an adb tester."""
Dan Shia2872172015-10-31 01:16:51 -07002442 self.run('touch %s' % constants.ANDROID_TESTER_FILEFLAG)
Kevin Chenga2619dc2016-03-28 11:42:08 -07002443
2444
2445 def get_labels(self):
Kevin Chengf8660142016-08-12 10:17:41 -07002446 """Return the detected labels on the host."""
Kevin Chenga2619dc2016-03-28 11:42:08 -07002447 return self.labels.get_labels(self)
Kevin Chengf8660142016-08-12 10:17:41 -07002448
2449
2450 def update_labels(self):
2451 """Update the labels on the host."""
2452 self.labels.update_labels(self)