blob: 074d5bd296166d80bc98a6d075b167cc043ddc4d [file] [log] [blame]
J. Richard Barnette24adbf42012-04-11 15:04:53 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Dale Curtisaa5eedb2011-08-23 16:18:52 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Aviv Keshet74c89a92013-02-04 15:18:30 -08005import functools
Christopher Wiley0ed712b2013-04-09 15:25:12 -07006import httplib
J. Richard Barnette1d78b012012-05-15 13:56:30 -07007import logging
Dan Shi0f466e82013-02-22 15:44:58 -08008import os
Simran Basid5e5e272012-09-24 15:23:59 -07009import re
Christopher Wileyd78249a2013-03-01 13:05:31 -080010import socket
J. Richard Barnette1d78b012012-05-15 13:56:30 -070011import subprocess
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070012import time
J. Richard Barnette1d78b012012-05-15 13:56:30 -070013import xmlrpclib
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070014
J. Richard Barnette45e93de2012-04-11 17:24:15 -070015from autotest_lib.client.bin import utils
Richard Barnette0c73ffc2012-11-19 15:21:18 -080016from autotest_lib.client.common_lib import error
17from autotest_lib.client.common_lib import global_config
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
Christopher Wileyd78249a2013-03-01 13:05:31 -080020from autotest_lib.client.common_lib.cros import retry
Richard Barnette82c35912012-11-20 10:09:10 -080021from autotest_lib.client.cros import constants
J. Richard Barnette84890bd2014-02-21 11:05:47 -080022from autotest_lib.client.cros import cros_ui
J. Richard Barnette45e93de2012-04-11 17:24:15 -070023from autotest_lib.server import autoserv_parser
Chris Sosaf4d43ff2012-10-30 11:21:05 -070024from autotest_lib.server import autotest
Dan Shia1ecd5c2013-06-06 11:21:31 -070025from autotest_lib.server import utils as server_utils
Scott Zawalski89c44dd2013-02-26 09:28:02 -050026from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070027from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
Fang Deng96667ca2013-08-01 17:46:18 -070028from autotest_lib.server.hosts import abstract_ssh
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +080029from autotest_lib.server.hosts import chameleon_host
Fang Deng5d518f42013-08-02 14:04:32 -070030from autotest_lib.server.hosts import servo_host
beeps687243d2013-07-18 15:29:27 -070031from autotest_lib.site_utils.graphite import stats
Simran Basidcff4252012-11-20 16:13:20 -080032from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070033
34
beeps32a63082013-08-22 14:02:29 -070035try:
36 import jsonrpclib
37except ImportError:
38 jsonrpclib = None
Fang Deng96667ca2013-08-01 17:46:18 -070039
Fang Dengd1c2b732013-08-20 12:59:46 -070040
beepsc87ff602013-07-31 21:53:00 -070041class FactoryImageCheckerException(error.AutoservError):
42 """Exception raised when an image is a factory image."""
43 pass
44
45
Aviv Keshet74c89a92013-02-04 15:18:30 -080046def add_label_detector(label_function_list, label_list=None, label=None):
47 """Decorator used to group functions together into the provided list.
48 @param label_function_list: List of label detecting functions to add
49 decorated function to.
50 @param label_list: List of detectable labels to add detectable labels to.
51 (Default: None)
52 @param label: Label string that is detectable by this detection function
53 (Default: None)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -080054 """
Simran Basic6f1f7a2012-10-16 10:47:46 -070055 def add_func(func):
Aviv Keshet74c89a92013-02-04 15:18:30 -080056 """
57 @param func: The function to be added as a detector.
58 """
59 label_function_list.append(func)
60 if label and label_list is not None:
61 label_list.append(label)
Simran Basic6f1f7a2012-10-16 10:47:46 -070062 return func
63 return add_func
64
65
Fang Deng0ca40e22013-08-27 17:47:44 -070066class CrosHost(abstract_ssh.AbstractSSHHost):
J. Richard Barnette45e93de2012-04-11 17:24:15 -070067 """Chromium OS specific subclass of Host."""
68
69 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -050070 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070071
Richard Barnette03a0c132012-11-05 12:40:35 -080072 # Timeout values (in seconds) associated with various Chrome OS
73 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070074 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080075 # In general, a good rule of thumb is that the timeout can be up
76 # to twice the typical measured value on the slowest platform.
77 # The times here have not necessarily been empirically tested to
78 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070079 #
80 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080081 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
82 # time to restart the netwowrk.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080083 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070084 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080085 # other things, this must account for the 30 second dev-mode
J. Richard Barnetted4649c62013-03-06 17:42:27 -080086 # screen delay and time to start the network.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070087 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080088 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -080089 # network.
beepsf079cfb2013-09-18 17:49:51 -070090 # INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnette84890bd2014-02-21 11:05:47 -080091 # POWERWASH_BOOT_TIMEOUT: Time to allow for a reboot that
92 # includes powerwash.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070093
94 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -080095 RESUME_TIMEOUT = 10
Tom Wai-Hong Tam4d169ed2014-02-14 11:05:40 +080096 SHUTDOWN_TIMEOUT = 5
J. Richard Barnettefbcc7122013-07-24 18:24:59 -070097 BOOT_TIMEOUT = 60
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070098 USB_BOOT_TIMEOUT = 150
J. Richard Barnette84890bd2014-02-21 11:05:47 -080099 INSTALL_TIMEOUT = 240
Dan Shi2c88eed2013-11-12 10:18:38 -0800100 POWERWASH_BOOT_TIMEOUT = 60
Chris Sosab76e0ee2013-05-22 16:55:41 -0700101
J. Richard Barnette84890bd2014-02-21 11:05:47 -0800102 # REBOOT_TIMEOUT: How long to wait for a reboot.
103 #
Chris Sosab76e0ee2013-05-22 16:55:41 -0700104 # We have a long timeout to ensure we don't flakily fail due to other
105 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
Simran Basi1160e2c2013-10-04 16:00:24 -0700106 # TODO(sbasi - crbug.com/276094) Restore to 5 mins once the 'host did not
107 # return from reboot' bug is solved.
108 REBOOT_TIMEOUT = 480
Chris Sosab76e0ee2013-05-22 16:55:41 -0700109
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800110 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
111 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
112 _USB_POWER_TIMEOUT = 5
113 _POWER_CYCLE_TIMEOUT = 10
114
beeps32a63082013-08-22 14:02:29 -0700115 _RPC_PROXY_URL = 'http://localhost:%d'
Christopher Wileydd181852013-10-10 19:56:58 -0700116 _RPC_SHUTDOWN_POLLING_PERIOD_SECONDS = 2
117 _RPC_SHUTDOWN_TIMEOUT_SECONDS = 20
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800118
Richard Barnette82c35912012-11-20 10:09:10 -0800119 _RPM_RECOVERY_BOARDS = global_config.global_config.get_config_value('CROS',
120 'rpm_recovery_boards', type=str).split(',')
121
122 _MAX_POWER_CYCLE_ATTEMPTS = 6
123 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
124 _RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
125 'host[0-9]+')
126 _LIGHTSENSOR_FILES = ['in_illuminance0_input',
127 'in_illuminance0_raw',
128 'illuminance0_input']
129 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
130 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800131 _DETECTABLE_LABELS = []
132 label_decorator = functools.partial(add_label_detector, _LABEL_FUNCTIONS,
133 _DETECTABLE_LABELS)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700134
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800135 # Constants used in ping_wait_up() and ping_wait_down().
136 #
137 # _PING_WAIT_COUNT is the approximate number of polling
138 # cycles to use when waiting for a host state change.
139 #
140 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
141 # for arguments to the internal _ping_wait_for_status()
142 # method.
143 _PING_WAIT_COUNT = 40
144 _PING_STATUS_DOWN = False
145 _PING_STATUS_UP = True
146
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800147 # Allowed values for the power_method argument.
148
149 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
150 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
151 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
152 POWER_CONTROL_RPM = 'RPM'
153 POWER_CONTROL_SERVO = 'servoj10'
154 POWER_CONTROL_MANUAL = 'manual'
155
156 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
157 POWER_CONTROL_SERVO,
158 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800159
Simran Basi5e6339a2013-03-21 11:34:32 -0700160 _RPM_OUTLET_CHANGED = 'outlet_changed'
161
beeps687243d2013-07-18 15:29:27 -0700162
J. Richard Barnette964fba02012-10-24 17:34:29 -0700163 @staticmethod
beeps46dadc92013-11-07 14:07:10 -0800164 def check_host(host, timeout=10):
165 """
166 Check if the given host is a chrome-os host.
167
168 @param host: An ssh host representing a device.
169 @param timeout: The timeout for the run command.
170
171 @return: True if the host device is chromeos.
172
beeps46dadc92013-11-07 14:07:10 -0800173 """
174 try:
Christopher Wiley1ea80942014-02-26 16:45:08 -0800175 result = host.run('grep -q CHROMEOS /etc/lsb-release && '
176 '! which adb >/dev/null 2>&1',
Christopher Wileyfc3eac02013-11-21 16:24:57 -0800177 ignore_status=True, timeout=timeout)
beeps46dadc92013-11-07 14:07:10 -0800178 except (error.AutoservRunError, error.AutoservSSHTimeout):
179 return False
180 return result.exit_status == 0
181
182
183 @staticmethod
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800184 def _extract_arguments(args_dict, key_subset):
185 """Extract options from `args_dict` and return a subset result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800186
187 Take the provided dictionary of argument options and return
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800188 a subset that represent standard arguments needed to construct
189 a test-assistant object (chameleon or servo) for a host. The
190 intent is to provide standard argument processing from
191 run_remote_tests for tests that require a test-assistant board
192 to operate.
193
194 @param args_dict Dictionary from which to extract the arguments.
195 @param key_subset Tuple of keys to extract from the args_dict, e.g.
196 ('servo_host', 'servo_port').
197 """
198 result = {}
199 for arg in key_subset:
200 if arg in args_dict:
201 result[arg] = args_dict[arg]
202 return result
203
204
205 @staticmethod
206 def get_chameleon_arguments(args_dict):
207 """Extract chameleon options from `args_dict` and return the result.
208
209 Recommended usage:
210 ~~~~~~~~
211 args_dict = utils.args_to_dict(args)
212 chameleon_args = hosts.CrosHost.get_chameleon_arguments(args_dict)
213 host = hosts.create_host(machine, chameleon_args=chameleon_args)
214 ~~~~~~~~
215
216 @param args_dict Dictionary from which to extract the chameleon
217 arguments.
218 """
219 return CrosHost._extract_arguments(
220 args_dict, ('chameleon_host', 'chameleon_port'))
221
222
223 @staticmethod
224 def get_servo_arguments(args_dict):
225 """Extract servo options from `args_dict` and return the result.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800226
227 Recommended usage:
228 ~~~~~~~~
229 args_dict = utils.args_to_dict(args)
Fang Deng0ca40e22013-08-27 17:47:44 -0700230 servo_args = hosts.CrosHost.get_servo_arguments(args_dict)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800231 host = hosts.create_host(machine, servo_args=servo_args)
232 ~~~~~~~~
233
234 @param args_dict Dictionary from which to extract the servo
235 arguments.
236 """
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800237 return CrosHost._extract_arguments(
238 args_dict, ('servo_host', 'servo_port'))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700239
J. Richard Barnette964fba02012-10-24 17:34:29 -0700240
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800241 def _initialize(self, hostname, chameleon_args=None, servo_args=None,
242 ssh_verbosity_flag='', ssh_options='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700243 *args, **dargs):
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800244 """Initialize superclasses, |self.chameleon|, and |self.servo|.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700245
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800246 This method checks whether a chameleon/servo (aka
247 test-assistant objects) is required by checking whether
248 chameleon_args/servo_args is None. This method will only
249 attempt to create the test-assistant object when it is
250 required by the test.
Fang Deng5d518f42013-08-02 14:04:32 -0700251
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800252 For creating the test-assistant object, there are three
253 possibilities: First, if the host is a lab system known to have
254 a test-assistant board, we connect to that board unconditionally.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700255 Second, if we're called from a control file that requires
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800256 test-assistant features for testing, it will pass settings from
257 the arguments, like `servo_host`, `servo_port`. If neither of
258 these cases apply, the test-assistant object will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700259
260 """
Fang Deng0ca40e22013-08-27 17:47:44 -0700261 super(CrosHost, self)._initialize(hostname=hostname,
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700262 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700263 # self.env is a dictionary of environment variable settings
264 # to be exported for commands run on the host.
265 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
266 # errors that might happen.
267 self.env['LIBC_FATAL_STDERR_'] = '1'
beeps32a63082013-08-22 14:02:29 -0700268 self._rpc_proxy_map = {}
Fang Dengd1c2b732013-08-20 12:59:46 -0700269 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700270 self._ssh_options = ssh_options
Fang Deng5d518f42013-08-02 14:04:32 -0700271 self.servo = None
272 # TODO(fdeng): We need to simplify the
273 # process of servo and servo_host initialization.
274 # crbug.com/298432
275 self._servo_host = self._create_servo_host(servo_args)
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800276 # TODO(waihong): Do the simplication on Chameleon too.
277 self._chameleon_host = self._create_chameleon_host(chameleon_args)
Fang Deng5d518f42013-08-02 14:04:32 -0700278 # TODO(fdeng): 'servo_args is not None' is used to determine whether
279 # a test needs a servo. Better solution is needed.
280 # There are three possible cases here:
281 # 1. servo_arg is None
282 # 2. servo arg is an empty dictionary
283 # 3. servo_arg is a dictionary that has entries of 'servo_host',
284 # 'servo_port'(optional).
285 # We assume that:
286 # a. A test that requires a servo always calls get_servo_arguments
287 # and passes in its return value as |servo_args|.
288 # b. get_servo_arguments never returns None.
289 # Based on the assumptions, we reason that only in case 2 and 3
290 # a servo is required, i.e. when the servo_args is not None.
291 if servo_args is not None:
292 self.servo = self._servo_host.create_healthy_servo_object()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800293 if chameleon_args is not None:
Tom Wai-Hong Tameaee3402014-01-22 08:52:10 +0800294 self.chameleon = self._chameleon_host.create_chameleon_board()
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800295
296
297 def _create_chameleon_host(self, chameleon_args):
298 """Create a ChameleonHost object.
299
300 There three possible cases:
301 1) If the DUT is in Cros Lab and has a chameleon board, then create
302 a ChameleonHost object pointing to the board. chameleon_args
303 is ignored.
304 2) If not case 1) and chameleon_args is neither None nor empty, then
305 create a ChameleonHost object using chameleon_args.
306 3) If neither case 1) or 2) applies, return None.
307
308 @param chameleon_args: A dictionary that contains args for creating
309 a ChameleonHost object,
310 e.g. {'chameleon_host': '172.11.11.112',
311 'chameleon_port': 9992}.
312
313 @returns: A ChameleonHost object or None.
314
315 """
316 hostname = chameleon_host.make_chameleon_hostname(self.hostname)
317 if utils.host_is_in_lab_zone(hostname):
318 return chameleon_host.ChameleonHost(chameleon_host=hostname)
319 elif chameleon_args is not None:
320 return chameleon_host.ChameleonHost(**chameleon_args)
321 else:
322 return None
Fang Deng5d518f42013-08-02 14:04:32 -0700323
324
325 def _create_servo_host(self, servo_args):
326 """Create a ServoHost object.
327
328 There three possible cases:
329 1) If the DUT is in Cros Lab and has a beaglebone and a servo, then
330 create a ServoHost object pointing to the beaglebone. servo_args
331 is ignored.
332 2) If not case 1) and servo_args is neither None nor empty, then
333 create a ServoHost object using servo_args.
334 3) If neither case 1) or 2) applies, return None.
335
336 @param servo_args: A dictionary that contains args for creating
337 a ServoHost object,
338 e.g. {'servo_host': '172.11.11.111',
339 'servo_port': 9999}.
340 See comments above.
341
342 @returns: A ServoHost object or None. See comments above.
343
344 """
Tom Wai-Hong Tamefe1c7f2014-01-02 14:00:11 +0800345 hostname = servo_host.make_servo_hostname(self.hostname)
346 if utils.host_is_in_lab_zone(hostname):
347 return servo_host.ServoHost(servo_host=hostname)
Fang Deng5d518f42013-08-02 14:04:32 -0700348 elif servo_args is not None:
349 return servo_host.ServoHost(**servo_args)
350 else:
351 return None
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700352
353
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500354 def get_repair_image_name(self):
355 """Generate a image_name from variables in the global config.
356
357 @returns a str of $board-version/$BUILD.
358
359 """
360 stable_version = global_config.global_config.get_config_value(
361 'CROS', 'stable_cros_version')
362 build_pattern = global_config.global_config.get_config_value(
363 'CROS', 'stable_build_pattern')
364 board = self._get_board_from_afe()
365 if board is None:
366 raise error.AutoservError('DUT has no board attribute, '
367 'cannot be repaired.')
368 return build_pattern % (board, stable_version)
369
370
Scott Zawalski62bacae2013-03-05 10:40:32 -0500371 def _host_in_AFE(self):
372 """Check if the host is an object the AFE knows.
373
374 @returns the host object.
375 """
376 return self._AFE.get_hosts(hostname=self.hostname)
377
378
Chris Sosab76e0ee2013-05-22 16:55:41 -0700379 def lookup_job_repo_url(self):
380 """Looks up the job_repo_url for the host.
381
382 @returns job_repo_url from AFE or None if not found.
383
384 @raises KeyError if the host does not have a job_repo_url
385 """
386 if not self._host_in_AFE():
387 return None
388
389 hosts = self._AFE.get_hosts(hostname=self.hostname)
beepsb5efc532013-06-04 11:29:34 -0700390 if hosts and ds_constants.JOB_REPO_URL in hosts[0].attributes:
391 return hosts[0].attributes[ds_constants.JOB_REPO_URL]
Chris Sosab76e0ee2013-05-22 16:55:41 -0700392
393
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500394 def clear_cros_version_labels_and_job_repo_url(self):
395 """Clear cros_version labels and host attribute job_repo_url."""
Scott Zawalski62bacae2013-03-05 10:40:32 -0500396 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400397 return
398
Scott Zawalski62bacae2013-03-05 10:40:32 -0500399 host_list = [self.hostname]
400 labels = self._AFE.get_labels(
401 name__startswith=ds_constants.VERSION_PREFIX,
402 host__hostname=self.hostname)
Dan Shi0f466e82013-02-22 15:44:58 -0800403
Scott Zawalski62bacae2013-03-05 10:40:32 -0500404 for label in labels:
405 label.remove_hosts(hosts=host_list)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500406
beepscb6f1e22013-06-28 19:14:10 -0700407 self.update_job_repo_url(None, None)
408
409
410 def update_job_repo_url(self, devserver_url, image_name):
411 """
412 Updates the job_repo_url host attribute and asserts it's value.
413
414 @param devserver_url: The devserver to use in the job_repo_url.
415 @param image_name: The name of the image to use in the job_repo_url.
416
417 @raises AutoservError: If we failed to update the job_repo_url.
418 """
419 repo_url = None
420 if devserver_url and image_name:
421 repo_url = tools.get_package_url(devserver_url, image_name)
422 self._AFE.set_host_attribute(ds_constants.JOB_REPO_URL, repo_url,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500423 hostname=self.hostname)
beepscb6f1e22013-06-28 19:14:10 -0700424 if self.lookup_job_repo_url() != repo_url:
425 raise error.AutoservError('Failed to update job_repo_url with %s, '
426 'host %s' % (repo_url, self.hostname))
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500427
428
Dan Shie9309262013-06-19 22:50:21 -0700429 def add_cros_version_labels_and_job_repo_url(self, image_name):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400430 """Add cros_version labels and host attribute job_repo_url.
431
432 @param image_name: The name of the image e.g.
433 lumpy-release/R27-3837.0.0
Dan Shi7458bf62013-06-10 12:50:16 -0700434
Scott Zawalskieadbf702013-03-14 09:23:06 -0400435 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500436 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400437 return
Scott Zawalski62bacae2013-03-05 10:40:32 -0500438
Scott Zawalskieadbf702013-03-14 09:23:06 -0400439 cros_label = '%s%s' % (ds_constants.VERSION_PREFIX, image_name)
Dan Shie9309262013-06-19 22:50:21 -0700440 devserver_url = dev_server.ImageServer.resolve(image_name).url()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500441
442 labels = self._AFE.get_labels(name=cros_label)
443 if labels:
444 label = labels[0]
445 else:
446 label = self._AFE.create_label(name=cros_label)
447
448 label.add_hosts([self.hostname])
beepscb6f1e22013-06-28 19:14:10 -0700449 self.update_job_repo_url(devserver_url, image_name)
450
451
beepsdae65fd2013-07-26 16:24:41 -0700452 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700453 """
454 Make sure job_repo_url of this host is valid.
455
joychen03eaad92013-06-26 09:55:21 -0700456 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700457 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
458 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
459 download and extract it. If the devserver embedded in the url is
460 unresponsive, update the job_repo_url of the host after staging it on
461 another devserver.
462
463 @param job_repo_url: A url pointing to the devserver where the autotest
464 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700465 @param tag: The tag from the server job, in the format
466 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700467
468 @raises DevServerException: If we could not resolve a devserver.
469 @raises AutoservError: If we're unable to save the new job_repo_url as
470 a result of choosing a new devserver because the old one failed to
471 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700472 @raises urllib2.URLError: If the devserver embedded in job_repo_url
473 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700474 """
475 job_repo_url = self.lookup_job_repo_url()
476 if not job_repo_url:
477 logging.warning('No job repo url set on host %s', self.hostname)
478 return
479
480 logging.info('Verifying job repo url %s', job_repo_url)
481 devserver_url, image_name = tools.get_devserver_build_from_package_url(
482 job_repo_url)
483
beeps0c865032013-07-30 11:37:06 -0700484 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700485
486 logging.info('Staging autotest artifacts for %s on devserver %s',
487 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700488
489 start_time = time.time()
beepscb6f1e22013-06-28 19:14:10 -0700490 ds.stage_artifacts(image_name, ['autotest'])
beeps687243d2013-07-18 15:29:27 -0700491 stage_time = time.time() - start_time
492
493 # Record how much of the verification time comes from a devserver
494 # restage. If we're doing things right we should not see multiple
495 # devservers for a given board/build/branch path.
496 try:
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800497 board, build_type, branch = server_utils.ParseBuildName(
beeps687243d2013-07-18 15:29:27 -0700498 image_name)[:3]
J. Richard Barnette3cbd76b2013-11-27 12:11:25 -0800499 except server_utils.ParseBuildNameException:
beeps687243d2013-07-18 15:29:27 -0700500 pass
501 else:
beeps0c865032013-07-30 11:37:06 -0700502 devserver = devserver_url[
Chris Sosa65425082013-10-16 13:26:22 -0700503 devserver_url.find('/') + 2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700504 stats_key = {
505 'board': board,
506 'build_type': build_type,
507 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700508 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700509 }
510 stats.Gauge('verify_job_repo_url').send(
511 '%(board)s.%(build_type)s.%(branch)s.%(devserver)s' % stats_key,
512 stage_time)
beepscb6f1e22013-06-28 19:14:10 -0700513
Scott Zawalskieadbf702013-03-14 09:23:06 -0400514
Dan Shi0f466e82013-02-22 15:44:58 -0800515 def _try_stateful_update(self, update_url, force_update, updater):
516 """Try to use stateful update to initialize DUT.
517
518 When DUT is already running the same version that machine_install
519 tries to install, stateful update is a much faster way to clean up
520 the DUT for testing, compared to a full reimage. It is implemeted
521 by calling autoupdater.run_update, but skipping updating root, as
522 updating the kernel is time consuming and not necessary.
523
524 @param update_url: url of the image.
525 @param force_update: Set to True to update the image even if the DUT
526 is running the same version.
527 @param updater: ChromiumOSUpdater instance used to update the DUT.
528 @returns: True if the DUT was updated with stateful update.
529
530 """
531 if not updater.check_version():
532 return False
533 if not force_update:
534 logging.info('Canceling stateful update because the new and '
535 'old versions are the same.')
536 return False
537 # Following folders should be rebuilt after stateful update.
538 # A test file is used to confirm each folder gets rebuilt after
539 # the stateful update.
540 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
541 test_file = '.test_file_to_be_deleted'
542 for folder in folders_to_check:
543 touch_path = os.path.join(folder, test_file)
544 self.run('touch %s' % touch_path)
545
546 if not updater.run_update(force_update=True, update_root=False):
547 return False
548
549 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700550 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800551 check_file_cmd = 'test -f %s; echo $?'
552 for folder in folders_to_check:
553 test_file_path = os.path.join(folder, test_file)
554 result = self.run(check_file_cmd % test_file_path,
555 ignore_status=True)
556 if result.exit_status == 1:
557 return False
558 return True
559
560
J. Richard Barnette7275b612013-06-04 18:13:11 -0700561 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800562 """After the DUT is updated, confirm machine_install succeeded.
563
564 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700565 @param expected_kernel: kernel expected to be active after reboot,
566 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800567
568 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700569 # Touch the lab machine file to leave a marker that
570 # distinguishes this image from other test images.
571 # Afterwards, we must re-run the autoreboot script because
572 # it depends on the _LAB_MACHINE_FILE.
Dan Shi0f466e82013-02-22 15:44:58 -0800573 self.run('touch %s' % self._LAB_MACHINE_FILE)
Dan Shi0f466e82013-02-22 15:44:58 -0800574 self.run('start autoreboot')
Chris Sosa65425082013-10-16 13:26:22 -0700575 updater.verify_boot_expectations(
576 expected_kernel, rollback_message=
577 'Build %s failed to boot on %s; system rolled back to previous'
578 'build' % (updater.update_version, self.hostname))
J. Richard Barnette7275b612013-06-04 18:13:11 -0700579 # Check that we've got the build we meant to install.
580 if not updater.check_version_to_confirm_install():
581 raise autoupdater.ChromiumOSError(
582 'Failed to update %s to build %s; found build '
583 '%s instead' % (self.hostname,
Chris Sosa65425082013-10-16 13:26:22 -0700584 updater.update_version,
585 updater.get_build_id()))
Dan Shi0f466e82013-02-22 15:44:58 -0800586
587
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700588 def _stage_image_for_update(self, image_name=None):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400589 """Stage a build on a devserver and return the update_url.
590
591 @param image_name: a name like lumpy-release/R27-3837.0.0
592 @returns an update URL like:
593 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
594 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700595 if not image_name:
596 image_name = self.get_repair_image_name()
597 logging.info('Staging build for AU: %s', image_name)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400598 devserver = dev_server.ImageServer.resolve(image_name)
599 devserver.trigger_download(image_name, synchronous=False)
600 return tools.image_url_pattern() % (devserver.url(), image_name)
601
602
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700603 def stage_image_for_servo(self, image_name=None):
604 """Stage a build on a devserver and return the update_url.
605
606 @param image_name: a name like lumpy-release/R27-3837.0.0
607 @returns an update URL like:
608 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
609 """
610 if not image_name:
611 image_name = self.get_repair_image_name()
612 logging.info('Staging build for servo install: %s', image_name)
613 devserver = dev_server.ImageServer.resolve(image_name)
614 devserver.stage_artifacts(image_name, ['test_image'])
615 return devserver.get_test_image_url(image_name)
616
617
beepse539be02013-07-31 21:57:39 -0700618 def stage_factory_image_for_servo(self, image_name):
619 """Stage a build on a devserver and return the update_url.
620
621 @param image_name: a name like <baord>/4262.204.0
beeps12c0a3c2013-09-03 11:58:27 -0700622
beepse539be02013-07-31 21:57:39 -0700623 @return: An update URL, eg:
624 http://<devserver>/static/canary-channel/\
625 <board>/4262.204.0/factory_test/chromiumos_factory_image.bin
beeps12c0a3c2013-09-03 11:58:27 -0700626
627 @raises: ValueError if the factory artifact name is missing from
628 the config.
629
beepse539be02013-07-31 21:57:39 -0700630 """
631 if not image_name:
632 logging.error('Need an image_name to stage a factory image.')
633 return
634
beeps12c0a3c2013-09-03 11:58:27 -0700635 factory_artifact = global_config.global_config.get_config_value(
636 'CROS', 'factory_artifact', type=str, default='')
637 if not factory_artifact:
638 raise ValueError('Cannot retrieve the factory artifact name from '
639 'autotest config, and hence cannot stage factory '
640 'artifacts.')
641
beepse539be02013-07-31 21:57:39 -0700642 logging.info('Staging build for servo install: %s', image_name)
643 devserver = dev_server.ImageServer.resolve(image_name)
644 devserver.stage_artifacts(
645 image_name,
beeps12c0a3c2013-09-03 11:58:27 -0700646 [factory_artifact],
647 archive_url=None)
beepse539be02013-07-31 21:57:39 -0700648
649 return tools.factory_image_url_pattern() % (devserver.url(), image_name)
650
651
Chris Sosaa3ac2152012-05-23 22:23:13 -0700652 def machine_install(self, update_url=None, force_update=False,
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500653 local_devserver=False, repair=False):
654 """Install the DUT.
655
Dan Shi0f466e82013-02-22 15:44:58 -0800656 Use stateful update if the DUT is already running the same build.
657 Stateful update does not update kernel and tends to run much faster
658 than a full reimage. If the DUT is running a different build, or it
659 failed to do a stateful update, full update, including kernel update,
660 will be applied to the DUT.
661
Scott Zawalskieadbf702013-03-14 09:23:06 -0400662 Once a host enters machine_install its cros_version label will be
663 removed as well as its host attribute job_repo_url (used for
664 package install).
665
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500666 @param update_url: The url to use for the update
667 pattern: http://$devserver:###/update/$build
668 If update_url is None and repair is True we will install the
669 stable image listed in global_config under
670 CROS.stable_cros_version.
671 @param force_update: Force an update even if the version installed
672 is the same. Default:False
673 @param local_devserver: Used by run_remote_test to allow people to
674 use their local devserver. Default: False
675 @param repair: Whether or not we are in repair mode. This adds special
676 cases for repairing a machine like starting update_engine.
677 Setting repair to True sets force_update to True as well.
678 default: False
679 @raises autoupdater.ChromiumOSError
680
681 """
Dan Shi7458bf62013-06-10 12:50:16 -0700682 if update_url:
683 logging.debug('update url is set to %s', update_url)
684 else:
685 logging.debug('update url is not set, resolving...')
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700686 if self._parser.options.image:
687 requested_build = self._parser.options.image
688 if requested_build.startswith('http://'):
689 update_url = requested_build
Dan Shi7458bf62013-06-10 12:50:16 -0700690 logging.debug('update url is retrieved from requested_build'
691 ': %s', update_url)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700692 else:
693 # Try to stage any build that does not start with
694 # http:// on the devservers defined in
695 # global_config.ini.
Dan Shi7458bf62013-06-10 12:50:16 -0700696 update_url = self._stage_image_for_update(requested_build)
697 logging.debug('Build staged, and update_url is set to: %s',
698 update_url)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700699 elif repair:
700 update_url = self._stage_image_for_update()
Dan Shi7458bf62013-06-10 12:50:16 -0700701 logging.debug('Build staged, and update_url is set to: %s',
702 update_url)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400703 else:
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700704 raise autoupdater.ChromiumOSError(
705 'Update failed. No update URL provided.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500706
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500707 if repair:
Dan Shi0f466e82013-02-22 15:44:58 -0800708 # In case the system is in a bad state, we always reboot the machine
709 # before machine_install.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700710 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500711 self.run('stop update-engine; start update-engine')
712 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800713
Chris Sosaa3ac2152012-05-23 22:23:13 -0700714 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
Chris Sosa72312602013-04-16 15:01:56 -0700715 local_devserver=local_devserver)
Dan Shi0f466e82013-02-22 15:44:58 -0800716 updated = False
Scott Zawalskieadbf702013-03-14 09:23:06 -0400717 # Remove cros-version and job_repo_url host attribute from host.
718 self.clear_cros_version_labels_and_job_repo_url()
Dan Shi0f466e82013-02-22 15:44:58 -0800719 # If the DUT is already running the same build, try stateful update
720 # first. Stateful update does not update kernel and tends to run much
721 # faster than a full reimage.
722 try:
Chris Sosab76e0ee2013-05-22 16:55:41 -0700723 updated = self._try_stateful_update(
724 update_url, force_update, updater)
Dan Shi0f466e82013-02-22 15:44:58 -0800725 if updated:
726 logging.info('DUT is updated with stateful update.')
727 except Exception as e:
728 logging.exception(e)
729 logging.warn('Failed to stateful update DUT, force to update.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700730
Dan Shi0f466e82013-02-22 15:44:58 -0800731 inactive_kernel = None
732 # Do a full update if stateful update is not applicable or failed.
733 if not updated:
734 # In case the system is in a bad state, we always reboot the
735 # machine before machine_install.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700736 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Chris Sosab7612bc2013-03-21 10:32:37 -0700737
738 # TODO(sosa): Remove temporary hack to get rid of bricked machines
739 # that can't update due to a corrupted policy.
740 self.run('rm -rf /var/lib/whitelist')
741 self.run('touch /var/lib/whitelist')
742 self.run('chmod -w /var/lib/whitelist')
Scott Zawalskib550d5a2013-03-22 09:23:59 -0400743 self.run('stop update-engine; start update-engine')
Chris Sosab7612bc2013-03-21 10:32:37 -0700744
Dan Shi0f466e82013-02-22 15:44:58 -0800745 if updater.run_update(force_update):
746 updated = True
747 # Figure out active and inactive kernel.
748 active_kernel, inactive_kernel = updater.get_kernel_state()
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700749
Dan Shi0f466e82013-02-22 15:44:58 -0800750 # Ensure inactive kernel has higher priority than active.
751 if (updater.get_kernel_priority(inactive_kernel)
752 < updater.get_kernel_priority(active_kernel)):
753 raise autoupdater.ChromiumOSError(
754 'Update failed. The priority of the inactive kernel'
755 ' partition is less than that of the active kernel'
756 ' partition.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700757
Dan Shi0f466e82013-02-22 15:44:58 -0800758 # Updater has returned successfully; reboot the host.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700759 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700760
Dan Shi0f466e82013-02-22 15:44:58 -0800761 if updated:
762 self._post_update_processing(updater, inactive_kernel)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400763 image_name = autoupdater.url_to_image_name(update_url)
Dan Shie9309262013-06-19 22:50:21 -0700764 self.add_cros_version_labels_and_job_repo_url(image_name)
Simran Basi13fa1ba2013-03-04 10:56:47 -0800765
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700766 # Clean up any old autotest directories which may be lying around.
767 for path in global_config.global_config.get_config_value(
768 'AUTOSERV', 'client_autodir_paths', type=list):
769 self.run('rm -rf ' + path)
770
771
Dan Shi10e992b2013-08-30 11:02:59 -0700772 def show_update_engine_log(self):
773 """Output update engine log."""
774 logging.debug('Dumping %s', constants.UPDATE_ENGINE_LOG)
775 self.run('cat %s' % constants.UPDATE_ENGINE_LOG)
776
777
Richard Barnette82c35912012-11-20 10:09:10 -0800778 def _get_board_from_afe(self):
779 """Retrieve this host's board from its labels in the AFE.
780
781 Looks for a host label of the form "board:<board>", and
782 returns the "<board>" part of the label. `None` is returned
783 if there is not a single, unique label matching the pattern.
784
785 @returns board from label, or `None`.
786 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700787 return server_utils.get_board_from_afe(self.hostname, self._AFE)
Simran Basi833814b2013-01-29 13:13:43 -0800788
789
790 def get_build(self):
791 """Retrieve the current build for this Host from the AFE.
792
793 Looks through this host's labels in the AFE to determine its build.
794
795 @returns The current build or None if it could not find it or if there
796 were multiple build labels assigned to this host.
797 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700798 return server_utils.get_build_from_afe(self.hostname, self._AFE)
Richard Barnette82c35912012-11-20 10:09:10 -0800799
800
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500801 def _install_repair(self):
802 """Attempt to repair this host using upate-engine.
803
804 If the host is up, try installing the DUT with a stable
805 "repair" version of Chrome OS as defined in the global_config
806 under CROS.stable_cros_version.
807
Scott Zawalski62bacae2013-03-05 10:40:32 -0500808 @raises AutoservRepairMethodNA if the DUT is not reachable.
809 @raises ChromiumOSError if the install failed for some reason.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500810
811 """
812 if not self.is_up():
Scott Zawalski62bacae2013-03-05 10:40:32 -0500813 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500814
815 logging.info('Attempting to reimage machine to repair image.')
816 try:
817 self.machine_install(repair=True)
Fang Dengd0672f32013-03-18 17:18:09 -0700818 except autoupdater.ChromiumOSError as e:
819 logging.exception(e)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500820 logging.info('Repair via install failed.')
Scott Zawalski62bacae2013-03-05 10:40:32 -0500821 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500822
823
Dan Shi2c88eed2013-11-12 10:18:38 -0800824 def _install_repair_with_powerwash(self):
Dan Shi9cc48452013-11-12 12:39:26 -0800825 """Attempt to powerwash first then repair this host using update-engine.
Dan Shi2c88eed2013-11-12 10:18:38 -0800826
Dan Shi9cc48452013-11-12 12:39:26 -0800827 update-engine may fail due to a bad image. In such case, powerwash
828 may help to cleanup the DUT for update-engine to work again.
Dan Shi2c88eed2013-11-12 10:18:38 -0800829
830 @raises AutoservRepairMethodNA if the DUT is not reachable.
831 @raises ChromiumOSError if the install failed for some reason.
832
833 """
834 if not self.is_up():
835 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
836
837 logging.info('Attempting to powerwash the DUT.')
838 self.run('echo "fast safe" > '
839 '/mnt/stateful_partition/factory_install_reset')
840 self.reboot(timeout=self.POWERWASH_BOOT_TIMEOUT, wait=True)
841 if not self.is_up():
Dan Shi9cc48452013-11-12 12:39:26 -0800842 logging.error('Powerwash failed. DUT did not come back after '
Dan Shi2c88eed2013-11-12 10:18:38 -0800843 'reboot.')
844 raise error.AutoservRepairFailure(
845 'DUT failed to boot from powerwash after %d seconds' %
846 self.POWERWASH_BOOT_TIMEOUT)
847
848 logging.info('Powerwash succeeded.')
849 self._install_repair()
850
851
beepsf079cfb2013-09-18 17:49:51 -0700852 def servo_install(self, image_url=None, usb_boot_timeout=USB_BOOT_TIMEOUT,
853 install_timeout=INSTALL_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500854 """
855 Re-install the OS on the DUT by:
856 1) installing a test image on a USB storage device attached to the Servo
857 board,
Richard Barnette03a0c132012-11-05 12:40:35 -0800858 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700859 3) installing the image with chromeos-install.
860
Scott Zawalski62bacae2013-03-05 10:40:32 -0500861 @param image_url: If specified use as the url to install on the DUT.
862 otherwise boot the currently staged image on the USB stick.
beepsf079cfb2013-09-18 17:49:51 -0700863 @param usb_boot_timeout: The usb_boot_timeout to use during reimage.
864 Factory images need a longer usb_boot_timeout than regular
865 cros images.
866 @param install_timeout: The timeout to use when installing the chromeos
867 image. Factory images need a longer install_timeout.
Richard Barnette03a0c132012-11-05 12:40:35 -0800868
Scott Zawalski62bacae2013-03-05 10:40:32 -0500869 @raises AutoservError if the image fails to boot.
Richard Barnette03a0c132012-11-05 12:40:35 -0800870 """
beepsf079cfb2013-09-18 17:49:51 -0700871
872 usb_boot_timer_key = ('servo_install.usb_boot_timeout_%s'
873 % usb_boot_timeout)
874 logging.info('Downloading image to USB, then booting from it. Usb boot '
875 'timeout = %s', usb_boot_timeout)
876 timer = stats.Timer(usb_boot_timer_key)
877 timer.start()
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700878 self.servo.install_recovery_image(image_url)
beepsf079cfb2013-09-18 17:49:51 -0700879 if not self.wait_up(timeout=usb_boot_timeout):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500880 raise error.AutoservRepairFailure(
881 'DUT failed to boot from USB after %d seconds' %
beepsf079cfb2013-09-18 17:49:51 -0700882 usb_boot_timeout)
883 timer.stop()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500884
beepsf079cfb2013-09-18 17:49:51 -0700885 install_timer_key = ('servo_install.install_timeout_%s'
886 % install_timeout)
887 timer = stats.Timer(install_timer_key)
888 timer.start()
889 logging.info('Installing image through chromeos-install.')
890 self.run('chromeos-install --yes', timeout=install_timeout)
891 timer.stop()
892
893 logging.info('Power cycling DUT through servo.')
Richard Barnette03a0c132012-11-05 12:40:35 -0800894 self.servo.power_long_press()
Fang Dengafb88142013-05-30 17:44:31 -0700895 self.servo.switch_usbkey('off')
J. Richard Barnettefbcc7122013-07-24 18:24:59 -0700896 # We *must* use power_on() here; on Parrot it's how we get
897 # out of recovery mode.
898 self.servo.get_power_state_controller().power_on()
beepsf079cfb2013-09-18 17:49:51 -0700899
900 logging.info('Waiting for DUT to come back up.')
Richard Barnette03a0c132012-11-05 12:40:35 -0800901 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
902 raise error.AutoservError('DUT failed to reboot installed '
903 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -0500904 self.BOOT_TIMEOUT)
905
906
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700907 def _servo_repair_reinstall(self):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500908 """Reinstall the DUT utilizing servo and a test image.
909
910 Re-install the OS on the DUT by:
911 1) installing a test image on a USB storage device attached to the Servo
912 board,
913 2) booting that image in recovery mode, and then
914 3) installing the image with chromeos-install.
915
Scott Zawalski62bacae2013-03-05 10:40:32 -0500916 @raises AutoservRepairMethodNA if the device does not have servo
917 support.
918
919 """
920 if not self.servo:
921 raise error.AutoservRepairMethodNA('Repair Reinstall NA: '
922 'DUT has no servo support.')
923
924 logging.info('Attempting to recovery servo enabled device with '
925 'servo_repair_reinstall')
926
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700927 image_url = self.stage_image_for_servo()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500928 self.servo_install(image_url)
929
930
931 def _servo_repair_power(self):
932 """Attempt to repair DUT using an attached Servo.
933
934 Attempt to power on the DUT via power_long_press.
935
936 @raises AutoservRepairMethodNA if the device does not have servo
937 support.
938 @raises AutoservRepairFailure if the repair fails for any reason.
939 """
940 if not self.servo:
941 raise error.AutoservRepairMethodNA('Repair Power NA: '
942 'DUT has no servo support.')
943
944 logging.info('Attempting to recover servo enabled device by '
945 'powering it off and on.')
946 self.servo.get_power_state_controller().power_off()
947 self.servo.get_power_state_controller().power_on()
948 if self.wait_up(self.BOOT_TIMEOUT):
949 return
950
951 raise error.AutoservRepairFailure('DUT did not boot after long_press.')
Richard Barnette03a0c132012-11-05 12:40:35 -0800952
953
Richard Barnette82c35912012-11-20 10:09:10 -0800954 def _powercycle_to_repair(self):
955 """Utilize the RPM Infrastructure to bring the host back up.
956
957 If the host is not up/repaired after the first powercycle we utilize
958 auto fallback to the last good install by powercycling and rebooting the
959 host 6 times.
Scott Zawalski62bacae2013-03-05 10:40:32 -0500960
961 @raises AutoservRepairMethodNA if the device does not support remote
962 power.
963 @raises AutoservRepairFailure if the repair fails for any reason.
964
Richard Barnette82c35912012-11-20 10:09:10 -0800965 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500966 if not self.has_power():
967 raise error.AutoservRepairMethodNA('Device does not support power.')
968
Richard Barnette82c35912012-11-20 10:09:10 -0800969 logging.info('Attempting repair via RPM powercycle.')
970 failed_cycles = 0
971 self.power_cycle()
972 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
973 failed_cycles += 1
974 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
Scott Zawalski62bacae2013-03-05 10:40:32 -0500975 raise error.AutoservRepairFailure(
976 'Powercycled host %s %d times; device did not come back'
977 ' online.' % (self.hostname, failed_cycles))
Richard Barnette82c35912012-11-20 10:09:10 -0800978 self.power_cycle()
979 if failed_cycles == 0:
980 logging.info('Powercycling was successful first time.')
981 else:
982 logging.info('Powercycling was successful after %d failures.',
983 failed_cycles)
984
985
986 def repair_full(self):
987 """Repair a host for repair level NO_PROTECTION.
988
989 This overrides the base class function for repair; it does
990 not call back to the parent class, but instead offers a
991 simplified implementation based on the capabilities in the
992 Chrome OS test lab.
993
Fang Deng5d518f42013-08-02 14:04:32 -0700994 It first verifies and repairs servo if it is a DUT in CrOS
Fang Deng03590af2013-10-07 17:34:20 -0700995 lab and a servo is attached.
Fang Deng5d518f42013-08-02 14:04:32 -0700996
J. Richard Barnettefde55fc2013-03-15 17:47:01 -0700997 If `self.verify()` fails, the following procedures are
998 attempted:
999 1. Try to re-install to a known stable image using
1000 auto-update.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001001 2. If there's a servo for the DUT, try to power the DUT off and
1002 on.
1003 3. If there's a servo for the DUT, try to re-install via
J. Richard Barnettefde55fc2013-03-15 17:47:01 -07001004 the servo.
Scott Zawalski62bacae2013-03-05 10:40:32 -05001005 4. If the DUT can be power-cycled via RPM, try to repair
Richard Barnette82c35912012-11-20 10:09:10 -08001006 by power-cycling.
1007
1008 As with the parent method, the last operation performed on
1009 the DUT must be to call `self.verify()`; if that call fails,
1010 the exception it raises is passed back to the caller.
J. Richard Barnettefde55fc2013-03-15 17:47:01 -07001011
Scott Zawalski62bacae2013-03-05 10:40:32 -05001012 @raises AutoservRepairTotalFailure if the repair process fails to
1013 fix the DUT.
Fang Deng5d518f42013-08-02 14:04:32 -07001014 @raises ServoHostRepairTotalFailure if the repair process fails to
1015 fix the servo host if one is attached to the DUT.
1016 @raises AutoservSshPermissionDeniedError if it is unable
1017 to ssh to the servo host due to permission error.
1018
Richard Barnette82c35912012-11-20 10:09:10 -08001019 """
Fang Deng5d518f42013-08-02 14:04:32 -07001020 if self._servo_host:
Fang Deng03590af2013-10-07 17:34:20 -07001021 try:
1022 self.servo = self._servo_host.create_healthy_servo_object()
1023 except Exception as e:
1024 self.servo = None
1025 logging.error('Could not create a healthy servo: %s', e)
Fang Deng5d518f42013-08-02 14:04:32 -07001026
Scott Zawalski62bacae2013-03-05 10:40:32 -05001027 # TODO(scottz): This should use something similar to label_decorator,
1028 # but needs to be populated in order so DUTs are repaired with the
1029 # least amount of effort.
Dan Shi2c88eed2013-11-12 10:18:38 -08001030 repair_funcs = [self._install_repair,
1031 self._install_repair_with_powerwash,
1032 self._servo_repair_power,
J. Richard Barnettee4af8b92013-05-01 13:16:12 -07001033 self._servo_repair_reinstall,
Scott Zawalski62bacae2013-03-05 10:40:32 -05001034 self._powercycle_to_repair]
1035 errors = []
Simran Basie6130932013-10-01 14:07:52 -07001036 board = self._get_board_from_afe()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001037 for repair_func in repair_funcs:
1038 try:
1039 repair_func()
1040 self.verify()
Simran Basie6130932013-10-01 14:07:52 -07001041 stats.Counter(
1042 '%s.SUCCEEDED' % repair_func.__name__).increment()
1043 if board:
1044 stats.Counter(
Dan Shib87c3aa2014-02-12 15:40:31 -08001045 '%s.%s.SUCCEEDED' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001046 board)).increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001047 return
Simran Basie6130932013-10-01 14:07:52 -07001048 except error.AutoservRepairMethodNA as e:
1049 stats.Counter(
1050 '%s.RepairNA' % repair_func.__name__).increment()
1051 if board:
1052 stats.Counter(
Dan Shib87c3aa2014-02-12 15:40:31 -08001053 '%s.%s.RepairNA' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001054 board)).increment()
1055 logging.warn('Repair function NA: %s', e)
1056 errors.append(str(e))
Scott Zawalski62bacae2013-03-05 10:40:32 -05001057 except Exception as e:
Simran Basie6130932013-10-01 14:07:52 -07001058 stats.Counter(
1059 '%s.FAILED' % repair_func.__name__).increment()
1060 if board:
1061 stats.Counter(
Dan Shib87c3aa2014-02-12 15:40:31 -08001062 '%s.%s.FAILED' % (repair_func.__name__,
Simran Basie6130932013-10-01 14:07:52 -07001063 board)).increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001064 logging.warn('Failed to repair device: %s', e)
1065 errors.append(str(e))
Scott Zawalski89c44dd2013-02-26 09:28:02 -05001066
Simran Basie6130932013-10-01 14:07:52 -07001067 stats.Counter('Full_Repair_Failed').increment()
1068 if board:
1069 stats.Counter(
1070 'Full_Repair_Failed.%s' % board).increment()
Scott Zawalski62bacae2013-03-05 10:40:32 -05001071 raise error.AutoservRepairTotalFailure(
1072 'All attempts at repairing the device failed:\n%s' %
1073 '\n'.join(errors))
Richard Barnette82c35912012-11-20 10:09:10 -08001074
1075
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001076 def close(self):
beeps32a63082013-08-22 14:02:29 -07001077 self.rpc_disconnect_all()
Fang Deng0ca40e22013-08-27 17:47:44 -07001078 super(CrosHost, self).close()
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001079
1080
Simran Basi5e6339a2013-03-21 11:34:32 -07001081 def _cleanup_poweron(self):
1082 """Special cleanup method to make sure hosts always get power back."""
1083 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1084 hosts = afe.get_hosts(hostname=self.hostname)
1085 if not hosts or not (self._RPM_OUTLET_CHANGED in
1086 hosts[0].attributes):
1087 return
1088 logging.debug('This host has recently interacted with the RPM'
1089 ' Infrastructure. Ensuring power is on.')
1090 try:
1091 self.power_on()
1092 except rpm_client.RemotePowerException:
1093 # If cleanup has completed but there was an issue with the RPM
1094 # Infrastructure, log an error message rather than fail cleanup
1095 logging.error('Failed to turn Power On for this host after '
1096 'cleanup through the RPM Infrastructure.')
1097 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
1098 hostname=self.hostname)
1099
1100
beepsc87ff602013-07-31 21:53:00 -07001101 def _is_factory_image(self):
1102 """Checks if the image on the DUT is a factory image.
1103
1104 @return: True if the image on the DUT is a factory image.
1105 False otherwise.
1106 """
1107 result = self.run('[ -f /root/.factory_test ]', ignore_status=True)
1108 return result.exit_status == 0
1109
1110
1111 def _restart_ui(self):
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001112 """Restart the Chrome UI.
beepsc87ff602013-07-31 21:53:00 -07001113
1114 @raises: FactoryImageCheckerException for factory images, since
1115 we cannot attempt to restart ui on them.
1116 error.AutoservRunError for any other type of error that
1117 occurs while restarting ui.
1118 """
1119 if self._is_factory_image():
1120 raise FactoryImageCheckerException('Cannot restart ui on factory '
1121 'images')
1122
J. Richard Barnette84890bd2014-02-21 11:05:47 -08001123 # TODO(jrbarnette): The command to stop/start the ui job
1124 # should live inside cros_ui, too. However that would seem
1125 # to imply interface changes to the existing start()/restart()
1126 # functions, which is a bridge too far (for now).
1127 prompt = cros_ui.get_login_prompt_state(self)
1128 self.run('stop ui; start ui')
1129 cros_ui.wait_for_chrome_ready(prompt, self)
beepsc87ff602013-07-31 21:53:00 -07001130
1131
1132 def cleanup(self):
Richard Barnette82c35912012-11-20 10:09:10 -08001133 self.run('rm -f %s' % constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001134 try:
beepsc87ff602013-07-31 21:53:00 -07001135 self._restart_ui()
1136 except (error.AutotestRunError, error.AutoservRunError,
1137 FactoryImageCheckerException):
Scott Zawalskiddbc31e2012-11-15 11:29:01 -05001138 logging.warn('Unable to restart ui, rebooting device.')
1139 # Since restarting the UI fails fall back to normal Autotest
1140 # cleanup routines, i.e. reboot the machine.
Fang Deng0ca40e22013-08-27 17:47:44 -07001141 super(CrosHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -07001142 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -07001143 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -07001144 self._cleanup_poweron()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001145
1146
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001147 def reboot(self, **dargs):
1148 """
1149 This function reboots the site host. The more generic
1150 RemoteHost.reboot() performs sync and sleeps for 5
1151 seconds. This is not necessary for Chrome OS devices as the
1152 sync should be finished in a short time during the reboot
1153 command.
1154 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001155 if 'reboot_cmd' not in dargs:
1156 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
1157 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001158 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +08001159 if 'fastsync' not in dargs:
1160 dargs['fastsync'] = True
Fang Deng0ca40e22013-08-27 17:47:44 -07001161 super(CrosHost, self).reboot(**dargs)
Yu-Ju Honga2be94a2012-07-31 09:48:52 -07001162
1163
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001164 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001165 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001166
Richard Barnetteb2bc13c2013-01-08 17:32:51 -08001167 Tests for the following conditions:
1168 1. All conditions tested by the parent version of this
1169 function.
1170 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -07001171 3. Sufficient space in /mnt/stateful_partition/encrypted.
1172 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001173
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001174 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001175 super(CrosHost, self).verify_software()
J. Richard Barnette45e93de2012-04-11 17:24:15 -07001176 self.check_diskspace(
1177 '/mnt/stateful_partition',
1178 global_config.global_config.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -07001179 'SERVER', 'gb_diskspace_required', type=float,
1180 default=20.0))
1181 self.check_diskspace(
1182 '/mnt/stateful_partition/encrypted',
1183 global_config.global_config.get_config_value(
1184 'SERVER', 'gb_encrypted_diskspace_required', type=float,
1185 default=0.1))
beepsc87ff602013-07-31 21:53:00 -07001186
1187 # Factory images don't run update engine,
1188 # goofy controls dbus on these DUTs.
1189 if not self._is_factory_image():
1190 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -05001191 # Makes sure python is present, loads and can use built in functions.
1192 # We have seen cases where importing cPickle fails with undefined
1193 # symbols in cPickle.so.
1194 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001195
1196
Fang Deng96667ca2013-08-01 17:46:18 -07001197 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
1198 connect_timeout=None, alive_interval=None):
1199 """Override default make_ssh_command to use options tuned for Chrome OS.
1200
1201 Tuning changes:
1202 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
1203 connection failure. Consistency with remote_access.sh.
1204
1205 - ServerAliveInterval=180; which causes SSH to ping connection every
1206 180 seconds. In conjunction with ServerAliveCountMax ensures
1207 that if the connection dies, Autotest will bail out quickly.
1208 Originally tried 60 secs, but saw frequent job ABORTS where
1209 the test completed successfully.
1210
1211 - ServerAliveCountMax=3; consistency with remote_access.sh.
1212
1213 - ConnectAttempts=4; reduce flakiness in connection errors;
1214 consistency with remote_access.sh.
1215
1216 - UserKnownHostsFile=/dev/null; we don't care about the keys.
1217 Host keys change with every new installation, don't waste
1218 memory/space saving them.
1219
1220 - SSH protocol forced to 2; needed for ServerAliveInterval.
1221
1222 @param user User name to use for the ssh connection.
1223 @param port Port on the target host to use for ssh connection.
1224 @param opts Additional options to the ssh command.
1225 @param hosts_file Ignored.
1226 @param connect_timeout Ignored.
1227 @param alive_interval Ignored.
1228 """
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001229 base_command = ('/usr/bin/ssh -a -x %s %s %s'
1230 ' -o StrictHostKeyChecking=no'
Fang Deng96667ca2013-08-01 17:46:18 -07001231 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
1232 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
1233 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
1234 ' -o Protocol=2 -l %s -p %d')
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001235 return base_command % (self._ssh_verbosity_flag, self._ssh_options,
1236 opts, user, port)
Fang Deng96667ca2013-08-01 17:46:18 -07001237
1238
beeps32a63082013-08-22 14:02:29 -07001239 def _create_ssh_tunnel(self, port, local_port):
1240 """Create an ssh tunnel from local_port to port.
1241
1242 @param port: remote port on the host.
1243 @param local_port: local forwarding port.
1244
1245 @return: the tunnel process.
1246 """
1247 # Chrome OS on the target closes down most external ports
1248 # for security. We could open the port, but doing that
1249 # would conflict with security tests that check that only
1250 # expected ports are open. So, to get to the port on the
1251 # target we use an ssh tunnel.
1252 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
1253 ssh_cmd = self.make_ssh_command(opts=tunnel_options)
1254 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
1255 logging.debug('Full tunnel command: %s', tunnel_cmd)
1256 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
1257 logging.debug('Started ssh tunnel, local = %d'
1258 ' remote = %d, pid = %d',
1259 local_port, port, tunnel_proc.pid)
1260 return tunnel_proc
1261
1262
Christopher Wileydd181852013-10-10 19:56:58 -07001263 def _setup_rpc(self, port, command_name, remote_pid=None):
beeps32a63082013-08-22 14:02:29 -07001264 """Sets up a tunnel process and performs rpc connection book keeping.
1265
1266 This method assumes that xmlrpc and jsonrpc never conflict, since
1267 we can only either have an xmlrpc or a jsonrpc server listening on
1268 a remote port. As such, it enforces a single proxy->remote port
1269 policy, i.e if one starts a jsonrpc proxy/server from port A->B,
1270 and then tries to start an xmlrpc proxy forwarded to the same port,
1271 the xmlrpc proxy will override the jsonrpc tunnel process, however:
1272
1273 1. None of the methods on the xmlrpc proxy will work because
1274 the server listening on B is jsonrpc.
1275
1276 2. The xmlrpc client cannot initiate a termination of the JsonRPC
1277 server, as the only use case currently is goofy, which is tied to
1278 the factory image. It is much easier to handle a failed xmlrpc
1279 call on the client than it is to terminate goofy in this scenario,
1280 as doing the latter might leave the DUT in a hard to recover state.
1281
1282 With the current implementation newer rpc proxy connections will
1283 terminate the tunnel processes of older rpc connections tunneling
1284 to the same remote port. If methods are invoked on the client
1285 after this has happened they will fail with connection closed errors.
1286
1287 @param port: The remote forwarding port.
1288 @param command_name: The name of the remote process, to terminate
1289 using pkill.
1290
1291 @return A url that we can use to initiate the rpc connection.
1292 """
1293 self.rpc_disconnect(port)
1294 local_port = utils.get_unused_port()
1295 tunnel_proc = self._create_ssh_tunnel(port, local_port)
Christopher Wileydd181852013-10-10 19:56:58 -07001296 self._rpc_proxy_map[port] = (command_name, tunnel_proc, remote_pid)
beeps32a63082013-08-22 14:02:29 -07001297 return self._RPC_PROXY_URL % local_port
1298
1299
Christopher Wileyd78249a2013-03-01 13:05:31 -08001300 def xmlrpc_connect(self, command, port, command_name=None,
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001301 ready_test_name=None, timeout_seconds=10,
1302 logfile='/dev/null'):
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001303 """Connect to an XMLRPC server on the host.
1304
1305 The `command` argument should be a simple shell command that
1306 starts an XMLRPC server on the given `port`. The command
1307 must not daemonize, and must terminate cleanly on SIGTERM.
1308 The command is started in the background on the host, and a
1309 local XMLRPC client for the server is created and returned
1310 to the caller.
1311
1312 Note that the process of creating an XMLRPC client makes no
1313 attempt to connect to the remote server; the caller is
1314 responsible for determining whether the server is running
1315 correctly, and is ready to serve requests.
1316
Christopher Wileyd78249a2013-03-01 13:05:31 -08001317 Optionally, the caller can pass ready_test_name, a string
1318 containing the name of a method to call on the proxy. This
1319 method should take no parameters and return successfully only
1320 when the server is ready to process client requests. When
1321 ready_test_name is set, xmlrpc_connect will block until the
1322 proxy is ready, and throw a TestError if the server isn't
1323 ready by timeout_seconds.
1324
beeps32a63082013-08-22 14:02:29 -07001325 If a server is already running on the remote port, this
1326 method will kill it and disconnect the tunnel process
1327 associated with the connection before establishing a new one,
1328 by consulting the rpc_proxy_map in rpc_disconnect.
1329
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001330 @param command Shell command to start the server.
1331 @param port Port number on which the server is expected to
1332 be serving.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001333 @param command_name String to use as input to `pkill` to
1334 terminate the XMLRPC server on the host.
Christopher Wileyd78249a2013-03-01 13:05:31 -08001335 @param ready_test_name String containing the name of a
1336 method defined on the XMLRPC server.
1337 @param timeout_seconds Number of seconds to wait
1338 for the server to become 'ready.' Will throw a
1339 TestFail error if server is not ready in time.
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001340 @param logfile Logfile to send output when running
1341 'command' argument.
Yusuf Mohsinally8d19e3c2013-11-21 14:25:45 -08001342
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001343 """
Christopher Wileyc14f06a2013-10-16 13:55:39 -07001344 # Clean up any existing state. If the caller is willing
1345 # to believe their server is down, we ought to clean up
1346 # any tunnels we might have sitting around.
1347 self.rpc_disconnect(port)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001348 # Start the server on the host. Redirection in the command
1349 # below is necessary, because 'ssh' won't terminate until
1350 # background child processes close stdin, stdout, and
1351 # stderr.
Yusuf Mohsinallyfff89d62013-11-18 16:34:07 -08001352 remote_cmd = '%s </dev/null >%s 2>&1 & echo $!' % (command, logfile)
Christopher Wileydd181852013-10-10 19:56:58 -07001353 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001354 logging.debug('Started XMLRPC server on host %s, pid = %s',
1355 self.hostname, remote_pid)
1356
Christopher Wileydd181852013-10-10 19:56:58 -07001357 # Tunnel through SSH to be able to reach that remote port.
1358 rpc_url = self._setup_rpc(port, command_name, remote_pid=remote_pid)
Christopher Wileyd78249a2013-03-01 13:05:31 -08001359 proxy = xmlrpclib.ServerProxy(rpc_url, allow_none=True)
Christopher Wileydd181852013-10-10 19:56:58 -07001360
Christopher Wileyd78249a2013-03-01 13:05:31 -08001361 if ready_test_name is not None:
J. Richard Barnette13eb7c02013-03-07 12:06:29 -08001362 # retry.retry logs each attempt; calculate delay_sec to
1363 # keep log spam to a dull roar.
Christopher Wiley0ed712b2013-04-09 15:25:12 -07001364 @retry.retry((socket.error,
1365 xmlrpclib.ProtocolError,
1366 httplib.BadStatusLine),
Chris Sosa65425082013-10-16 13:26:22 -07001367 timeout_min=timeout_seconds / 60.0,
1368 delay_sec=min(max(timeout_seconds / 20.0, 0.1), 1))
Christopher Wileyd78249a2013-03-01 13:05:31 -08001369 def ready_test():
1370 """ Call proxy.ready_test_name(). """
1371 getattr(proxy, ready_test_name)()
1372 successful = False
1373 try:
1374 logging.info('Waiting %d seconds for XMLRPC server '
1375 'to start.', timeout_seconds)
1376 ready_test()
1377 successful = True
Christopher Wileyd78249a2013-03-01 13:05:31 -08001378 finally:
1379 if not successful:
1380 logging.error('Failed to start XMLRPC server.')
beeps32a63082013-08-22 14:02:29 -07001381 self.rpc_disconnect(port)
Christopher Wileyd78249a2013-03-01 13:05:31 -08001382 logging.info('XMLRPC server started successfully.')
1383 return proxy
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001384
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001385
Jason Abeleb6f924f2013-11-13 16:01:54 -08001386 def syslog(self, message, tag='autotest'):
1387 """Logs a message to syslog on host.
1388
1389 @param message String message to log into syslog
1390 @param tag String tag prefix for syslog
1391
1392 """
1393 self.run('logger -t "%s" "%s"' % (tag, message))
1394
1395
beeps32a63082013-08-22 14:02:29 -07001396 def jsonrpc_connect(self, port):
1397 """Creates a jsonrpc proxy connection through an ssh tunnel.
1398
1399 This method exists to facilitate communication with goofy (which is
1400 the default system manager on all factory images) and as such, leaves
1401 most of the rpc server sanity checking to the caller. Unlike
1402 xmlrpc_connect, this method does not facilitate the creation of a remote
1403 jsonrpc server, as the only clients of this code are factory tests,
1404 for which the goofy system manager is built in to the image and starts
1405 when the target boots.
1406
1407 One can theoretically create multiple jsonrpc proxies all forwarded
1408 to the same remote port, provided the remote port has an rpc server
1409 listening. However, in doing so we stand the risk of leaking an
1410 existing tunnel process, so we always disconnect any older tunnels
1411 we might have through rpc_disconnect.
1412
1413 @param port: port on the remote host that is serving this proxy.
1414
1415 @return: The client proxy.
1416 """
1417 if not jsonrpclib:
1418 logging.warning('Jsonrpclib could not be imported. Check that '
1419 'site-packages contains jsonrpclib.')
1420 return None
1421
1422 proxy = jsonrpclib.jsonrpc.ServerProxy(self._setup_rpc(port, None))
1423
1424 logging.info('Established a jsonrpc connection through port %s.', port)
1425 return proxy
1426
1427
1428 def rpc_disconnect(self, port):
1429 """Disconnect from an RPC server on the host.
1430
1431 Terminates the remote RPC server previously started for
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001432 the given `port`. Also closes the local ssh tunnel created
1433 for the connection to the host. This function does not
beeps32a63082013-08-22 14:02:29 -07001434 directly alter the state of a previously returned RPC
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001435 client object; however disconnection will cause all
1436 subsequent calls to methods on the object to fail.
1437
1438 This function does nothing if requested to disconnect a port
beeps32a63082013-08-22 14:02:29 -07001439 that was not previously connected via _setup_rpc.
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001440
1441 @param port Port number passed to a previous call to
beeps32a63082013-08-22 14:02:29 -07001442 `_setup_rpc()`.
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001443 """
beeps32a63082013-08-22 14:02:29 -07001444 if port not in self._rpc_proxy_map:
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001445 return
Christopher Wileydd181852013-10-10 19:56:58 -07001446 remote_name, tunnel_proc, remote_pid = self._rpc_proxy_map[port]
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001447 if remote_name:
1448 # We use 'pkill' to find our target process rather than
1449 # a PID, because the host may have rebooted since
1450 # connecting, and we don't want to kill an innocent
1451 # process with the same PID.
1452 #
1453 # 'pkill' helpfully exits with status 1 if no target
1454 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -07001455 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001456 # status.
1457 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
Christopher Wileydd181852013-10-10 19:56:58 -07001458 if remote_pid:
1459 logging.info('Waiting for RPC server "%s" shutdown',
1460 remote_name)
1461 start_time = time.time()
1462 while (time.time() - start_time <
1463 self._RPC_SHUTDOWN_TIMEOUT_SECONDS):
1464 running_processes = self.run(
1465 "pgrep -f '%s'" % remote_name,
1466 ignore_status=True).stdout.split()
1467 if not remote_pid in running_processes:
1468 logging.info('Shut down RPC server.')
1469 break
1470 time.sleep(self._RPC_SHUTDOWN_POLLING_PERIOD_SECONDS)
1471 else:
1472 raise error.TestError('Failed to shutdown RPC server %s' %
1473 remote_name)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001474
1475 if tunnel_proc.poll() is None:
1476 tunnel_proc.terminate()
1477 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
1478 else:
1479 logging.debug('Tunnel pid %d terminated early, status %d',
1480 tunnel_proc.pid, tunnel_proc.returncode)
beeps32a63082013-08-22 14:02:29 -07001481 del self._rpc_proxy_map[port]
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001482
1483
beeps32a63082013-08-22 14:02:29 -07001484 def rpc_disconnect_all(self):
1485 """Disconnect all known RPC proxy ports."""
1486 for port in self._rpc_proxy_map.keys():
1487 self.rpc_disconnect(port)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001488
1489
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001490 def _ping_check_status(self, status):
1491 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001492
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001493 @param status Check the ping status against this value.
1494 @return True iff `status` and the result of ping are the same
1495 (i.e. both True or both False).
1496
1497 """
1498 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1499 return not (status ^ (ping_val == 0))
1500
1501 def _ping_wait_for_status(self, status, timeout):
1502 """Wait for the host to have a given status (UP or DOWN).
1503
1504 Status is checked by polling. Polling will not last longer
1505 than the number of seconds in `timeout`. The polling
1506 interval will be long enough that only approximately
1507 _PING_WAIT_COUNT polling cycles will be executed, subject
1508 to a maximum interval of about one minute.
1509
1510 @param status Waiting will stop immediately if `ping` of the
1511 host returns this status.
1512 @param timeout Poll for at most this many seconds.
1513 @return True iff the host status from `ping` matched the
1514 requested status at the time of return.
1515
1516 """
1517 # _ping_check_status() takes about 1 second, hence the
1518 # "- 1" in the formula below.
1519 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1520 end_time = time.time() + timeout
1521 while time.time() <= end_time:
1522 if self._ping_check_status(status):
1523 return True
1524 if poll_interval > 0:
1525 time.sleep(poll_interval)
1526
1527 # The last thing we did was sleep(poll_interval), so it may
1528 # have been too long since the last `ping`. Check one more
1529 # time, just to be sure.
1530 return self._ping_check_status(status)
1531
1532 def ping_wait_up(self, timeout):
1533 """Wait for the host to respond to `ping`.
1534
1535 N.B. This method is not a reliable substitute for
1536 `wait_up()`, because a host that responds to ping will not
1537 necessarily respond to ssh. This method should only be used
1538 if the target DUT can be considered functional even if it
1539 can't be reached via ssh.
1540
1541 @param timeout Minimum time to allow before declaring the
1542 host to be non-responsive.
1543 @return True iff the host answered to ping before the timeout.
1544
1545 """
1546 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001547
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001548 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001549 """Wait until the host no longer responds to `ping`.
1550
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001551 This function can be used as a slightly faster version of
1552 `wait_down()`, by avoiding potentially long ssh timeouts.
1553
1554 @param timeout Minimum time to allow for the host to become
1555 non-responsive.
1556 @return True iff the host quit answering ping before the
1557 timeout.
1558
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001559 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001560 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001561
1562 def test_wait_for_sleep(self):
1563 """Wait for the client to enter low-power sleep mode.
1564
1565 The test for "is asleep" can't distinguish a system that is
1566 powered off; to confirm that the unit was asleep, it is
1567 necessary to force resume, and then call
1568 `test_wait_for_resume()`.
1569
1570 This function is expected to be called from a test as part
1571 of a sequence like the following:
1572
1573 ~~~~~~~~
1574 boot_id = host.get_boot_id()
1575 # trigger sleep on the host
1576 host.test_wait_for_sleep()
1577 # trigger resume on the host
1578 host.test_wait_for_resume(boot_id)
1579 ~~~~~~~~
1580
1581 @exception TestFail The host did not go to sleep within
1582 the allowed time.
1583 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001584 if not self.ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001585 raise error.TestFail(
1586 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001587 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001588
1589
1590 def test_wait_for_resume(self, old_boot_id):
1591 """Wait for the client to resume from low-power sleep mode.
1592
1593 The `old_boot_id` parameter should be the value from
1594 `get_boot_id()` obtained prior to entering sleep mode. A
1595 `TestFail` exception is raised if the boot id changes.
1596
1597 See @ref test_wait_for_sleep for more on this function's
1598 usage.
1599
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001600 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001601 target host went to sleep.
1602
1603 @exception TestFail The host did not respond within the
1604 allowed time.
1605 @exception TestFail The host responded, but the boot id test
1606 indicated a reboot rather than a sleep
1607 cycle.
1608 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001609 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001610 raise error.TestFail(
1611 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001612 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001613 else:
1614 new_boot_id = self.get_boot_id()
1615 if new_boot_id != old_boot_id:
1616 raise error.TestFail(
1617 'client rebooted, but sleep was expected'
1618 ' (old boot %s, new boot %s)'
1619 % (old_boot_id, new_boot_id))
1620
1621
1622 def test_wait_for_shutdown(self):
1623 """Wait for the client to shut down.
1624
1625 The test for "has shut down" can't distinguish a system that
1626 is merely asleep; to confirm that the unit was down, it is
1627 necessary to force boot, and then call test_wait_for_boot().
1628
1629 This function is expected to be called from a test as part
1630 of a sequence like the following:
1631
1632 ~~~~~~~~
1633 boot_id = host.get_boot_id()
1634 # trigger shutdown on the host
1635 host.test_wait_for_shutdown()
1636 # trigger boot on the host
1637 host.test_wait_for_boot(boot_id)
1638 ~~~~~~~~
1639
1640 @exception TestFail The host did not shut down within the
1641 allowed time.
1642 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001643 if not self.ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001644 raise error.TestFail(
1645 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001646 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001647
1648
1649 def test_wait_for_boot(self, old_boot_id=None):
1650 """Wait for the client to boot from cold power.
1651
1652 The `old_boot_id` parameter should be the value from
1653 `get_boot_id()` obtained prior to shutting down. A
1654 `TestFail` exception is raised if the boot id does not
1655 change. The boot id test is omitted if `old_boot_id` is not
1656 specified.
1657
1658 See @ref test_wait_for_shutdown for more on this function's
1659 usage.
1660
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001661 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001662 shut down.
1663
1664 @exception TestFail The host did not respond within the
1665 allowed time.
1666 @exception TestFail The host responded, but the boot id test
1667 indicated that there was no reboot.
1668 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001669 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001670 raise error.TestFail(
1671 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001672 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001673 elif old_boot_id:
1674 if self.get_boot_id() == old_boot_id:
1675 raise error.TestFail(
1676 'client is back up, but did not reboot'
1677 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -07001678
1679
1680 @staticmethod
1681 def check_for_rpm_support(hostname):
1682 """For a given hostname, return whether or not it is powered by an RPM.
1683
Simran Basi1df55112013-09-06 11:25:09 -07001684 @param hostname: hostname to check for rpm support.
1685
Simran Basid5e5e272012-09-24 15:23:59 -07001686 @return None if this host does not follows the defined naming format
1687 for RPM powered DUT's in the lab. If it does follow the format,
1688 it returns a regular expression MatchObject instead.
1689 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001690 return re.match(CrosHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001691
1692
1693 def has_power(self):
1694 """For this host, return whether or not it is powered by an RPM.
1695
1696 @return True if this host is in the CROS lab and follows the defined
1697 naming format.
1698 """
Fang Deng0ca40e22013-08-27 17:47:44 -07001699 return CrosHost.check_for_rpm_support(self.hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001700
1701
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001702 def _set_power(self, state, power_method):
1703 """Sets the power to the host via RPM, Servo or manual.
1704
1705 @param state Specifies which power state to set to DUT
1706 @param power_method Specifies which method of power control to
1707 use. By default "RPM" will be used. Valid values
1708 are the strings "RPM", "manual", "servoj10".
1709
1710 """
1711 ACCEPTABLE_STATES = ['ON', 'OFF']
1712
1713 if state.upper() not in ACCEPTABLE_STATES:
1714 raise error.TestError('State must be one of: %s.'
1715 % (ACCEPTABLE_STATES,))
1716
1717 if power_method == self.POWER_CONTROL_SERVO:
1718 logging.info('Setting servo port J10 to %s', state)
1719 self.servo.set('prtctl3_pwren', state.lower())
1720 time.sleep(self._USB_POWER_TIMEOUT)
1721 elif power_method == self.POWER_CONTROL_MANUAL:
1722 logging.info('You have %d seconds to set the AC power to %s.',
1723 self._POWER_CYCLE_TIMEOUT, state)
1724 time.sleep(self._POWER_CYCLE_TIMEOUT)
1725 else:
1726 if not self.has_power():
1727 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07001728 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1729 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
1730 hostname=self.hostname)
Simran Basi1df55112013-09-06 11:25:09 -07001731 rpm_client.set_power(self.hostname, state.upper(), timeout_mins=5)
Simran Basid5e5e272012-09-24 15:23:59 -07001732
1733
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001734 def power_off(self, power_method=POWER_CONTROL_RPM):
1735 """Turn off power to this host via RPM, Servo or manual.
1736
1737 @param power_method Specifies which method of power control to
1738 use. By default "RPM" will be used. Valid values
1739 are the strings "RPM", "manual", "servoj10".
1740
1741 """
1742 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001743
1744
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001745 def power_on(self, power_method=POWER_CONTROL_RPM):
1746 """Turn on power to this host via RPM, Servo or manual.
1747
1748 @param power_method Specifies which method of power control to
1749 use. By default "RPM" will be used. Valid values
1750 are the strings "RPM", "manual", "servoj10".
1751
1752 """
1753 self._set_power('ON', power_method)
1754
1755
1756 def power_cycle(self, power_method=POWER_CONTROL_RPM):
1757 """Cycle power to this host by turning it OFF, then ON.
1758
1759 @param power_method Specifies which method of power control to
1760 use. By default "RPM" will be used. Valid values
1761 are the strings "RPM", "manual", "servoj10".
1762
1763 """
1764 if power_method in (self.POWER_CONTROL_SERVO,
1765 self.POWER_CONTROL_MANUAL):
1766 self.power_off(power_method=power_method)
1767 time.sleep(self._POWER_CYCLE_TIMEOUT)
1768 self.power_on(power_method=power_method)
1769 else:
1770 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001771
1772
1773 def get_platform(self):
1774 """Determine the correct platform label for this host.
1775
1776 @returns a string representing this host's platform.
1777 """
1778 crossystem = utils.Crossystem(self)
1779 crossystem.init()
1780 # Extract fwid value and use the leading part as the platform id.
1781 # fwid generally follow the format of {platform}.{firmware version}
1782 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1783 platform = crossystem.fwid().split('.')[0].lower()
1784 # Newer platforms start with 'Google_' while the older ones do not.
1785 return platform.replace('google_', '')
1786
1787
Luis Lozano40b7d0d2014-01-17 15:12:06 -08001788 def get_chrome_version(self):
1789 """Gets the Chrome version number and milestone as strings.
1790
1791 Invokes "chrome --version" to get the version number and milestone.
1792
1793 @return A tuple (chrome_ver, milestone) where "chrome_ver" is the
1794 current Chrome version number as a string (in the form "W.X.Y.Z")
1795 and "milestone" is the first component of the version number
1796 (the "W" from "W.X.Y.Z"). If the version number cannot be parsed
1797 in the "W.X.Y.Z" format, the "chrome_ver" will be the full output
1798 of "chrome --version" and the milestone will be the empty string.
1799
1800 """
1801 version_string = self.run(constants.CHROME_VERSION_COMMAND).stdout
1802 return utils.parse_chrome_version(version_string)
1803
Aviv Keshet74c89a92013-02-04 15:18:30 -08001804 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07001805 def get_board(self):
1806 """Determine the correct board label for this host.
1807
1808 @returns a string representing this host's board.
1809 """
1810 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1811 run_method=self.run)
1812 board = release_info['CHROMEOS_RELEASE_BOARD']
1813 # Devices in the lab generally have the correct board name but our own
1814 # development devices have {board_name}-signed-{key_type}. The board
1815 # name may also begin with 'x86-' which we need to keep.
Simran Basi833814b2013-01-29 13:13:43 -08001816 board_format_string = ds_constants.BOARD_PREFIX + '%s'
Simran Basic6f1f7a2012-10-16 10:47:46 -07001817 if 'x86' not in board:
Simran Basi833814b2013-01-29 13:13:43 -08001818 return board_format_string % board.split('-')[0]
1819 return board_format_string % '-'.join(board.split('-')[0:2])
Simran Basic6f1f7a2012-10-16 10:47:46 -07001820
1821
Aviv Keshet74c89a92013-02-04 15:18:30 -08001822 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001823 def has_lightsensor(self):
1824 """Determine the correct board label for this host.
1825
1826 @returns the string 'lightsensor' if this host has a lightsensor or
1827 None if it does not.
1828 """
1829 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08001830 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07001831 try:
1832 # Run the search cmd following the symlinks. Stderr_tee is set to
1833 # None as there can be a symlink loop, but the command will still
1834 # execute correctly with a few messages printed to stderr.
1835 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
1836 return 'lightsensor'
1837 except error.AutoservRunError:
1838 # egrep exited with a return code of 1 meaning none of the possible
1839 # lightsensor files existed.
1840 return None
1841
1842
Aviv Keshet74c89a92013-02-04 15:18:30 -08001843 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001844 def has_bluetooth(self):
1845 """Determine the correct board label for this host.
1846
1847 @returns the string 'bluetooth' if this host has bluetooth or
1848 None if it does not.
1849 """
1850 try:
1851 self.run('test -d /sys/class/bluetooth/hci0')
1852 # test exited with a return code of 0.
1853 return 'bluetooth'
1854 except error.AutoservRunError:
1855 # test exited with a return code 1 meaning the directory did not
1856 # exist.
1857 return None
1858
1859
Ilja Friedel0ce0b602013-08-15 18:45:27 -07001860 @label_decorator('graphics')
1861 def get_graphics(self):
1862 """
1863 Determine the correct board label for this host.
1864
1865 @returns a string representing this host's graphics. For now ARM boards
1866 return graphics:gles while all other boards return graphics:gl. This
1867 may change over time, but for robustness reasons this should avoid
1868 executing code in actual graphics libraries (which may not be ready and
1869 is tested by graphics_GLAPICheck).
1870 """
1871 uname = self.run('uname -a').stdout.lower()
1872 if 'arm' in uname:
1873 return 'graphics:gles'
1874 return 'graphics:gl'
1875
1876
Simran Basic6f1f7a2012-10-16 10:47:46 -07001877 def get_labels(self):
1878 """Return a list of labels for this given host.
1879
1880 This is the main way to retrieve all the automatic labels for a host
1881 as it will run through all the currently implemented label functions.
1882 """
1883 labels = []
Richard Barnette82c35912012-11-20 10:09:10 -08001884 for label_function in self._LABEL_FUNCTIONS:
Simran Basic6f1f7a2012-10-16 10:47:46 -07001885 label = label_function(self)
1886 if label:
1887 labels.append(label)
1888 return labels