blob: d1b39c2b3d86ada4684d8f448d126daba258ed68 [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 Barnette45e93de2012-04-11 17:24:15 -070022from autotest_lib.server import autoserv_parser
Chris Sosaf4d43ff2012-10-30 11:21:05 -070023from autotest_lib.server import autotest
J. Richard Barnette45e93de2012-04-11 17:24:15 -070024from autotest_lib.server import site_host_attributes
Scott Zawalski89c44dd2013-02-26 09:28:02 -050025from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070026from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
J. Richard Barnette75487572013-03-08 12:47:50 -080027from autotest_lib.server.cros.servo import servo
J. Richard Barnette45e93de2012-04-11 17:24:15 -070028from autotest_lib.server.hosts import remote
Simran Basidcff4252012-11-20 16:13:20 -080029from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070030
31
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -080032def _make_servo_hostname(hostname):
33 host_parts = hostname.split('.')
34 host_parts[0] = host_parts[0] + '-servo'
35 return '.'.join(host_parts)
36
37
38def _get_lab_servo(target_hostname):
39 """Instantiate a Servo for |target_hostname| in the lab.
40
41 Assuming that |target_hostname| is a device in the CrOS test
42 lab, create and return a Servo object pointed at the servo
43 attached to that DUT. The servo in the test lab is assumed
44 to already have servod up and running on it.
45
46 @param target_hostname: device whose servo we want to target.
47 @return an appropriately configured Servo instance.
48 """
49 servo_host = _make_servo_hostname(target_hostname)
50 if utils.host_is_in_lab_zone(servo_host):
51 try:
J. Richard Barnetted5f807a2013-02-11 16:51:00 -080052 return servo.Servo(servo_host=servo_host)
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -080053 except: # pylint: disable=W0702
54 # TODO(jrbarnette): Long-term, if we can't get to
55 # a servo in the lab, we want to fail, so we should
56 # pass any exceptions along. Short-term, we're not
57 # ready to rely on servo, so we ignore failures.
58 pass
59 return None
60
61
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070062def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
63 connect_timeout=None, alive_interval=None):
64 """Override default make_ssh_command to use options tuned for Chrome OS.
65
66 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070067 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
68 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070069
Dale Curtisaa5eedb2011-08-23 16:18:52 -070070 - ServerAliveInterval=180; which causes SSH to ping connection every
71 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
72 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
73 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070074
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070075 - ServerAliveCountMax=3; consistency with remote_access.sh.
76
77 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
78 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070079
80 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
81 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070082
83 - SSH protocol forced to 2; needed for ServerAliveInterval.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -080084
85 @param user User name to use for the ssh connection.
86 @param port Port on the target host to use for ssh connection.
87 @param opts Additional options to the ssh command.
88 @param hosts_file Ignored.
89 @param connect_timeout Ignored.
90 @param alive_interval Ignored.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070091 """
92 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
93 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070094 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
95 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
96 ' -o Protocol=2 -l %s -p %d')
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070097 return base_command % (opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070098
99
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800100
Aviv Keshet74c89a92013-02-04 15:18:30 -0800101def add_label_detector(label_function_list, label_list=None, label=None):
102 """Decorator used to group functions together into the provided list.
103 @param label_function_list: List of label detecting functions to add
104 decorated function to.
105 @param label_list: List of detectable labels to add detectable labels to.
106 (Default: None)
107 @param label: Label string that is detectable by this detection function
108 (Default: None)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800109 """
Simran Basic6f1f7a2012-10-16 10:47:46 -0700110 def add_func(func):
Aviv Keshet74c89a92013-02-04 15:18:30 -0800111 """
112 @param func: The function to be added as a detector.
113 """
114 label_function_list.append(func)
115 if label and label_list is not None:
116 label_list.append(label)
Simran Basic6f1f7a2012-10-16 10:47:46 -0700117 return func
118 return add_func
119
120
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700121class SiteHost(remote.RemoteHost):
122 """Chromium OS specific subclass of Host."""
123
124 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -0500125 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700126
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800127 # Time to wait for new kernel to be marked successful after
128 # auto update.
Chris Masone163cead2012-05-16 11:49:48 -0700129 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700130
Richard Barnette03a0c132012-11-05 12:40:35 -0800131 # Timeout values (in seconds) associated with various Chrome OS
132 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700133 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800134 # In general, a good rule of thumb is that the timeout can be up
135 # to twice the typical measured value on the slowest platform.
136 # The times here have not necessarily been empirically tested to
137 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700138 #
139 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800140 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
141 # time to restart the netwowrk.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700142 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800143 # other things, this must account for the 30 second dev-mode
J. Richard Barnetted4649c62013-03-06 17:42:27 -0800144 # screen delay and time to start the network.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700145 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800146 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -0800147 # network.
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800148 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700149 # REBOOT_TIMEOUT: How long to wait for a reboot.
Richard Barnette03a0c132012-11-05 12:40:35 -0800150 # _INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700151
152 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -0800153 RESUME_TIMEOUT = 10
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700154 BOOT_TIMEOUT = 45
155 USB_BOOT_TIMEOUT = 150
Chris Sosab76e0ee2013-05-22 16:55:41 -0700156
157 # We have a long timeout to ensure we don't flakily fail due to other
158 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
159 REBOOT_TIMEOUT = 300
160
Richard Barnette03a0c132012-11-05 12:40:35 -0800161 _INSTALL_TIMEOUT = 240
162
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800163 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
164 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
165 _USB_POWER_TIMEOUT = 5
166 _POWER_CYCLE_TIMEOUT = 10
167
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800168
Richard Barnette82c35912012-11-20 10:09:10 -0800169 _RPM_RECOVERY_BOARDS = global_config.global_config.get_config_value('CROS',
170 'rpm_recovery_boards', type=str).split(',')
171
172 _MAX_POWER_CYCLE_ATTEMPTS = 6
173 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
174 _RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
175 'host[0-9]+')
176 _LIGHTSENSOR_FILES = ['in_illuminance0_input',
177 'in_illuminance0_raw',
178 'illuminance0_input']
179 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
180 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800181 _DETECTABLE_LABELS = []
182 label_decorator = functools.partial(add_label_detector, _LABEL_FUNCTIONS,
183 _DETECTABLE_LABELS)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700184
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800185 # Constants used in ping_wait_up() and ping_wait_down().
186 #
187 # _PING_WAIT_COUNT is the approximate number of polling
188 # cycles to use when waiting for a host state change.
189 #
190 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
191 # for arguments to the internal _ping_wait_for_status()
192 # method.
193 _PING_WAIT_COUNT = 40
194 _PING_STATUS_DOWN = False
195 _PING_STATUS_UP = True
196
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800197 # Allowed values for the power_method argument.
198
199 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
200 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
201 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
202 POWER_CONTROL_RPM = 'RPM'
203 POWER_CONTROL_SERVO = 'servoj10'
204 POWER_CONTROL_MANUAL = 'manual'
205
206 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
207 POWER_CONTROL_SERVO,
208 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800209
Simran Basi5e6339a2013-03-21 11:34:32 -0700210 _RPM_OUTLET_CHANGED = 'outlet_changed'
211
J. Richard Barnette964fba02012-10-24 17:34:29 -0700212 @staticmethod
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800213 def get_servo_arguments(args_dict):
214 """Extract servo options from `args_dict` and return the result.
215
216 Take the provided dictionary of argument options and return
217 a subset that represent standard arguments needed to
218 construct a servo object for a host. The intent is to
219 provide standard argument processing from run_remote_tests
220 for tests that require a servo to operate.
221
222 Recommended usage:
223 ~~~~~~~~
224 args_dict = utils.args_to_dict(args)
225 servo_args = hosts.SiteHost.get_servo_arguments(args_dict)
226 host = hosts.create_host(machine, servo_args=servo_args)
227 ~~~~~~~~
228
229 @param args_dict Dictionary from which to extract the servo
230 arguments.
231 """
J. Richard Barnette964fba02012-10-24 17:34:29 -0700232 servo_args = {}
233 for arg in ('servo_host', 'servo_port'):
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800234 if arg in args_dict:
235 servo_args[arg] = args_dict[arg]
J. Richard Barnette964fba02012-10-24 17:34:29 -0700236 return servo_args
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700237
J. Richard Barnette964fba02012-10-24 17:34:29 -0700238
239 def _initialize(self, hostname, servo_args=None, *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700240 """Initialize superclasses, and |self.servo|.
241
242 For creating the host servo object, there are three
243 possibilities: First, if the host is a lab system known to
244 have a servo board, we connect to that servo unconditionally.
245 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700246 servo features for testing, it will pass settings for
247 `servo_host`, `servo_port`, or both. If neither of these
248 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700249
250 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700251 super(SiteHost, self)._initialize(hostname=hostname,
252 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700253 # self.env is a dictionary of environment variable settings
254 # to be exported for commands run on the host.
255 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
256 # errors that might happen.
257 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700258 self._xmlrpc_proxy_map = {}
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -0800259 self.servo = _get_lab_servo(hostname)
J. Richard Barnettead7da482012-10-30 16:46:52 -0700260 if not self.servo and servo_args is not None:
J. Richard Barnette964fba02012-10-24 17:34:29 -0700261 self.servo = servo.Servo(**servo_args)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700262
263
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500264 def get_repair_image_name(self):
265 """Generate a image_name from variables in the global config.
266
267 @returns a str of $board-version/$BUILD.
268
269 """
270 stable_version = global_config.global_config.get_config_value(
271 'CROS', 'stable_cros_version')
272 build_pattern = global_config.global_config.get_config_value(
273 'CROS', 'stable_build_pattern')
274 board = self._get_board_from_afe()
275 if board is None:
276 raise error.AutoservError('DUT has no board attribute, '
277 'cannot be repaired.')
278 return build_pattern % (board, stable_version)
279
280
Scott Zawalski62bacae2013-03-05 10:40:32 -0500281 def _host_in_AFE(self):
282 """Check if the host is an object the AFE knows.
283
284 @returns the host object.
285 """
286 return self._AFE.get_hosts(hostname=self.hostname)
287
288
Chris Sosab76e0ee2013-05-22 16:55:41 -0700289 def lookup_job_repo_url(self):
290 """Looks up the job_repo_url for the host.
291
292 @returns job_repo_url from AFE or None if not found.
293
294 @raises KeyError if the host does not have a job_repo_url
295 """
296 if not self._host_in_AFE():
297 return None
298
299 hosts = self._AFE.get_hosts(hostname=self.hostname)
beepsb5efc532013-06-04 11:29:34 -0700300 if hosts and ds_constants.JOB_REPO_URL in hosts[0].attributes:
301 return hosts[0].attributes[ds_constants.JOB_REPO_URL]
Chris Sosab76e0ee2013-05-22 16:55:41 -0700302
303
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500304 def clear_cros_version_labels_and_job_repo_url(self):
305 """Clear cros_version labels and host attribute job_repo_url."""
Scott Zawalski62bacae2013-03-05 10:40:32 -0500306 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400307 return
308
Scott Zawalski62bacae2013-03-05 10:40:32 -0500309 host_list = [self.hostname]
310 labels = self._AFE.get_labels(
311 name__startswith=ds_constants.VERSION_PREFIX,
312 host__hostname=self.hostname)
Dan Shi0f466e82013-02-22 15:44:58 -0800313
Scott Zawalski62bacae2013-03-05 10:40:32 -0500314 for label in labels:
315 label.remove_hosts(hosts=host_list)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500316
beepsb5efc532013-06-04 11:29:34 -0700317 self._AFE.set_host_attribute(ds_constants.JOB_REPO_URL, None,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500318 hostname=self.hostname)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500319
320
Scott Zawalskieadbf702013-03-14 09:23:06 -0400321 def add_cros_version_labels_and_job_repo_url(self, image_name):
322 """Add cros_version labels and host attribute job_repo_url.
323
324 @param image_name: The name of the image e.g.
325 lumpy-release/R27-3837.0.0
326 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500327 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400328 return
Scott Zawalski62bacae2013-03-05 10:40:32 -0500329
Scott Zawalskieadbf702013-03-14 09:23:06 -0400330 cros_label = '%s%s' % (ds_constants.VERSION_PREFIX, image_name)
331 devserver_url = dev_server.ImageServer.resolve(image_name).url()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500332
333 labels = self._AFE.get_labels(name=cros_label)
334 if labels:
335 label = labels[0]
336 else:
337 label = self._AFE.create_label(name=cros_label)
338
339 label.add_hosts([self.hostname])
Scott Zawalskieadbf702013-03-14 09:23:06 -0400340 repo_url = tools.get_package_url(devserver_url, image_name)
beepsb5efc532013-06-04 11:29:34 -0700341 self._AFE.set_host_attribute(ds_constants.JOB_REPO_URL, repo_url,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500342 hostname=self.hostname)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400343
344
Dan Shi0f466e82013-02-22 15:44:58 -0800345 def _try_stateful_update(self, update_url, force_update, updater):
346 """Try to use stateful update to initialize DUT.
347
348 When DUT is already running the same version that machine_install
349 tries to install, stateful update is a much faster way to clean up
350 the DUT for testing, compared to a full reimage. It is implemeted
351 by calling autoupdater.run_update, but skipping updating root, as
352 updating the kernel is time consuming and not necessary.
353
354 @param update_url: url of the image.
355 @param force_update: Set to True to update the image even if the DUT
356 is running the same version.
357 @param updater: ChromiumOSUpdater instance used to update the DUT.
358 @returns: True if the DUT was updated with stateful update.
359
360 """
361 if not updater.check_version():
362 return False
363 if not force_update:
364 logging.info('Canceling stateful update because the new and '
365 'old versions are the same.')
366 return False
367 # Following folders should be rebuilt after stateful update.
368 # A test file is used to confirm each folder gets rebuilt after
369 # the stateful update.
370 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
371 test_file = '.test_file_to_be_deleted'
372 for folder in folders_to_check:
373 touch_path = os.path.join(folder, test_file)
374 self.run('touch %s' % touch_path)
375
376 if not updater.run_update(force_update=True, update_root=False):
377 return False
378
379 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700380 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800381 check_file_cmd = 'test -f %s; echo $?'
382 for folder in folders_to_check:
383 test_file_path = os.path.join(folder, test_file)
384 result = self.run(check_file_cmd % test_file_path,
385 ignore_status=True)
386 if result.exit_status == 1:
387 return False
388 return True
389
390
391 def _post_update_processing(self, updater, inactive_kernel=None):
392 """After the DUT is updated, confirm machine_install succeeded.
393
394 @param updater: ChromiumOSUpdater instance used to update the DUT.
395 @param inactive_kernel: kernel state of inactive kernel before reboot.
396
397 """
398
399 # Touch the lab machine file to leave a marker that distinguishes
400 # this image from other test images.
401 self.run('touch %s' % self._LAB_MACHINE_FILE)
402
403 # Kick off the autoreboot script as the _LAB_MACHINE_FILE was
404 # missing on the first boot.
405 self.run('start autoreboot')
406
407 # Following the reboot, verify the correct version.
Dan Shib95bb862013-03-22 16:29:28 -0700408 if not updater.check_version_to_confirm_install():
Dan Shi0f466e82013-02-22 15:44:58 -0800409 # Print out crossystem to make it easier to debug the rollback.
410 logging.debug('Dumping partition table.')
Dan Shi346725f2013-03-20 15:22:38 -0700411 self.run('cgpt show $(rootdev -s -d)')
Dan Shi0f466e82013-02-22 15:44:58 -0800412 logging.debug('Dumping crossystem for firmware debugging.')
Dan Shi346725f2013-03-20 15:22:38 -0700413 self.run('crossystem --all')
Dan Shi0f466e82013-02-22 15:44:58 -0800414 logging.error('Expected Chromium OS version: %s. '
415 'Found Chromium OS %s',
Dan Shi346725f2013-03-20 15:22:38 -0700416 updater.update_version, updater.get_build_id())
417 raise autoupdater.ChromiumOSError('Updater failed on host %s' %
418 self.hostname)
Dan Shi0f466e82013-02-22 15:44:58 -0800419
420 # Figure out newly active kernel.
421 new_active_kernel, _ = updater.get_kernel_state()
422
423 # Ensure that previously inactive kernel is now the active kernel.
424 if inactive_kernel and new_active_kernel != inactive_kernel:
425 raise autoupdater.ChromiumOSError(
426 'Update failed. New kernel partition is not active after'
427 ' boot.')
428
Scott Zawalski62bacae2013-03-05 10:40:32 -0500429 host_attributes = None
430 if self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400431 host_attributes = site_host_attributes.HostAttributes(self.hostname)
Scott Zawalski62bacae2013-03-05 10:40:32 -0500432
Scott Zawalskieadbf702013-03-14 09:23:06 -0400433 if host_attributes and host_attributes.has_chromeos_firmware:
Dan Shi0f466e82013-02-22 15:44:58 -0800434 # Wait until tries == 0 and success, or until timeout.
435 utils.poll_for_condition(
436 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
437 and updater.get_kernel_success(new_active_kernel)),
438 exception=autoupdater.ChromiumOSError(
439 'Update failed. Timed out waiting for system to mark'
440 ' new kernel as successful.'),
441 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
442
443
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700444 def _stage_image_for_update(self, image_name=None):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400445 """Stage a build on a devserver and return the update_url.
446
447 @param image_name: a name like lumpy-release/R27-3837.0.0
448 @returns an update URL like:
449 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
450 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700451 if not image_name:
452 image_name = self.get_repair_image_name()
453 logging.info('Staging build for AU: %s', image_name)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400454 devserver = dev_server.ImageServer.resolve(image_name)
455 devserver.trigger_download(image_name, synchronous=False)
456 return tools.image_url_pattern() % (devserver.url(), image_name)
457
458
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700459 def stage_image_for_servo(self, image_name=None):
460 """Stage a build on a devserver and return the update_url.
461
462 @param image_name: a name like lumpy-release/R27-3837.0.0
463 @returns an update URL like:
464 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
465 """
466 if not image_name:
467 image_name = self.get_repair_image_name()
468 logging.info('Staging build for servo install: %s', image_name)
469 devserver = dev_server.ImageServer.resolve(image_name)
470 devserver.stage_artifacts(image_name, ['test_image'])
471 return devserver.get_test_image_url(image_name)
472
473
Chris Sosaa3ac2152012-05-23 22:23:13 -0700474 def machine_install(self, update_url=None, force_update=False,
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500475 local_devserver=False, repair=False):
476 """Install the DUT.
477
Dan Shi0f466e82013-02-22 15:44:58 -0800478 Use stateful update if the DUT is already running the same build.
479 Stateful update does not update kernel and tends to run much faster
480 than a full reimage. If the DUT is running a different build, or it
481 failed to do a stateful update, full update, including kernel update,
482 will be applied to the DUT.
483
Scott Zawalskieadbf702013-03-14 09:23:06 -0400484 Once a host enters machine_install its cros_version label will be
485 removed as well as its host attribute job_repo_url (used for
486 package install).
487
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500488 @param update_url: The url to use for the update
489 pattern: http://$devserver:###/update/$build
490 If update_url is None and repair is True we will install the
491 stable image listed in global_config under
492 CROS.stable_cros_version.
493 @param force_update: Force an update even if the version installed
494 is the same. Default:False
495 @param local_devserver: Used by run_remote_test to allow people to
496 use their local devserver. Default: False
497 @param repair: Whether or not we are in repair mode. This adds special
498 cases for repairing a machine like starting update_engine.
499 Setting repair to True sets force_update to True as well.
500 default: False
501 @raises autoupdater.ChromiumOSError
502
503 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700504 if not update_url:
505 if self._parser.options.image:
506 requested_build = self._parser.options.image
507 if requested_build.startswith('http://'):
508 update_url = requested_build
509 else:
510 # Try to stage any build that does not start with
511 # http:// on the devservers defined in
512 # global_config.ini.
513 update_url = self._stage_image_for_update(
514 requested_build)
515 elif repair:
516 update_url = self._stage_image_for_update()
Scott Zawalskieadbf702013-03-14 09:23:06 -0400517 else:
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700518 raise autoupdater.ChromiumOSError(
519 'Update failed. No update URL provided.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500520
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500521 if repair:
Dan Shi0f466e82013-02-22 15:44:58 -0800522 # In case the system is in a bad state, we always reboot the machine
523 # before machine_install.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700524 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500525 self.run('stop update-engine; start update-engine')
526 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800527
Chris Sosaa3ac2152012-05-23 22:23:13 -0700528 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
Chris Sosa72312602013-04-16 15:01:56 -0700529 local_devserver=local_devserver)
Dan Shi0f466e82013-02-22 15:44:58 -0800530 updated = False
Scott Zawalskieadbf702013-03-14 09:23:06 -0400531 # Remove cros-version and job_repo_url host attribute from host.
532 self.clear_cros_version_labels_and_job_repo_url()
Dan Shi0f466e82013-02-22 15:44:58 -0800533 # If the DUT is already running the same build, try stateful update
534 # first. Stateful update does not update kernel and tends to run much
535 # faster than a full reimage.
536 try:
Chris Sosab76e0ee2013-05-22 16:55:41 -0700537 updated = self._try_stateful_update(
538 update_url, force_update, updater)
Dan Shi0f466e82013-02-22 15:44:58 -0800539 if updated:
540 logging.info('DUT is updated with stateful update.')
541 except Exception as e:
542 logging.exception(e)
543 logging.warn('Failed to stateful update DUT, force to update.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700544
Dan Shi0f466e82013-02-22 15:44:58 -0800545 inactive_kernel = None
546 # Do a full update if stateful update is not applicable or failed.
547 if not updated:
548 # In case the system is in a bad state, we always reboot the
549 # machine before machine_install.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700550 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Chris Sosab7612bc2013-03-21 10:32:37 -0700551
552 # TODO(sosa): Remove temporary hack to get rid of bricked machines
553 # that can't update due to a corrupted policy.
554 self.run('rm -rf /var/lib/whitelist')
555 self.run('touch /var/lib/whitelist')
556 self.run('chmod -w /var/lib/whitelist')
Scott Zawalskib550d5a2013-03-22 09:23:59 -0400557 self.run('stop update-engine; start update-engine')
Chris Sosab7612bc2013-03-21 10:32:37 -0700558
Dan Shi0f466e82013-02-22 15:44:58 -0800559 if updater.run_update(force_update):
560 updated = True
561 # Figure out active and inactive kernel.
562 active_kernel, inactive_kernel = updater.get_kernel_state()
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700563
Dan Shi0f466e82013-02-22 15:44:58 -0800564 # Ensure inactive kernel has higher priority than active.
565 if (updater.get_kernel_priority(inactive_kernel)
566 < updater.get_kernel_priority(active_kernel)):
567 raise autoupdater.ChromiumOSError(
568 'Update failed. The priority of the inactive kernel'
569 ' partition is less than that of the active kernel'
570 ' partition.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700571
Dan Shi0f466e82013-02-22 15:44:58 -0800572 update_engine_log = '/var/log/update_engine.log'
573 logging.info('Dumping %s', update_engine_log)
574 self.run('cat %s' % update_engine_log)
575 # Updater has returned successfully; reboot the host.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700576 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700577
Dan Shi0f466e82013-02-22 15:44:58 -0800578 if updated:
579 self._post_update_processing(updater, inactive_kernel)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400580 image_name = autoupdater.url_to_image_name(update_url)
581 self.add_cros_version_labels_and_job_repo_url(image_name)
Simran Basi13fa1ba2013-03-04 10:56:47 -0800582
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700583 # Clean up any old autotest directories which may be lying around.
584 for path in global_config.global_config.get_config_value(
585 'AUTOSERV', 'client_autodir_paths', type=list):
586 self.run('rm -rf ' + path)
587
588
Simran Basi833814b2013-01-29 13:13:43 -0800589 def _get_label_from_afe(self, label_prefix):
590 """Retrieve a host's specific label from the AFE.
591
592 Looks for a host label that has the form <label_prefix>:<value>
593 and returns the "<value>" part of the label. None is returned
594 if there is not a label matching the pattern
595
596 @returns the label that matches the prefix or 'None'
597 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500598 labels = self._AFE.get_labels(name__startswith=label_prefix,
599 host__hostname__in=[self.hostname])
600 if labels and len(labels) == 1:
601 return labels[0].name.split(label_prefix, 1)[1]
Simran Basi833814b2013-01-29 13:13:43 -0800602
603
Richard Barnette82c35912012-11-20 10:09:10 -0800604 def _get_board_from_afe(self):
605 """Retrieve this host's board from its labels in the AFE.
606
607 Looks for a host label of the form "board:<board>", and
608 returns the "<board>" part of the label. `None` is returned
609 if there is not a single, unique label matching the pattern.
610
611 @returns board from label, or `None`.
612 """
Simran Basi833814b2013-01-29 13:13:43 -0800613 return self._get_label_from_afe(ds_constants.BOARD_PREFIX)
614
615
616 def get_build(self):
617 """Retrieve the current build for this Host from the AFE.
618
619 Looks through this host's labels in the AFE to determine its build.
620
621 @returns The current build or None if it could not find it or if there
622 were multiple build labels assigned to this host.
623 """
624 return self._get_label_from_afe(ds_constants.VERSION_PREFIX)
Richard Barnette82c35912012-11-20 10:09:10 -0800625
626
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500627 def _install_repair(self):
628 """Attempt to repair this host using upate-engine.
629
630 If the host is up, try installing the DUT with a stable
631 "repair" version of Chrome OS as defined in the global_config
632 under CROS.stable_cros_version.
633
Scott Zawalski62bacae2013-03-05 10:40:32 -0500634 @raises AutoservRepairMethodNA if the DUT is not reachable.
635 @raises ChromiumOSError if the install failed for some reason.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500636
637 """
638 if not self.is_up():
Scott Zawalski62bacae2013-03-05 10:40:32 -0500639 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500640
641 logging.info('Attempting to reimage machine to repair image.')
642 try:
643 self.machine_install(repair=True)
Fang Dengd0672f32013-03-18 17:18:09 -0700644 except autoupdater.ChromiumOSError as e:
645 logging.exception(e)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500646 logging.info('Repair via install failed.')
Scott Zawalski62bacae2013-03-05 10:40:32 -0500647 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500648
649
Scott Zawalski62bacae2013-03-05 10:40:32 -0500650 def servo_install(self, image_url=None):
651 """
652 Re-install the OS on the DUT by:
653 1) installing a test image on a USB storage device attached to the Servo
654 board,
Richard Barnette03a0c132012-11-05 12:40:35 -0800655 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700656 3) installing the image with chromeos-install.
657
Scott Zawalski62bacae2013-03-05 10:40:32 -0500658 @param image_url: If specified use as the url to install on the DUT.
659 otherwise boot the currently staged image on the USB stick.
Richard Barnette03a0c132012-11-05 12:40:35 -0800660
Scott Zawalski62bacae2013-03-05 10:40:32 -0500661 @raises AutoservError if the image fails to boot.
Richard Barnette03a0c132012-11-05 12:40:35 -0800662 """
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700663 self.servo.install_recovery_image(image_url)
Richard Barnette03a0c132012-11-05 12:40:35 -0800664 if not self.wait_up(timeout=self.USB_BOOT_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500665 raise error.AutoservRepairFailure(
666 'DUT failed to boot from USB after %d seconds' %
667 self.USB_BOOT_TIMEOUT)
668
669 self.run('chromeos-install --yes', timeout=self._INSTALL_TIMEOUT)
Richard Barnette03a0c132012-11-05 12:40:35 -0800670 self.servo.power_long_press()
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700671 self.servo.switch_usbkey('host')
Richard Barnette03a0c132012-11-05 12:40:35 -0800672 self.servo.power_short_press()
673 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
674 raise error.AutoservError('DUT failed to reboot installed '
675 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -0500676 self.BOOT_TIMEOUT)
677
678
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700679 def _servo_repair_reinstall(self):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500680 """Reinstall the DUT utilizing servo and a test image.
681
682 Re-install the OS on the DUT by:
683 1) installing a test image on a USB storage device attached to the Servo
684 board,
685 2) booting that image in recovery mode, and then
686 3) installing the image with chromeos-install.
687
Scott Zawalski62bacae2013-03-05 10:40:32 -0500688 @raises AutoservRepairMethodNA if the device does not have servo
689 support.
690
691 """
692 if not self.servo:
693 raise error.AutoservRepairMethodNA('Repair Reinstall NA: '
694 'DUT has no servo support.')
695
696 logging.info('Attempting to recovery servo enabled device with '
697 'servo_repair_reinstall')
698
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700699 image_url = self.stage_image_for_servo()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500700 self.servo_install(image_url)
701
702
703 def _servo_repair_power(self):
704 """Attempt to repair DUT using an attached Servo.
705
706 Attempt to power on the DUT via power_long_press.
707
708 @raises AutoservRepairMethodNA if the device does not have servo
709 support.
710 @raises AutoservRepairFailure if the repair fails for any reason.
711 """
712 if not self.servo:
713 raise error.AutoservRepairMethodNA('Repair Power NA: '
714 'DUT has no servo support.')
715
716 logging.info('Attempting to recover servo enabled device by '
717 'powering it off and on.')
718 self.servo.get_power_state_controller().power_off()
719 self.servo.get_power_state_controller().power_on()
720 if self.wait_up(self.BOOT_TIMEOUT):
721 return
722
723 raise error.AutoservRepairFailure('DUT did not boot after long_press.')
Richard Barnette03a0c132012-11-05 12:40:35 -0800724
725
Richard Barnette82c35912012-11-20 10:09:10 -0800726 def _powercycle_to_repair(self):
727 """Utilize the RPM Infrastructure to bring the host back up.
728
729 If the host is not up/repaired after the first powercycle we utilize
730 auto fallback to the last good install by powercycling and rebooting the
731 host 6 times.
Scott Zawalski62bacae2013-03-05 10:40:32 -0500732
733 @raises AutoservRepairMethodNA if the device does not support remote
734 power.
735 @raises AutoservRepairFailure if the repair fails for any reason.
736
Richard Barnette82c35912012-11-20 10:09:10 -0800737 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500738 if not self.has_power():
739 raise error.AutoservRepairMethodNA('Device does not support power.')
740
Richard Barnette82c35912012-11-20 10:09:10 -0800741 logging.info('Attempting repair via RPM powercycle.')
742 failed_cycles = 0
743 self.power_cycle()
744 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
745 failed_cycles += 1
746 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
Scott Zawalski62bacae2013-03-05 10:40:32 -0500747 raise error.AutoservRepairFailure(
748 'Powercycled host %s %d times; device did not come back'
749 ' online.' % (self.hostname, failed_cycles))
Richard Barnette82c35912012-11-20 10:09:10 -0800750 self.power_cycle()
751 if failed_cycles == 0:
752 logging.info('Powercycling was successful first time.')
753 else:
754 logging.info('Powercycling was successful after %d failures.',
755 failed_cycles)
756
757
758 def repair_full(self):
759 """Repair a host for repair level NO_PROTECTION.
760
761 This overrides the base class function for repair; it does
762 not call back to the parent class, but instead offers a
763 simplified implementation based on the capabilities in the
764 Chrome OS test lab.
765
J. Richard Barnettefde55fc2013-03-15 17:47:01 -0700766 If `self.verify()` fails, the following procedures are
767 attempted:
768 1. Try to re-install to a known stable image using
769 auto-update.
Scott Zawalski62bacae2013-03-05 10:40:32 -0500770 2. If there's a servo for the DUT, try to power the DUT off and
771 on.
772 3. If there's a servo for the DUT, try to re-install via
J. Richard Barnettefde55fc2013-03-15 17:47:01 -0700773 the servo.
Scott Zawalski62bacae2013-03-05 10:40:32 -0500774 4. If the DUT can be power-cycled via RPM, try to repair
Richard Barnette82c35912012-11-20 10:09:10 -0800775 by power-cycling.
776
777 As with the parent method, the last operation performed on
778 the DUT must be to call `self.verify()`; if that call fails,
779 the exception it raises is passed back to the caller.
J. Richard Barnettefde55fc2013-03-15 17:47:01 -0700780
Scott Zawalski62bacae2013-03-05 10:40:32 -0500781 @raises AutoservRepairTotalFailure if the repair process fails to
782 fix the DUT.
Richard Barnette82c35912012-11-20 10:09:10 -0800783 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500784 # TODO(scottz): This should use something similar to label_decorator,
785 # but needs to be populated in order so DUTs are repaired with the
786 # least amount of effort.
787 repair_funcs = [self._install_repair, self._servo_repair_power,
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700788 self._servo_repair_reinstall,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500789 self._powercycle_to_repair]
790 errors = []
791 for repair_func in repair_funcs:
792 try:
793 repair_func()
794 self.verify()
795 return
796 except Exception as e:
797 logging.warn('Failed to repair device: %s', e)
798 errors.append(str(e))
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500799
Scott Zawalski62bacae2013-03-05 10:40:32 -0500800 raise error.AutoservRepairTotalFailure(
801 'All attempts at repairing the device failed:\n%s' %
802 '\n'.join(errors))
Richard Barnette82c35912012-11-20 10:09:10 -0800803
804
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700805 def close(self):
806 super(SiteHost, self).close()
807 self.xmlrpc_disconnect_all()
808
809
Simran Basi5e6339a2013-03-21 11:34:32 -0700810 def _cleanup_poweron(self):
811 """Special cleanup method to make sure hosts always get power back."""
812 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
813 hosts = afe.get_hosts(hostname=self.hostname)
814 if not hosts or not (self._RPM_OUTLET_CHANGED in
815 hosts[0].attributes):
816 return
817 logging.debug('This host has recently interacted with the RPM'
818 ' Infrastructure. Ensuring power is on.')
819 try:
820 self.power_on()
821 except rpm_client.RemotePowerException:
822 # If cleanup has completed but there was an issue with the RPM
823 # Infrastructure, log an error message rather than fail cleanup
824 logging.error('Failed to turn Power On for this host after '
825 'cleanup through the RPM Infrastructure.')
826 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
827 hostname=self.hostname)
828
829
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700830 def cleanup(self):
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700831 client_at = autotest.Autotest(self)
Richard Barnette82c35912012-11-20 10:09:10 -0800832 self.run('rm -f %s' % constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500833 try:
834 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
835 '_clear_login_prompt_state')
836 self.run('restart ui')
837 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
838 '_wait_for_login_prompt')
Alex Millerf4517962013-02-25 15:03:02 -0800839 except (error.AutotestRunError, error.AutoservRunError):
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500840 logging.warn('Unable to restart ui, rebooting device.')
841 # Since restarting the UI fails fall back to normal Autotest
842 # cleanup routines, i.e. reboot the machine.
843 super(SiteHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -0700844 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -0700845 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -0700846 self._cleanup_poweron()
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700847
848
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700849 def reboot(self, **dargs):
850 """
851 This function reboots the site host. The more generic
852 RemoteHost.reboot() performs sync and sleeps for 5
853 seconds. This is not necessary for Chrome OS devices as the
854 sync should be finished in a short time during the reboot
855 command.
856 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800857 if 'reboot_cmd' not in dargs:
858 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
859 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700860 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800861 if 'fastsync' not in dargs:
862 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700863 super(SiteHost, self).reboot(**dargs)
864
865
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700866 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800867 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700868
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800869 Tests for the following conditions:
870 1. All conditions tested by the parent version of this
871 function.
872 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -0700873 3. Sufficient space in /mnt/stateful_partition/encrypted.
874 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700875
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700876 """
877 super(SiteHost, self).verify_software()
878 self.check_diskspace(
879 '/mnt/stateful_partition',
880 global_config.global_config.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -0700881 'SERVER', 'gb_diskspace_required', type=float,
882 default=20.0))
883 self.check_diskspace(
884 '/mnt/stateful_partition/encrypted',
885 global_config.global_config.get_config_value(
886 'SERVER', 'gb_encrypted_diskspace_required', type=float,
887 default=0.1))
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800888 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -0500889 # Makes sure python is present, loads and can use built in functions.
890 # We have seen cases where importing cPickle fails with undefined
891 # symbols in cPickle.so.
892 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700893
894
Christopher Wileyd78249a2013-03-01 13:05:31 -0800895 def xmlrpc_connect(self, command, port, command_name=None,
896 ready_test_name=None, timeout_seconds=10):
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700897 """Connect to an XMLRPC server on the host.
898
899 The `command` argument should be a simple shell command that
900 starts an XMLRPC server on the given `port`. The command
901 must not daemonize, and must terminate cleanly on SIGTERM.
902 The command is started in the background on the host, and a
903 local XMLRPC client for the server is created and returned
904 to the caller.
905
906 Note that the process of creating an XMLRPC client makes no
907 attempt to connect to the remote server; the caller is
908 responsible for determining whether the server is running
909 correctly, and is ready to serve requests.
910
Christopher Wileyd78249a2013-03-01 13:05:31 -0800911 Optionally, the caller can pass ready_test_name, a string
912 containing the name of a method to call on the proxy. This
913 method should take no parameters and return successfully only
914 when the server is ready to process client requests. When
915 ready_test_name is set, xmlrpc_connect will block until the
916 proxy is ready, and throw a TestError if the server isn't
917 ready by timeout_seconds.
918
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700919 @param command Shell command to start the server.
920 @param port Port number on which the server is expected to
921 be serving.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800922 @param command_name String to use as input to `pkill` to
923 terminate the XMLRPC server on the host.
Christopher Wileyd78249a2013-03-01 13:05:31 -0800924 @param ready_test_name String containing the name of a
925 method defined on the XMLRPC server.
926 @param timeout_seconds Number of seconds to wait
927 for the server to become 'ready.' Will throw a
928 TestFail error if server is not ready in time.
929
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700930 """
931 self.xmlrpc_disconnect(port)
932
933 # Chrome OS on the target closes down most external ports
934 # for security. We could open the port, but doing that
935 # would conflict with security tests that check that only
936 # expected ports are open. So, to get to the port on the
937 # target we use an ssh tunnel.
938 local_port = utils.get_unused_port()
939 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
940 ssh_cmd = make_ssh_command(opts=tunnel_options)
941 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
942 logging.debug('Full tunnel command: %s', tunnel_cmd)
943 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
944 logging.debug('Started XMLRPC tunnel, local = %d'
945 ' remote = %d, pid = %d',
946 local_port, port, tunnel_proc.pid)
947
948 # Start the server on the host. Redirection in the command
949 # below is necessary, because 'ssh' won't terminate until
950 # background child processes close stdin, stdout, and
951 # stderr.
952 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
953 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
954 logging.debug('Started XMLRPC server on host %s, pid = %s',
955 self.hostname, remote_pid)
956
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800957 self._xmlrpc_proxy_map[port] = (command_name, tunnel_proc)
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700958 rpc_url = 'http://localhost:%d' % local_port
Christopher Wileyd78249a2013-03-01 13:05:31 -0800959 proxy = xmlrpclib.ServerProxy(rpc_url, allow_none=True)
960 if ready_test_name is not None:
J. Richard Barnette13eb7c02013-03-07 12:06:29 -0800961 # retry.retry logs each attempt; calculate delay_sec to
962 # keep log spam to a dull roar.
Christopher Wiley0ed712b2013-04-09 15:25:12 -0700963 @retry.retry((socket.error,
964 xmlrpclib.ProtocolError,
965 httplib.BadStatusLine),
Christopher Wileyd78249a2013-03-01 13:05:31 -0800966 timeout_min=timeout_seconds/60.0,
J. Richard Barnette13eb7c02013-03-07 12:06:29 -0800967 delay_sec=min(max(timeout_seconds/20.0, 0.1), 1))
Christopher Wileyd78249a2013-03-01 13:05:31 -0800968 def ready_test():
969 """ Call proxy.ready_test_name(). """
970 getattr(proxy, ready_test_name)()
971 successful = False
972 try:
973 logging.info('Waiting %d seconds for XMLRPC server '
974 'to start.', timeout_seconds)
975 ready_test()
976 successful = True
977 except retry.TimeoutException:
978 raise error.TestError('Unable to start XMLRPC server after '
979 '%d seconds.' % timeout_seconds)
980 finally:
981 if not successful:
982 logging.error('Failed to start XMLRPC server.')
983 self.xmlrpc_disconnect(port)
984 logging.info('XMLRPC server started successfully.')
985 return proxy
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700986
987 def xmlrpc_disconnect(self, port):
988 """Disconnect from an XMLRPC server on the host.
989
990 Terminates the remote XMLRPC server previously started for
991 the given `port`. Also closes the local ssh tunnel created
992 for the connection to the host. This function does not
993 directly alter the state of a previously returned XMLRPC
994 client object; however disconnection will cause all
995 subsequent calls to methods on the object to fail.
996
997 This function does nothing if requested to disconnect a port
998 that was not previously connected via `self.xmlrpc_connect()`
999
1000 @param port Port number passed to a previous call to
1001 `xmlrpc_connect()`
1002 """
1003 if port not in self._xmlrpc_proxy_map:
1004 return
1005 entry = self._xmlrpc_proxy_map[port]
1006 remote_name = entry[0]
1007 tunnel_proc = entry[1]
1008 if remote_name:
1009 # We use 'pkill' to find our target process rather than
1010 # a PID, because the host may have rebooted since
1011 # connecting, and we don't want to kill an innocent
1012 # process with the same PID.
1013 #
1014 # 'pkill' helpfully exits with status 1 if no target
1015 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -07001016 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001017 # status.
1018 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
1019
1020 if tunnel_proc.poll() is None:
1021 tunnel_proc.terminate()
1022 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
1023 else:
1024 logging.debug('Tunnel pid %d terminated early, status %d',
1025 tunnel_proc.pid, tunnel_proc.returncode)
1026 del self._xmlrpc_proxy_map[port]
1027
1028
1029 def xmlrpc_disconnect_all(self):
1030 """Disconnect all known XMLRPC proxy ports."""
1031 for port in self._xmlrpc_proxy_map.keys():
1032 self.xmlrpc_disconnect(port)
1033
1034
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001035 def _ping_check_status(self, status):
1036 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001037
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001038 @param status Check the ping status against this value.
1039 @return True iff `status` and the result of ping are the same
1040 (i.e. both True or both False).
1041
1042 """
1043 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1044 return not (status ^ (ping_val == 0))
1045
1046 def _ping_wait_for_status(self, status, timeout):
1047 """Wait for the host to have a given status (UP or DOWN).
1048
1049 Status is checked by polling. Polling will not last longer
1050 than the number of seconds in `timeout`. The polling
1051 interval will be long enough that only approximately
1052 _PING_WAIT_COUNT polling cycles will be executed, subject
1053 to a maximum interval of about one minute.
1054
1055 @param status Waiting will stop immediately if `ping` of the
1056 host returns this status.
1057 @param timeout Poll for at most this many seconds.
1058 @return True iff the host status from `ping` matched the
1059 requested status at the time of return.
1060
1061 """
1062 # _ping_check_status() takes about 1 second, hence the
1063 # "- 1" in the formula below.
1064 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1065 end_time = time.time() + timeout
1066 while time.time() <= end_time:
1067 if self._ping_check_status(status):
1068 return True
1069 if poll_interval > 0:
1070 time.sleep(poll_interval)
1071
1072 # The last thing we did was sleep(poll_interval), so it may
1073 # have been too long since the last `ping`. Check one more
1074 # time, just to be sure.
1075 return self._ping_check_status(status)
1076
1077 def ping_wait_up(self, timeout):
1078 """Wait for the host to respond to `ping`.
1079
1080 N.B. This method is not a reliable substitute for
1081 `wait_up()`, because a host that responds to ping will not
1082 necessarily respond to ssh. This method should only be used
1083 if the target DUT can be considered functional even if it
1084 can't be reached via ssh.
1085
1086 @param timeout Minimum time to allow before declaring the
1087 host to be non-responsive.
1088 @return True iff the host answered to ping before the timeout.
1089
1090 """
1091 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001092
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001093 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001094 """Wait until the host no longer responds to `ping`.
1095
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001096 This function can be used as a slightly faster version of
1097 `wait_down()`, by avoiding potentially long ssh timeouts.
1098
1099 @param timeout Minimum time to allow for the host to become
1100 non-responsive.
1101 @return True iff the host quit answering ping before the
1102 timeout.
1103
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001104 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001105 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001106
1107 def test_wait_for_sleep(self):
1108 """Wait for the client to enter low-power sleep mode.
1109
1110 The test for "is asleep" can't distinguish a system that is
1111 powered off; to confirm that the unit was asleep, it is
1112 necessary to force resume, and then call
1113 `test_wait_for_resume()`.
1114
1115 This function is expected to be called from a test as part
1116 of a sequence like the following:
1117
1118 ~~~~~~~~
1119 boot_id = host.get_boot_id()
1120 # trigger sleep on the host
1121 host.test_wait_for_sleep()
1122 # trigger resume on the host
1123 host.test_wait_for_resume(boot_id)
1124 ~~~~~~~~
1125
1126 @exception TestFail The host did not go to sleep within
1127 the allowed time.
1128 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001129 if not self.ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001130 raise error.TestFail(
1131 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001132 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001133
1134
1135 def test_wait_for_resume(self, old_boot_id):
1136 """Wait for the client to resume from low-power sleep mode.
1137
1138 The `old_boot_id` parameter should be the value from
1139 `get_boot_id()` obtained prior to entering sleep mode. A
1140 `TestFail` exception is raised if the boot id changes.
1141
1142 See @ref test_wait_for_sleep for more on this function's
1143 usage.
1144
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001145 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001146 target host went to sleep.
1147
1148 @exception TestFail The host did not respond within the
1149 allowed time.
1150 @exception TestFail The host responded, but the boot id test
1151 indicated a reboot rather than a sleep
1152 cycle.
1153 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001154 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001155 raise error.TestFail(
1156 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001157 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001158 else:
1159 new_boot_id = self.get_boot_id()
1160 if new_boot_id != old_boot_id:
1161 raise error.TestFail(
1162 'client rebooted, but sleep was expected'
1163 ' (old boot %s, new boot %s)'
1164 % (old_boot_id, new_boot_id))
1165
1166
1167 def test_wait_for_shutdown(self):
1168 """Wait for the client to shut down.
1169
1170 The test for "has shut down" can't distinguish a system that
1171 is merely asleep; to confirm that the unit was down, it is
1172 necessary to force boot, and then call test_wait_for_boot().
1173
1174 This function is expected to be called from a test as part
1175 of a sequence like the following:
1176
1177 ~~~~~~~~
1178 boot_id = host.get_boot_id()
1179 # trigger shutdown on the host
1180 host.test_wait_for_shutdown()
1181 # trigger boot on the host
1182 host.test_wait_for_boot(boot_id)
1183 ~~~~~~~~
1184
1185 @exception TestFail The host did not shut down within the
1186 allowed time.
1187 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001188 if not self.ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001189 raise error.TestFail(
1190 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001191 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001192
1193
1194 def test_wait_for_boot(self, old_boot_id=None):
1195 """Wait for the client to boot from cold power.
1196
1197 The `old_boot_id` parameter should be the value from
1198 `get_boot_id()` obtained prior to shutting down. A
1199 `TestFail` exception is raised if the boot id does not
1200 change. The boot id test is omitted if `old_boot_id` is not
1201 specified.
1202
1203 See @ref test_wait_for_shutdown for more on this function's
1204 usage.
1205
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001206 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001207 shut down.
1208
1209 @exception TestFail The host did not respond within the
1210 allowed time.
1211 @exception TestFail The host responded, but the boot id test
1212 indicated that there was no reboot.
1213 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001214 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001215 raise error.TestFail(
1216 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001217 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001218 elif old_boot_id:
1219 if self.get_boot_id() == old_boot_id:
1220 raise error.TestFail(
1221 'client is back up, but did not reboot'
1222 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -07001223
1224
1225 @staticmethod
1226 def check_for_rpm_support(hostname):
1227 """For a given hostname, return whether or not it is powered by an RPM.
1228
1229 @return None if this host does not follows the defined naming format
1230 for RPM powered DUT's in the lab. If it does follow the format,
1231 it returns a regular expression MatchObject instead.
1232 """
Richard Barnette82c35912012-11-20 10:09:10 -08001233 return re.match(SiteHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001234
1235
1236 def has_power(self):
1237 """For this host, return whether or not it is powered by an RPM.
1238
1239 @return True if this host is in the CROS lab and follows the defined
1240 naming format.
1241 """
1242 return SiteHost.check_for_rpm_support(self.hostname)
1243
1244
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001245 def _set_power(self, state, power_method):
1246 """Sets the power to the host via RPM, Servo or manual.
1247
1248 @param state Specifies which power state to set to DUT
1249 @param power_method Specifies which method of power control to
1250 use. By default "RPM" will be used. Valid values
1251 are the strings "RPM", "manual", "servoj10".
1252
1253 """
1254 ACCEPTABLE_STATES = ['ON', 'OFF']
1255
1256 if state.upper() not in ACCEPTABLE_STATES:
1257 raise error.TestError('State must be one of: %s.'
1258 % (ACCEPTABLE_STATES,))
1259
1260 if power_method == self.POWER_CONTROL_SERVO:
1261 logging.info('Setting servo port J10 to %s', state)
1262 self.servo.set('prtctl3_pwren', state.lower())
1263 time.sleep(self._USB_POWER_TIMEOUT)
1264 elif power_method == self.POWER_CONTROL_MANUAL:
1265 logging.info('You have %d seconds to set the AC power to %s.',
1266 self._POWER_CYCLE_TIMEOUT, state)
1267 time.sleep(self._POWER_CYCLE_TIMEOUT)
1268 else:
1269 if not self.has_power():
1270 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07001271 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1272 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
1273 hostname=self.hostname)
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001274 rpm_client.set_power(self.hostname, state.upper())
Simran Basid5e5e272012-09-24 15:23:59 -07001275
1276
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001277 def power_off(self, power_method=POWER_CONTROL_RPM):
1278 """Turn off power to this host via RPM, Servo or manual.
1279
1280 @param power_method Specifies which method of power control to
1281 use. By default "RPM" will be used. Valid values
1282 are the strings "RPM", "manual", "servoj10".
1283
1284 """
1285 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001286
1287
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001288 def power_on(self, power_method=POWER_CONTROL_RPM):
1289 """Turn on power to this host via RPM, Servo or manual.
1290
1291 @param power_method Specifies which method of power control to
1292 use. By default "RPM" will be used. Valid values
1293 are the strings "RPM", "manual", "servoj10".
1294
1295 """
1296 self._set_power('ON', power_method)
1297
1298
1299 def power_cycle(self, power_method=POWER_CONTROL_RPM):
1300 """Cycle power to this host by turning it OFF, then ON.
1301
1302 @param power_method Specifies which method of power control to
1303 use. By default "RPM" will be used. Valid values
1304 are the strings "RPM", "manual", "servoj10".
1305
1306 """
1307 if power_method in (self.POWER_CONTROL_SERVO,
1308 self.POWER_CONTROL_MANUAL):
1309 self.power_off(power_method=power_method)
1310 time.sleep(self._POWER_CYCLE_TIMEOUT)
1311 self.power_on(power_method=power_method)
1312 else:
1313 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001314
1315
1316 def get_platform(self):
1317 """Determine the correct platform label for this host.
1318
1319 @returns a string representing this host's platform.
1320 """
1321 crossystem = utils.Crossystem(self)
1322 crossystem.init()
1323 # Extract fwid value and use the leading part as the platform id.
1324 # fwid generally follow the format of {platform}.{firmware version}
1325 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1326 platform = crossystem.fwid().split('.')[0].lower()
1327 # Newer platforms start with 'Google_' while the older ones do not.
1328 return platform.replace('google_', '')
1329
1330
Aviv Keshet74c89a92013-02-04 15:18:30 -08001331 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07001332 def get_board(self):
1333 """Determine the correct board label for this host.
1334
1335 @returns a string representing this host's board.
1336 """
1337 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1338 run_method=self.run)
1339 board = release_info['CHROMEOS_RELEASE_BOARD']
1340 # Devices in the lab generally have the correct board name but our own
1341 # development devices have {board_name}-signed-{key_type}. The board
1342 # name may also begin with 'x86-' which we need to keep.
Simran Basi833814b2013-01-29 13:13:43 -08001343 board_format_string = ds_constants.BOARD_PREFIX + '%s'
Simran Basic6f1f7a2012-10-16 10:47:46 -07001344 if 'x86' not in board:
Simran Basi833814b2013-01-29 13:13:43 -08001345 return board_format_string % board.split('-')[0]
1346 return board_format_string % '-'.join(board.split('-')[0:2])
Simran Basic6f1f7a2012-10-16 10:47:46 -07001347
1348
Aviv Keshet74c89a92013-02-04 15:18:30 -08001349 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001350 def has_lightsensor(self):
1351 """Determine the correct board label for this host.
1352
1353 @returns the string 'lightsensor' if this host has a lightsensor or
1354 None if it does not.
1355 """
1356 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08001357 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07001358 try:
1359 # Run the search cmd following the symlinks. Stderr_tee is set to
1360 # None as there can be a symlink loop, but the command will still
1361 # execute correctly with a few messages printed to stderr.
1362 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
1363 return 'lightsensor'
1364 except error.AutoservRunError:
1365 # egrep exited with a return code of 1 meaning none of the possible
1366 # lightsensor files existed.
1367 return None
1368
1369
Aviv Keshet74c89a92013-02-04 15:18:30 -08001370 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001371 def has_bluetooth(self):
1372 """Determine the correct board label for this host.
1373
1374 @returns the string 'bluetooth' if this host has bluetooth or
1375 None if it does not.
1376 """
1377 try:
1378 self.run('test -d /sys/class/bluetooth/hci0')
1379 # test exited with a return code of 0.
1380 return 'bluetooth'
1381 except error.AutoservRunError:
1382 # test exited with a return code 1 meaning the directory did not
1383 # exist.
1384 return None
1385
1386
1387 def get_labels(self):
1388 """Return a list of labels for this given host.
1389
1390 This is the main way to retrieve all the automatic labels for a host
1391 as it will run through all the currently implemented label functions.
1392 """
1393 labels = []
Richard Barnette82c35912012-11-20 10:09:10 -08001394 for label_function in self._LABEL_FUNCTIONS:
Simran Basic6f1f7a2012-10-16 10:47:46 -07001395 label = label_function(self)
1396 if label:
1397 labels.append(label)
1398 return labels