blob: 7ae58bf39faabe50a644235ee304a7685d6cd742 [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
J. Richard Barnette1d78b012012-05-15 13:56:30 -07006import logging
Simran Basid5e5e272012-09-24 15:23:59 -07007import re
J. Richard Barnette1d78b012012-05-15 13:56:30 -07008import subprocess
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07009import time
J. Richard Barnette1d78b012012-05-15 13:56:30 -070010import xmlrpclib
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070011
J. Richard Barnette45e93de2012-04-11 17:24:15 -070012from autotest_lib.client.bin import utils
Richard Barnette0c73ffc2012-11-19 15:21:18 -080013from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib import global_config
J. Richard Barnette45e93de2012-04-11 17:24:15 -070015from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette03a0c132012-11-05 12:40:35 -080016from autotest_lib.client.common_lib.cros import dev_server
Richard Barnette82c35912012-11-20 10:09:10 -080017from autotest_lib.client.cros import constants
J. Richard Barnette45e93de2012-04-11 17:24:15 -070018from autotest_lib.server import autoserv_parser
Chris Sosaf4d43ff2012-10-30 11:21:05 -070019from autotest_lib.server import autotest
J. Richard Barnette45e93de2012-04-11 17:24:15 -070020from autotest_lib.server import site_host_attributes
J. Richard Barnette67ccb872012-04-19 16:34:56 -070021from autotest_lib.server.cros import servo
Scott Zawalski89c44dd2013-02-26 09:28:02 -050022from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
23from autotest_lib.server.cros.dynamic_suite import tools
J. Richard Barnette45e93de2012-04-11 17:24:15 -070024from autotest_lib.server.hosts import remote
Simran Basidcff4252012-11-20 16:13:20 -080025from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070026
Richard Barnette82c35912012-11-20 10:09:10 -080027# Importing frontend.afe.models requires a full Autotest
28# installation (with the Django modules), not just the source
29# repository. Most developers won't have the full installation, so
30# the imports below will fail for them.
31#
32# The fix is to catch import exceptions, and set `models` to `None`
33# on failure. This has the side effect that
34# SiteHost._get_board_from_afe() will fail: That will manifest as
35# failures during Repair jobs leaving the DUT as "Repair Failed".
36# In practice, you can't test Repair jobs without a full
37# installation, so that kind of failure isn't expected.
38try:
J. Richard Barnette7214e0b2013-02-06 15:20:49 -080039 # pylint: disable=W0611
Richard Barnette82c35912012-11-20 10:09:10 -080040 from autotest_lib.frontend import setup_django_environment
41 from autotest_lib.frontend.afe import models
42except:
43 models = None
44
Simran Basid5e5e272012-09-24 15:23:59 -070045
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -080046def _make_servo_hostname(hostname):
47 host_parts = hostname.split('.')
48 host_parts[0] = host_parts[0] + '-servo'
49 return '.'.join(host_parts)
50
51
52def _get_lab_servo(target_hostname):
53 """Instantiate a Servo for |target_hostname| in the lab.
54
55 Assuming that |target_hostname| is a device in the CrOS test
56 lab, create and return a Servo object pointed at the servo
57 attached to that DUT. The servo in the test lab is assumed
58 to already have servod up and running on it.
59
60 @param target_hostname: device whose servo we want to target.
61 @return an appropriately configured Servo instance.
62 """
63 servo_host = _make_servo_hostname(target_hostname)
64 if utils.host_is_in_lab_zone(servo_host):
65 try:
J. Richard Barnetted5f807a2013-02-11 16:51:00 -080066 return servo.Servo(servo_host=servo_host)
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -080067 except: # pylint: disable=W0702
68 # TODO(jrbarnette): Long-term, if we can't get to
69 # a servo in the lab, we want to fail, so we should
70 # pass any exceptions along. Short-term, we're not
71 # ready to rely on servo, so we ignore failures.
72 pass
73 return None
74
75
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070076def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
77 connect_timeout=None, alive_interval=None):
78 """Override default make_ssh_command to use options tuned for Chrome OS.
79
80 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070081 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
82 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070083
Dale Curtisaa5eedb2011-08-23 16:18:52 -070084 - ServerAliveInterval=180; which causes SSH to ping connection every
85 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
86 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
87 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070088
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070089 - ServerAliveCountMax=3; consistency with remote_access.sh.
90
91 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
92 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070093
94 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
95 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070096
97 - SSH protocol forced to 2; needed for ServerAliveInterval.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -080098
99 @param user User name to use for the ssh connection.
100 @param port Port on the target host to use for ssh connection.
101 @param opts Additional options to the ssh command.
102 @param hosts_file Ignored.
103 @param connect_timeout Ignored.
104 @param alive_interval Ignored.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -0700105 """
106 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
107 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -0700108 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
109 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
110 ' -o Protocol=2 -l %s -p %d')
Dale Curtiscb7bfaf2011-06-07 16:21:57 -0700111 return base_command % (opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700112
113
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800114
Aviv Keshet74c89a92013-02-04 15:18:30 -0800115def add_label_detector(label_function_list, label_list=None, label=None):
116 """Decorator used to group functions together into the provided list.
117 @param label_function_list: List of label detecting functions to add
118 decorated function to.
119 @param label_list: List of detectable labels to add detectable labels to.
120 (Default: None)
121 @param label: Label string that is detectable by this detection function
122 (Default: None)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800123 """
Simran Basic6f1f7a2012-10-16 10:47:46 -0700124 def add_func(func):
Aviv Keshet74c89a92013-02-04 15:18:30 -0800125 """
126 @param func: The function to be added as a detector.
127 """
128 label_function_list.append(func)
129 if label and label_list is not None:
130 label_list.append(label)
Simran Basic6f1f7a2012-10-16 10:47:46 -0700131 return func
132 return add_func
133
134
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700135class SiteHost(remote.RemoteHost):
136 """Chromium OS specific subclass of Host."""
137
138 _parser = autoserv_parser.autoserv_parser
139
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800140 # Time to wait for new kernel to be marked successful after
141 # auto update.
Chris Masone163cead2012-05-16 11:49:48 -0700142 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700143
Richard Barnette03a0c132012-11-05 12:40:35 -0800144 # Timeout values (in seconds) associated with various Chrome OS
145 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700146 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800147 # In general, a good rule of thumb is that the timeout can be up
148 # to twice the typical measured value on the slowest platform.
149 # The times here have not necessarily been empirically tested to
150 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700151 #
152 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800153 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
154 # time to restart the netwowrk.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700155 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800156 # other things, this must account for the 30 second dev-mode
157 # screen delay and time to start the network,
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700158 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800159 # including the 30 second dev-mode delay and time to start the
160 # network,
161 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700162 # REBOOT_TIMEOUT: Combination of shutdown and reboot times.
Richard Barnette03a0c132012-11-05 12:40:35 -0800163 # _INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700164
165 SLEEP_TIMEOUT = 2
166 RESUME_TIMEOUT = 5
167 BOOT_TIMEOUT = 45
168 USB_BOOT_TIMEOUT = 150
169 SHUTDOWN_TIMEOUT = 5
170 REBOOT_TIMEOUT = SHUTDOWN_TIMEOUT + BOOT_TIMEOUT
Richard Barnette03a0c132012-11-05 12:40:35 -0800171 _INSTALL_TIMEOUT = 240
172
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800173 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
174 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
175 _USB_POWER_TIMEOUT = 5
176 _POWER_CYCLE_TIMEOUT = 10
177
Richard Barnette03a0c132012-11-05 12:40:35 -0800178 _DEFAULT_SERVO_URL_FORMAT = ('/static/servo-images/'
179 '%(board)s_test_image.bin')
180
181 # TODO(jrbarnette): Servo repair is restricted to x86-alex,
182 # because the existing servo client code won't work on other
183 # boards. http://crosbug.com/36973
184 _SERVO_REPAIR_WHITELIST = [ 'x86-alex' ]
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800185
186
Richard Barnette82c35912012-11-20 10:09:10 -0800187 _RPM_RECOVERY_BOARDS = global_config.global_config.get_config_value('CROS',
188 'rpm_recovery_boards', type=str).split(',')
189
190 _MAX_POWER_CYCLE_ATTEMPTS = 6
191 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
192 _RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
193 'host[0-9]+')
194 _LIGHTSENSOR_FILES = ['in_illuminance0_input',
195 'in_illuminance0_raw',
196 'illuminance0_input']
197 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
198 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800199 _DETECTABLE_LABELS = []
200 label_decorator = functools.partial(add_label_detector, _LABEL_FUNCTIONS,
201 _DETECTABLE_LABELS)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700202
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800203 # Constants used in ping_wait_up() and ping_wait_down().
204 #
205 # _PING_WAIT_COUNT is the approximate number of polling
206 # cycles to use when waiting for a host state change.
207 #
208 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
209 # for arguments to the internal _ping_wait_for_status()
210 # method.
211 _PING_WAIT_COUNT = 40
212 _PING_STATUS_DOWN = False
213 _PING_STATUS_UP = True
214
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800215 # Allowed values for the power_method argument.
216
217 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
218 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
219 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
220 POWER_CONTROL_RPM = 'RPM'
221 POWER_CONTROL_SERVO = 'servoj10'
222 POWER_CONTROL_MANUAL = 'manual'
223
224 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
225 POWER_CONTROL_SERVO,
226 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800227
J. Richard Barnette964fba02012-10-24 17:34:29 -0700228 @staticmethod
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800229 def get_servo_arguments(args_dict):
230 """Extract servo options from `args_dict` and return the result.
231
232 Take the provided dictionary of argument options and return
233 a subset that represent standard arguments needed to
234 construct a servo object for a host. The intent is to
235 provide standard argument processing from run_remote_tests
236 for tests that require a servo to operate.
237
238 Recommended usage:
239 ~~~~~~~~
240 args_dict = utils.args_to_dict(args)
241 servo_args = hosts.SiteHost.get_servo_arguments(args_dict)
242 host = hosts.create_host(machine, servo_args=servo_args)
243 ~~~~~~~~
244
245 @param args_dict Dictionary from which to extract the servo
246 arguments.
247 """
J. Richard Barnette964fba02012-10-24 17:34:29 -0700248 servo_args = {}
249 for arg in ('servo_host', 'servo_port'):
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800250 if arg in args_dict:
251 servo_args[arg] = args_dict[arg]
J. Richard Barnette964fba02012-10-24 17:34:29 -0700252 return servo_args
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700253
J. Richard Barnette964fba02012-10-24 17:34:29 -0700254
255 def _initialize(self, hostname, servo_args=None, *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700256 """Initialize superclasses, and |self.servo|.
257
258 For creating the host servo object, there are three
259 possibilities: First, if the host is a lab system known to
260 have a servo board, we connect to that servo unconditionally.
261 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700262 servo features for testing, it will pass settings for
263 `servo_host`, `servo_port`, or both. If neither of these
264 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700265
266 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700267 super(SiteHost, self)._initialize(hostname=hostname,
268 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700269 # self.env is a dictionary of environment variable settings
270 # to be exported for commands run on the host.
271 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
272 # errors that might happen.
273 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700274 self._xmlrpc_proxy_map = {}
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -0800275 self.servo = _get_lab_servo(hostname)
J. Richard Barnettead7da482012-10-30 16:46:52 -0700276 if not self.servo and servo_args is not None:
J. Richard Barnette964fba02012-10-24 17:34:29 -0700277 self.servo = servo.Servo(**servo_args)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700278
279
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500280 def get_repair_image_name(self):
281 """Generate a image_name from variables in the global config.
282
283 @returns a str of $board-version/$BUILD.
284
285 """
286 stable_version = global_config.global_config.get_config_value(
287 'CROS', 'stable_cros_version')
288 build_pattern = global_config.global_config.get_config_value(
289 'CROS', 'stable_build_pattern')
290 board = self._get_board_from_afe()
291 if board is None:
292 raise error.AutoservError('DUT has no board attribute, '
293 'cannot be repaired.')
294 return build_pattern % (board, stable_version)
295
296
297 def clear_cros_version_labels_and_job_repo_url(self):
298 """Clear cros_version labels and host attribute job_repo_url."""
299 host_model = models.Host.objects.get(hostname=self.hostname)
300 for label in host_model.labels.iterator():
301 if not label.name.startswith(ds_constants.VERSION_PREFIX):
302 continue
303 label = models.Label.smart_get(label.name)
304 label.host_set.remove(host_model)
305
306 host_model.set_or_delete_attribute('job_repo_url', None)
307
308
Chris Sosaa3ac2152012-05-23 22:23:13 -0700309 def machine_install(self, update_url=None, force_update=False,
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500310 local_devserver=False, repair=False):
311 """Install the DUT.
312
313 @param update_url: The url to use for the update
314 pattern: http://$devserver:###/update/$build
315 If update_url is None and repair is True we will install the
316 stable image listed in global_config under
317 CROS.stable_cros_version.
318 @param force_update: Force an update even if the version installed
319 is the same. Default:False
320 @param local_devserver: Used by run_remote_test to allow people to
321 use their local devserver. Default: False
322 @param repair: Whether or not we are in repair mode. This adds special
323 cases for repairing a machine like starting update_engine.
324 Setting repair to True sets force_update to True as well.
325 default: False
326 @raises autoupdater.ChromiumOSError
327
328 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700329 if not update_url and self._parser.options.image:
330 update_url = self._parser.options.image
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500331 elif not update_url and not repair:
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700332 raise autoupdater.ChromiumOSError(
333 'Update failed. No update URL provided.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500334 elif not update_url and repair:
335 image_name = self.get_repair_image_name()
336 devserver = dev_server.ImageServer.resolve(image_name)
337 logging.info('Staging repair build: %s', image_name)
338 devserver.trigger_download(image_name, synchronous=False)
339 self.clear_cros_version_labels_and_job_repo_url()
340 update_url = tools.image_url_pattern() % (devserver.url(),
341 image_name)
342
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700343
Chris Sosafab08082013-01-04 15:21:20 -0800344 # In case the system is in a bad state, we always reboot the machine
345 # before machine_install.
346 self.reboot(timeout=60, wait=True)
347
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500348 if repair:
349 self.run('stop update-engine; start update-engine')
350 force_update = True
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700351 # Attempt to update the system.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700352 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
353 local_devserver=local_devserver)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700354 if updater.run_update(force_update):
355 # Figure out active and inactive kernel.
356 active_kernel, inactive_kernel = updater.get_kernel_state()
357
358 # Ensure inactive kernel has higher priority than active.
359 if (updater.get_kernel_priority(inactive_kernel)
360 < updater.get_kernel_priority(active_kernel)):
361 raise autoupdater.ChromiumOSError(
362 'Update failed. The priority of the inactive kernel'
363 ' partition is less than that of the active kernel'
364 ' partition.')
365
Scott Zawalski21902002012-09-19 17:57:00 -0400366 update_engine_log = '/var/log/update_engine.log'
367 logging.info('Dumping %s', update_engine_log)
368 self.run('cat %s' % update_engine_log)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800369 # Updater has returned successfully; reboot the host.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700370 self.reboot(timeout=60, wait=True)
Chris Sosae146ed82012-09-19 17:58:36 -0700371 # Touch the lab machine file to leave a marker that distinguishes
372 # this image from other test images.
Richard Barnette82c35912012-11-20 10:09:10 -0800373 self.run('touch %s' % self._LAB_MACHINE_FILE)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700374
375 # Following the reboot, verify the correct version.
376 updater.check_version()
377
378 # Figure out newly active kernel.
379 new_active_kernel, _ = updater.get_kernel_state()
380
381 # Ensure that previously inactive kernel is now the active kernel.
382 if new_active_kernel != inactive_kernel:
383 raise autoupdater.ChromiumOSError(
384 'Update failed. New kernel partition is not active after'
385 ' boot.')
386
387 host_attributes = site_host_attributes.HostAttributes(self.hostname)
388 if host_attributes.has_chromeos_firmware:
389 # Wait until tries == 0 and success, or until timeout.
390 utils.poll_for_condition(
391 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
392 and updater.get_kernel_success(new_active_kernel)),
393 exception=autoupdater.ChromiumOSError(
394 'Update failed. Timed out waiting for system to mark'
395 ' new kernel as successful.'),
396 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
397
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700398 # Clean up any old autotest directories which may be lying around.
399 for path in global_config.global_config.get_config_value(
400 'AUTOSERV', 'client_autodir_paths', type=list):
401 self.run('rm -rf ' + path)
402
403
Richard Barnette82c35912012-11-20 10:09:10 -0800404 def _get_board_from_afe(self):
405 """Retrieve this host's board from its labels in the AFE.
406
407 Looks for a host label of the form "board:<board>", and
408 returns the "<board>" part of the label. `None` is returned
409 if there is not a single, unique label matching the pattern.
410
411 @returns board from label, or `None`.
412 """
413 host_model = models.Host.objects.get(hostname=self.hostname)
414 board_labels = filter(lambda l: l.name.startswith('board:'),
415 host_model.labels.all())
416 board_name = None
417 if len(board_labels) == 1:
418 board_name = board_labels[0].name.split(':', 1)[1]
419 elif len(board_labels) == 0:
420 logging.error('Host %s does not have a board label.',
421 self.hostname)
422 else:
423 logging.error('Host %s has multiple board labels.',
424 self.hostname)
425 return board_name
426
427
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500428 def _install_repair(self):
429 """Attempt to repair this host using upate-engine.
430
431 If the host is up, try installing the DUT with a stable
432 "repair" version of Chrome OS as defined in the global_config
433 under CROS.stable_cros_version.
434
435 @returns True if successful, False if update_engine failed.
436
437 """
438 if not self.is_up():
439 return False
440
441 logging.info('Attempting to reimage machine to repair image.')
442 try:
443 self.machine_install(repair=True)
444 except autoupdater.ChromiumOSError:
445 logging.info('Repair via install failed.')
446 return False
447
448 return True
449
450
Richard Barnette03a0c132012-11-05 12:40:35 -0800451 def _servo_repair(self, board):
452 """Attempt to repair this host using an attached Servo.
453
454 Re-install the OS on the DUT by 1) installing a test image
455 on a USB storage device attached to the Servo board,
456 2) booting that image in recovery mode, and then
457 3) installing the image.
458
459 """
460 server = dev_server.ImageServer.devserver_url_for_servo(board)
461 image = server + (self._DEFAULT_SERVO_URL_FORMAT %
462 { 'board': board })
463 self.servo.install_recovery_image(image)
464 if not self.wait_up(timeout=self.USB_BOOT_TIMEOUT):
465 raise error.AutoservError('DUT failed to boot from USB'
466 ' after %d seconds' %
467 self.USB_BOOT_TIMEOUT)
468 self.run('chromeos-install --yes',
469 timeout=self._INSTALL_TIMEOUT)
470 self.servo.power_long_press()
471 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
472 self.servo.power_short_press()
473 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
474 raise error.AutoservError('DUT failed to reboot installed '
475 'test image after %d seconds' %
476 self.BOOT_TIMEOUT)
477
478
Richard Barnette82c35912012-11-20 10:09:10 -0800479 def _powercycle_to_repair(self):
480 """Utilize the RPM Infrastructure to bring the host back up.
481
482 If the host is not up/repaired after the first powercycle we utilize
483 auto fallback to the last good install by powercycling and rebooting the
484 host 6 times.
485 """
486 logging.info('Attempting repair via RPM powercycle.')
487 failed_cycles = 0
488 self.power_cycle()
489 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
490 failed_cycles += 1
491 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
492 raise error.AutoservError('Powercycled host %s %d times; '
493 'device did not come back online.' %
494 (self.hostname, failed_cycles))
495 self.power_cycle()
496 if failed_cycles == 0:
497 logging.info('Powercycling was successful first time.')
498 else:
499 logging.info('Powercycling was successful after %d failures.',
500 failed_cycles)
501
502
503 def repair_full(self):
504 """Repair a host for repair level NO_PROTECTION.
505
506 This overrides the base class function for repair; it does
507 not call back to the parent class, but instead offers a
508 simplified implementation based on the capabilities in the
509 Chrome OS test lab.
510
511 Repair follows this sequence:
512 1. If the DUT passes `self.verify()`, do nothing.
513 2. If the DUT can be power-cycled via RPM, try to repair
514 by power-cycling.
515
516 As with the parent method, the last operation performed on
517 the DUT must be to call `self.verify()`; if that call fails,
518 the exception it raises is passed back to the caller.
519 """
520 try:
521 self.verify()
522 except:
523 host_board = self._get_board_from_afe()
Richard Barnette03a0c132012-11-05 12:40:35 -0800524 if host_board is None:
525 logging.error('host %s has no board; failing repair',
526 self.hostname)
Richard Barnette82c35912012-11-20 10:09:10 -0800527 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500528
529 reimage_success = self._install_repair()
530 # TODO(scottz): All repair pathways should be executed until we've
531 # exhausted all options. Below we favor servo over powercycle when
532 # we really should be falling back to power if servo fails.
533 if (not reimage_success and self.servo and
Richard Barnette03a0c132012-11-05 12:40:35 -0800534 host_board in self._SERVO_REPAIR_WHITELIST):
535 self._servo_repair(host_board)
536 elif (self.has_power() and
537 host_board in self._RPM_RECOVERY_BOARDS):
538 self._powercycle_to_repair()
539 else:
540 logging.error('host %s has no servo and no RPM control; '
541 'failing repair', self.hostname)
542 raise
Richard Barnette82c35912012-11-20 10:09:10 -0800543 self.verify()
544
545
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700546 def close(self):
547 super(SiteHost, self).close()
548 self.xmlrpc_disconnect_all()
549
550
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700551 def cleanup(self):
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700552 client_at = autotest.Autotest(self)
Richard Barnette82c35912012-11-20 10:09:10 -0800553 self.run('rm -f %s' % constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500554 try:
555 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
556 '_clear_login_prompt_state')
557 self.run('restart ui')
558 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
559 '_wait_for_login_prompt')
Alex Millerf4517962013-02-25 15:03:02 -0800560 except (error.AutotestRunError, error.AutoservRunError):
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500561 logging.warn('Unable to restart ui, rebooting device.')
562 # Since restarting the UI fails fall back to normal Autotest
563 # cleanup routines, i.e. reboot the machine.
564 super(SiteHost, self).cleanup()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700565
566
Simran Basi154f5582012-10-23 16:27:11 -0700567 # TODO (sbasi) crosbug.com/35656
568 # Renamed the sitehost cleanup method so we don't go down this pathway.
569 # def cleanup(self):
570 def cleanup_poweron(self):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700571 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700572 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700573 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700574 try:
575 self.power_on()
Chris Sosafab08082013-01-04 15:21:20 -0800576 except rpm_client.RemotePowerException:
Simran Basifd23fb22012-10-22 17:56:22 -0700577 # If cleanup has completed but there was an issue with the RPM
578 # Infrastructure, log an error message rather than fail cleanup
579 logging.error('Failed to turn Power On for this host after '
580 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700581
582
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700583 def reboot(self, **dargs):
584 """
585 This function reboots the site host. The more generic
586 RemoteHost.reboot() performs sync and sleeps for 5
587 seconds. This is not necessary for Chrome OS devices as the
588 sync should be finished in a short time during the reboot
589 command.
590 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800591 if 'reboot_cmd' not in dargs:
592 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
593 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700594 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800595 if 'fastsync' not in dargs:
596 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700597 super(SiteHost, self).reboot(**dargs)
598
599
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700600 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800601 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700602
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800603 Tests for the following conditions:
604 1. All conditions tested by the parent version of this
605 function.
606 2. Sufficient space in /mnt/stateful_partition.
607 3. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700608
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700609 """
610 super(SiteHost, self).verify_software()
611 self.check_diskspace(
612 '/mnt/stateful_partition',
613 global_config.global_config.get_config_value(
614 'SERVER', 'gb_diskspace_required', type=int,
615 default=20))
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800616 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -0500617 # Makes sure python is present, loads and can use built in functions.
618 # We have seen cases where importing cPickle fails with undefined
619 # symbols in cPickle.so.
620 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700621
622
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800623 def xmlrpc_connect(self, command, port, command_name=None):
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700624 """Connect to an XMLRPC server on the host.
625
626 The `command` argument should be a simple shell command that
627 starts an XMLRPC server on the given `port`. The command
628 must not daemonize, and must terminate cleanly on SIGTERM.
629 The command is started in the background on the host, and a
630 local XMLRPC client for the server is created and returned
631 to the caller.
632
633 Note that the process of creating an XMLRPC client makes no
634 attempt to connect to the remote server; the caller is
635 responsible for determining whether the server is running
636 correctly, and is ready to serve requests.
637
638 @param command Shell command to start the server.
639 @param port Port number on which the server is expected to
640 be serving.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800641 @param command_name String to use as input to `pkill` to
642 terminate the XMLRPC server on the host.
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700643 """
644 self.xmlrpc_disconnect(port)
645
646 # Chrome OS on the target closes down most external ports
647 # for security. We could open the port, but doing that
648 # would conflict with security tests that check that only
649 # expected ports are open. So, to get to the port on the
650 # target we use an ssh tunnel.
651 local_port = utils.get_unused_port()
652 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
653 ssh_cmd = make_ssh_command(opts=tunnel_options)
654 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
655 logging.debug('Full tunnel command: %s', tunnel_cmd)
656 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
657 logging.debug('Started XMLRPC tunnel, local = %d'
658 ' remote = %d, pid = %d',
659 local_port, port, tunnel_proc.pid)
660
661 # Start the server on the host. Redirection in the command
662 # below is necessary, because 'ssh' won't terminate until
663 # background child processes close stdin, stdout, and
664 # stderr.
665 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
666 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
667 logging.debug('Started XMLRPC server on host %s, pid = %s',
668 self.hostname, remote_pid)
669
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800670 self._xmlrpc_proxy_map[port] = (command_name, tunnel_proc)
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700671 rpc_url = 'http://localhost:%d' % local_port
672 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
673
674
675 def xmlrpc_disconnect(self, port):
676 """Disconnect from an XMLRPC server on the host.
677
678 Terminates the remote XMLRPC server previously started for
679 the given `port`. Also closes the local ssh tunnel created
680 for the connection to the host. This function does not
681 directly alter the state of a previously returned XMLRPC
682 client object; however disconnection will cause all
683 subsequent calls to methods on the object to fail.
684
685 This function does nothing if requested to disconnect a port
686 that was not previously connected via `self.xmlrpc_connect()`
687
688 @param port Port number passed to a previous call to
689 `xmlrpc_connect()`
690 """
691 if port not in self._xmlrpc_proxy_map:
692 return
693 entry = self._xmlrpc_proxy_map[port]
694 remote_name = entry[0]
695 tunnel_proc = entry[1]
696 if remote_name:
697 # We use 'pkill' to find our target process rather than
698 # a PID, because the host may have rebooted since
699 # connecting, and we don't want to kill an innocent
700 # process with the same PID.
701 #
702 # 'pkill' helpfully exits with status 1 if no target
703 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700704 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700705 # status.
706 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
707
708 if tunnel_proc.poll() is None:
709 tunnel_proc.terminate()
710 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
711 else:
712 logging.debug('Tunnel pid %d terminated early, status %d',
713 tunnel_proc.pid, tunnel_proc.returncode)
714 del self._xmlrpc_proxy_map[port]
715
716
717 def xmlrpc_disconnect_all(self):
718 """Disconnect all known XMLRPC proxy ports."""
719 for port in self._xmlrpc_proxy_map.keys():
720 self.xmlrpc_disconnect(port)
721
722
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800723 def _ping_check_status(self, status):
724 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700725
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800726 @param status Check the ping status against this value.
727 @return True iff `status` and the result of ping are the same
728 (i.e. both True or both False).
729
730 """
731 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
732 return not (status ^ (ping_val == 0))
733
734 def _ping_wait_for_status(self, status, timeout):
735 """Wait for the host to have a given status (UP or DOWN).
736
737 Status is checked by polling. Polling will not last longer
738 than the number of seconds in `timeout`. The polling
739 interval will be long enough that only approximately
740 _PING_WAIT_COUNT polling cycles will be executed, subject
741 to a maximum interval of about one minute.
742
743 @param status Waiting will stop immediately if `ping` of the
744 host returns this status.
745 @param timeout Poll for at most this many seconds.
746 @return True iff the host status from `ping` matched the
747 requested status at the time of return.
748
749 """
750 # _ping_check_status() takes about 1 second, hence the
751 # "- 1" in the formula below.
752 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
753 end_time = time.time() + timeout
754 while time.time() <= end_time:
755 if self._ping_check_status(status):
756 return True
757 if poll_interval > 0:
758 time.sleep(poll_interval)
759
760 # The last thing we did was sleep(poll_interval), so it may
761 # have been too long since the last `ping`. Check one more
762 # time, just to be sure.
763 return self._ping_check_status(status)
764
765 def ping_wait_up(self, timeout):
766 """Wait for the host to respond to `ping`.
767
768 N.B. This method is not a reliable substitute for
769 `wait_up()`, because a host that responds to ping will not
770 necessarily respond to ssh. This method should only be used
771 if the target DUT can be considered functional even if it
772 can't be reached via ssh.
773
774 @param timeout Minimum time to allow before declaring the
775 host to be non-responsive.
776 @return True iff the host answered to ping before the timeout.
777
778 """
779 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700780
Andrew Bresticker678c0c72013-01-22 10:44:09 -0800781 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700782 """Wait until the host no longer responds to `ping`.
783
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800784 This function can be used as a slightly faster version of
785 `wait_down()`, by avoiding potentially long ssh timeouts.
786
787 @param timeout Minimum time to allow for the host to become
788 non-responsive.
789 @return True iff the host quit answering ping before the
790 timeout.
791
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700792 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800793 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700794
795 def test_wait_for_sleep(self):
796 """Wait for the client to enter low-power sleep mode.
797
798 The test for "is asleep" can't distinguish a system that is
799 powered off; to confirm that the unit was asleep, it is
800 necessary to force resume, and then call
801 `test_wait_for_resume()`.
802
803 This function is expected to be called from a test as part
804 of a sequence like the following:
805
806 ~~~~~~~~
807 boot_id = host.get_boot_id()
808 # trigger sleep on the host
809 host.test_wait_for_sleep()
810 # trigger resume on the host
811 host.test_wait_for_resume(boot_id)
812 ~~~~~~~~
813
814 @exception TestFail The host did not go to sleep within
815 the allowed time.
816 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -0800817 if not self.ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700818 raise error.TestFail(
819 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700820 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700821
822
823 def test_wait_for_resume(self, old_boot_id):
824 """Wait for the client to resume from low-power sleep mode.
825
826 The `old_boot_id` parameter should be the value from
827 `get_boot_id()` obtained prior to entering sleep mode. A
828 `TestFail` exception is raised if the boot id changes.
829
830 See @ref test_wait_for_sleep for more on this function's
831 usage.
832
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800833 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700834 target host went to sleep.
835
836 @exception TestFail The host did not respond within the
837 allowed time.
838 @exception TestFail The host responded, but the boot id test
839 indicated a reboot rather than a sleep
840 cycle.
841 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700842 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700843 raise error.TestFail(
844 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700845 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700846 else:
847 new_boot_id = self.get_boot_id()
848 if new_boot_id != old_boot_id:
849 raise error.TestFail(
850 'client rebooted, but sleep was expected'
851 ' (old boot %s, new boot %s)'
852 % (old_boot_id, new_boot_id))
853
854
855 def test_wait_for_shutdown(self):
856 """Wait for the client to shut down.
857
858 The test for "has shut down" can't distinguish a system that
859 is merely asleep; to confirm that the unit was down, it is
860 necessary to force boot, and then call test_wait_for_boot().
861
862 This function is expected to be called from a test as part
863 of a sequence like the following:
864
865 ~~~~~~~~
866 boot_id = host.get_boot_id()
867 # trigger shutdown on the host
868 host.test_wait_for_shutdown()
869 # trigger boot on the host
870 host.test_wait_for_boot(boot_id)
871 ~~~~~~~~
872
873 @exception TestFail The host did not shut down within the
874 allowed time.
875 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -0800876 if not self.ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700877 raise error.TestFail(
878 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700879 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700880
881
882 def test_wait_for_boot(self, old_boot_id=None):
883 """Wait for the client to boot from cold power.
884
885 The `old_boot_id` parameter should be the value from
886 `get_boot_id()` obtained prior to shutting down. A
887 `TestFail` exception is raised if the boot id does not
888 change. The boot id test is omitted if `old_boot_id` is not
889 specified.
890
891 See @ref test_wait_for_shutdown for more on this function's
892 usage.
893
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800894 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700895 shut down.
896
897 @exception TestFail The host did not respond within the
898 allowed time.
899 @exception TestFail The host responded, but the boot id test
900 indicated that there was no reboot.
901 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700902 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700903 raise error.TestFail(
904 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700905 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700906 elif old_boot_id:
907 if self.get_boot_id() == old_boot_id:
908 raise error.TestFail(
909 'client is back up, but did not reboot'
910 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700911
912
913 @staticmethod
914 def check_for_rpm_support(hostname):
915 """For a given hostname, return whether or not it is powered by an RPM.
916
917 @return None if this host does not follows the defined naming format
918 for RPM powered DUT's in the lab. If it does follow the format,
919 it returns a regular expression MatchObject instead.
920 """
Richard Barnette82c35912012-11-20 10:09:10 -0800921 return re.match(SiteHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -0700922
923
924 def has_power(self):
925 """For this host, return whether or not it is powered by an RPM.
926
927 @return True if this host is in the CROS lab and follows the defined
928 naming format.
929 """
930 return SiteHost.check_for_rpm_support(self.hostname)
931
932
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800933 def _set_power(self, state, power_method):
934 """Sets the power to the host via RPM, Servo or manual.
935
936 @param state Specifies which power state to set to DUT
937 @param power_method Specifies which method of power control to
938 use. By default "RPM" will be used. Valid values
939 are the strings "RPM", "manual", "servoj10".
940
941 """
942 ACCEPTABLE_STATES = ['ON', 'OFF']
943
944 if state.upper() not in ACCEPTABLE_STATES:
945 raise error.TestError('State must be one of: %s.'
946 % (ACCEPTABLE_STATES,))
947
948 if power_method == self.POWER_CONTROL_SERVO:
949 logging.info('Setting servo port J10 to %s', state)
950 self.servo.set('prtctl3_pwren', state.lower())
951 time.sleep(self._USB_POWER_TIMEOUT)
952 elif power_method == self.POWER_CONTROL_MANUAL:
953 logging.info('You have %d seconds to set the AC power to %s.',
954 self._POWER_CYCLE_TIMEOUT, state)
955 time.sleep(self._POWER_CYCLE_TIMEOUT)
956 else:
957 if not self.has_power():
958 raise error.TestFail('DUT does not have RPM connected.')
959 rpm_client.set_power(self.hostname, state.upper())
Simran Basid5e5e272012-09-24 15:23:59 -0700960
961
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800962 def power_off(self, power_method=POWER_CONTROL_RPM):
963 """Turn off power to this host via RPM, Servo or manual.
964
965 @param power_method Specifies which method of power control to
966 use. By default "RPM" will be used. Valid values
967 are the strings "RPM", "manual", "servoj10".
968
969 """
970 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -0700971
972
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800973 def power_on(self, power_method=POWER_CONTROL_RPM):
974 """Turn on power to this host via RPM, Servo or manual.
975
976 @param power_method Specifies which method of power control to
977 use. By default "RPM" will be used. Valid values
978 are the strings "RPM", "manual", "servoj10".
979
980 """
981 self._set_power('ON', power_method)
982
983
984 def power_cycle(self, power_method=POWER_CONTROL_RPM):
985 """Cycle power to this host by turning it OFF, then ON.
986
987 @param power_method Specifies which method of power control to
988 use. By default "RPM" will be used. Valid values
989 are the strings "RPM", "manual", "servoj10".
990
991 """
992 if power_method in (self.POWER_CONTROL_SERVO,
993 self.POWER_CONTROL_MANUAL):
994 self.power_off(power_method=power_method)
995 time.sleep(self._POWER_CYCLE_TIMEOUT)
996 self.power_on(power_method=power_method)
997 else:
998 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700999
1000
1001 def get_platform(self):
1002 """Determine the correct platform label for this host.
1003
1004 @returns a string representing this host's platform.
1005 """
1006 crossystem = utils.Crossystem(self)
1007 crossystem.init()
1008 # Extract fwid value and use the leading part as the platform id.
1009 # fwid generally follow the format of {platform}.{firmware version}
1010 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1011 platform = crossystem.fwid().split('.')[0].lower()
1012 # Newer platforms start with 'Google_' while the older ones do not.
1013 return platform.replace('google_', '')
1014
1015
Aviv Keshet74c89a92013-02-04 15:18:30 -08001016 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07001017 def get_board(self):
1018 """Determine the correct board label for this host.
1019
1020 @returns a string representing this host's board.
1021 """
1022 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1023 run_method=self.run)
1024 board = release_info['CHROMEOS_RELEASE_BOARD']
1025 # Devices in the lab generally have the correct board name but our own
1026 # development devices have {board_name}-signed-{key_type}. The board
1027 # name may also begin with 'x86-' which we need to keep.
1028 if 'x86' not in board:
1029 return 'board:%s' % board.split('-')[0]
1030 return 'board:%s' % '-'.join(board.split('-')[0:2])
1031
1032
Aviv Keshet74c89a92013-02-04 15:18:30 -08001033 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001034 def has_lightsensor(self):
1035 """Determine the correct board label for this host.
1036
1037 @returns the string 'lightsensor' if this host has a lightsensor or
1038 None if it does not.
1039 """
1040 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08001041 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07001042 try:
1043 # Run the search cmd following the symlinks. Stderr_tee is set to
1044 # None as there can be a symlink loop, but the command will still
1045 # execute correctly with a few messages printed to stderr.
1046 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
1047 return 'lightsensor'
1048 except error.AutoservRunError:
1049 # egrep exited with a return code of 1 meaning none of the possible
1050 # lightsensor files existed.
1051 return None
1052
1053
Aviv Keshet74c89a92013-02-04 15:18:30 -08001054 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001055 def has_bluetooth(self):
1056 """Determine the correct board label for this host.
1057
1058 @returns the string 'bluetooth' if this host has bluetooth or
1059 None if it does not.
1060 """
1061 try:
1062 self.run('test -d /sys/class/bluetooth/hci0')
1063 # test exited with a return code of 0.
1064 return 'bluetooth'
1065 except error.AutoservRunError:
1066 # test exited with a return code 1 meaning the directory did not
1067 # exist.
1068 return None
1069
1070
1071 def get_labels(self):
1072 """Return a list of labels for this given host.
1073
1074 This is the main way to retrieve all the automatic labels for a host
1075 as it will run through all the currently implemented label functions.
1076 """
1077 labels = []
Richard Barnette82c35912012-11-20 10:09:10 -08001078 for label_function in self._LABEL_FUNCTIONS:
Simran Basic6f1f7a2012-10-16 10:47:46 -07001079 label = label_function(self)
1080 if label:
1081 labels.append(label)
1082 return labels