blob: 655758a2564341e85c34dcce810688d1f998073a [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
Simran Basi13fa1ba2013-03-04 10:56:47 -0800398 # Kick off the autoreboot script as the _LAB_MACHINE_FILE was
399 # missing on the first boot.
400 self.run('start autoreboot')
401
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700402 # Clean up any old autotest directories which may be lying around.
403 for path in global_config.global_config.get_config_value(
404 'AUTOSERV', 'client_autodir_paths', type=list):
405 self.run('rm -rf ' + path)
406
407
Richard Barnette82c35912012-11-20 10:09:10 -0800408 def _get_board_from_afe(self):
409 """Retrieve this host's board from its labels in the AFE.
410
411 Looks for a host label of the form "board:<board>", and
412 returns the "<board>" part of the label. `None` is returned
413 if there is not a single, unique label matching the pattern.
414
415 @returns board from label, or `None`.
416 """
417 host_model = models.Host.objects.get(hostname=self.hostname)
418 board_labels = filter(lambda l: l.name.startswith('board:'),
419 host_model.labels.all())
420 board_name = None
421 if len(board_labels) == 1:
422 board_name = board_labels[0].name.split(':', 1)[1]
423 elif len(board_labels) == 0:
424 logging.error('Host %s does not have a board label.',
425 self.hostname)
426 else:
427 logging.error('Host %s has multiple board labels.',
428 self.hostname)
429 return board_name
430
431
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500432 def _install_repair(self):
433 """Attempt to repair this host using upate-engine.
434
435 If the host is up, try installing the DUT with a stable
436 "repair" version of Chrome OS as defined in the global_config
437 under CROS.stable_cros_version.
438
439 @returns True if successful, False if update_engine failed.
440
441 """
442 if not self.is_up():
443 return False
444
445 logging.info('Attempting to reimage machine to repair image.')
446 try:
447 self.machine_install(repair=True)
448 except autoupdater.ChromiumOSError:
449 logging.info('Repair via install failed.')
450 return False
451
452 return True
453
454
Richard Barnette03a0c132012-11-05 12:40:35 -0800455 def _servo_repair(self, board):
456 """Attempt to repair this host using an attached Servo.
457
458 Re-install the OS on the DUT by 1) installing a test image
459 on a USB storage device attached to the Servo board,
460 2) booting that image in recovery mode, and then
461 3) installing the image.
462
463 """
464 server = dev_server.ImageServer.devserver_url_for_servo(board)
465 image = server + (self._DEFAULT_SERVO_URL_FORMAT %
466 { 'board': board })
467 self.servo.install_recovery_image(image)
468 if not self.wait_up(timeout=self.USB_BOOT_TIMEOUT):
469 raise error.AutoservError('DUT failed to boot from USB'
470 ' after %d seconds' %
471 self.USB_BOOT_TIMEOUT)
472 self.run('chromeos-install --yes',
473 timeout=self._INSTALL_TIMEOUT)
474 self.servo.power_long_press()
475 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
476 self.servo.power_short_press()
477 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
478 raise error.AutoservError('DUT failed to reboot installed '
479 'test image after %d seconds' %
480 self.BOOT_TIMEOUT)
481
482
Richard Barnette82c35912012-11-20 10:09:10 -0800483 def _powercycle_to_repair(self):
484 """Utilize the RPM Infrastructure to bring the host back up.
485
486 If the host is not up/repaired after the first powercycle we utilize
487 auto fallback to the last good install by powercycling and rebooting the
488 host 6 times.
489 """
490 logging.info('Attempting repair via RPM powercycle.')
491 failed_cycles = 0
492 self.power_cycle()
493 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
494 failed_cycles += 1
495 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
496 raise error.AutoservError('Powercycled host %s %d times; '
497 'device did not come back online.' %
498 (self.hostname, failed_cycles))
499 self.power_cycle()
500 if failed_cycles == 0:
501 logging.info('Powercycling was successful first time.')
502 else:
503 logging.info('Powercycling was successful after %d failures.',
504 failed_cycles)
505
506
507 def repair_full(self):
508 """Repair a host for repair level NO_PROTECTION.
509
510 This overrides the base class function for repair; it does
511 not call back to the parent class, but instead offers a
512 simplified implementation based on the capabilities in the
513 Chrome OS test lab.
514
515 Repair follows this sequence:
516 1. If the DUT passes `self.verify()`, do nothing.
517 2. If the DUT can be power-cycled via RPM, try to repair
518 by power-cycling.
519
520 As with the parent method, the last operation performed on
521 the DUT must be to call `self.verify()`; if that call fails,
522 the exception it raises is passed back to the caller.
523 """
524 try:
525 self.verify()
526 except:
527 host_board = self._get_board_from_afe()
Richard Barnette03a0c132012-11-05 12:40:35 -0800528 if host_board is None:
529 logging.error('host %s has no board; failing repair',
530 self.hostname)
Richard Barnette82c35912012-11-20 10:09:10 -0800531 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500532
533 reimage_success = self._install_repair()
534 # TODO(scottz): All repair pathways should be executed until we've
535 # exhausted all options. Below we favor servo over powercycle when
536 # we really should be falling back to power if servo fails.
537 if (not reimage_success and self.servo and
Richard Barnette03a0c132012-11-05 12:40:35 -0800538 host_board in self._SERVO_REPAIR_WHITELIST):
539 self._servo_repair(host_board)
540 elif (self.has_power() and
541 host_board in self._RPM_RECOVERY_BOARDS):
542 self._powercycle_to_repair()
543 else:
544 logging.error('host %s has no servo and no RPM control; '
545 'failing repair', self.hostname)
546 raise
Richard Barnette82c35912012-11-20 10:09:10 -0800547 self.verify()
548
549
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700550 def close(self):
551 super(SiteHost, self).close()
552 self.xmlrpc_disconnect_all()
553
554
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700555 def cleanup(self):
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700556 client_at = autotest.Autotest(self)
Richard Barnette82c35912012-11-20 10:09:10 -0800557 self.run('rm -f %s' % constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500558 try:
559 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
560 '_clear_login_prompt_state')
561 self.run('restart ui')
562 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
563 '_wait_for_login_prompt')
Alex Millerf4517962013-02-25 15:03:02 -0800564 except (error.AutotestRunError, error.AutoservRunError):
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500565 logging.warn('Unable to restart ui, rebooting device.')
566 # Since restarting the UI fails fall back to normal Autotest
567 # cleanup routines, i.e. reboot the machine.
568 super(SiteHost, self).cleanup()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700569
570
Simran Basi154f5582012-10-23 16:27:11 -0700571 # TODO (sbasi) crosbug.com/35656
572 # Renamed the sitehost cleanup method so we don't go down this pathway.
573 # def cleanup(self):
574 def cleanup_poweron(self):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700575 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700576 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700577 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700578 try:
579 self.power_on()
Chris Sosafab08082013-01-04 15:21:20 -0800580 except rpm_client.RemotePowerException:
Simran Basifd23fb22012-10-22 17:56:22 -0700581 # If cleanup has completed but there was an issue with the RPM
582 # Infrastructure, log an error message rather than fail cleanup
583 logging.error('Failed to turn Power On for this host after '
584 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700585
586
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700587 def reboot(self, **dargs):
588 """
589 This function reboots the site host. The more generic
590 RemoteHost.reboot() performs sync and sleeps for 5
591 seconds. This is not necessary for Chrome OS devices as the
592 sync should be finished in a short time during the reboot
593 command.
594 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800595 if 'reboot_cmd' not in dargs:
596 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
597 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700598 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800599 if 'fastsync' not in dargs:
600 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700601 super(SiteHost, self).reboot(**dargs)
602
603
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700604 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800605 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700606
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800607 Tests for the following conditions:
608 1. All conditions tested by the parent version of this
609 function.
610 2. Sufficient space in /mnt/stateful_partition.
611 3. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700612
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700613 """
614 super(SiteHost, self).verify_software()
615 self.check_diskspace(
616 '/mnt/stateful_partition',
617 global_config.global_config.get_config_value(
618 'SERVER', 'gb_diskspace_required', type=int,
619 default=20))
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800620 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -0500621 # Makes sure python is present, loads and can use built in functions.
622 # We have seen cases where importing cPickle fails with undefined
623 # symbols in cPickle.so.
624 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700625
626
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800627 def xmlrpc_connect(self, command, port, command_name=None):
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700628 """Connect to an XMLRPC server on the host.
629
630 The `command` argument should be a simple shell command that
631 starts an XMLRPC server on the given `port`. The command
632 must not daemonize, and must terminate cleanly on SIGTERM.
633 The command is started in the background on the host, and a
634 local XMLRPC client for the server is created and returned
635 to the caller.
636
637 Note that the process of creating an XMLRPC client makes no
638 attempt to connect to the remote server; the caller is
639 responsible for determining whether the server is running
640 correctly, and is ready to serve requests.
641
642 @param command Shell command to start the server.
643 @param port Port number on which the server is expected to
644 be serving.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800645 @param command_name String to use as input to `pkill` to
646 terminate the XMLRPC server on the host.
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700647 """
648 self.xmlrpc_disconnect(port)
649
650 # Chrome OS on the target closes down most external ports
651 # for security. We could open the port, but doing that
652 # would conflict with security tests that check that only
653 # expected ports are open. So, to get to the port on the
654 # target we use an ssh tunnel.
655 local_port = utils.get_unused_port()
656 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
657 ssh_cmd = make_ssh_command(opts=tunnel_options)
658 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
659 logging.debug('Full tunnel command: %s', tunnel_cmd)
660 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
661 logging.debug('Started XMLRPC tunnel, local = %d'
662 ' remote = %d, pid = %d',
663 local_port, port, tunnel_proc.pid)
664
665 # Start the server on the host. Redirection in the command
666 # below is necessary, because 'ssh' won't terminate until
667 # background child processes close stdin, stdout, and
668 # stderr.
669 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
670 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
671 logging.debug('Started XMLRPC server on host %s, pid = %s',
672 self.hostname, remote_pid)
673
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800674 self._xmlrpc_proxy_map[port] = (command_name, tunnel_proc)
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700675 rpc_url = 'http://localhost:%d' % local_port
676 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
677
678
679 def xmlrpc_disconnect(self, port):
680 """Disconnect from an XMLRPC server on the host.
681
682 Terminates the remote XMLRPC server previously started for
683 the given `port`. Also closes the local ssh tunnel created
684 for the connection to the host. This function does not
685 directly alter the state of a previously returned XMLRPC
686 client object; however disconnection will cause all
687 subsequent calls to methods on the object to fail.
688
689 This function does nothing if requested to disconnect a port
690 that was not previously connected via `self.xmlrpc_connect()`
691
692 @param port Port number passed to a previous call to
693 `xmlrpc_connect()`
694 """
695 if port not in self._xmlrpc_proxy_map:
696 return
697 entry = self._xmlrpc_proxy_map[port]
698 remote_name = entry[0]
699 tunnel_proc = entry[1]
700 if remote_name:
701 # We use 'pkill' to find our target process rather than
702 # a PID, because the host may have rebooted since
703 # connecting, and we don't want to kill an innocent
704 # process with the same PID.
705 #
706 # 'pkill' helpfully exits with status 1 if no target
707 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700708 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700709 # status.
710 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
711
712 if tunnel_proc.poll() is None:
713 tunnel_proc.terminate()
714 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
715 else:
716 logging.debug('Tunnel pid %d terminated early, status %d',
717 tunnel_proc.pid, tunnel_proc.returncode)
718 del self._xmlrpc_proxy_map[port]
719
720
721 def xmlrpc_disconnect_all(self):
722 """Disconnect all known XMLRPC proxy ports."""
723 for port in self._xmlrpc_proxy_map.keys():
724 self.xmlrpc_disconnect(port)
725
726
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800727 def _ping_check_status(self, status):
728 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700729
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800730 @param status Check the ping status against this value.
731 @return True iff `status` and the result of ping are the same
732 (i.e. both True or both False).
733
734 """
735 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
736 return not (status ^ (ping_val == 0))
737
738 def _ping_wait_for_status(self, status, timeout):
739 """Wait for the host to have a given status (UP or DOWN).
740
741 Status is checked by polling. Polling will not last longer
742 than the number of seconds in `timeout`. The polling
743 interval will be long enough that only approximately
744 _PING_WAIT_COUNT polling cycles will be executed, subject
745 to a maximum interval of about one minute.
746
747 @param status Waiting will stop immediately if `ping` of the
748 host returns this status.
749 @param timeout Poll for at most this many seconds.
750 @return True iff the host status from `ping` matched the
751 requested status at the time of return.
752
753 """
754 # _ping_check_status() takes about 1 second, hence the
755 # "- 1" in the formula below.
756 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
757 end_time = time.time() + timeout
758 while time.time() <= end_time:
759 if self._ping_check_status(status):
760 return True
761 if poll_interval > 0:
762 time.sleep(poll_interval)
763
764 # The last thing we did was sleep(poll_interval), so it may
765 # have been too long since the last `ping`. Check one more
766 # time, just to be sure.
767 return self._ping_check_status(status)
768
769 def ping_wait_up(self, timeout):
770 """Wait for the host to respond to `ping`.
771
772 N.B. This method is not a reliable substitute for
773 `wait_up()`, because a host that responds to ping will not
774 necessarily respond to ssh. This method should only be used
775 if the target DUT can be considered functional even if it
776 can't be reached via ssh.
777
778 @param timeout Minimum time to allow before declaring the
779 host to be non-responsive.
780 @return True iff the host answered to ping before the timeout.
781
782 """
783 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700784
Andrew Bresticker678c0c72013-01-22 10:44:09 -0800785 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700786 """Wait until the host no longer responds to `ping`.
787
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800788 This function can be used as a slightly faster version of
789 `wait_down()`, by avoiding potentially long ssh timeouts.
790
791 @param timeout Minimum time to allow for the host to become
792 non-responsive.
793 @return True iff the host quit answering ping before the
794 timeout.
795
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700796 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800797 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700798
799 def test_wait_for_sleep(self):
800 """Wait for the client to enter low-power sleep mode.
801
802 The test for "is asleep" can't distinguish a system that is
803 powered off; to confirm that the unit was asleep, it is
804 necessary to force resume, and then call
805 `test_wait_for_resume()`.
806
807 This function is expected to be called from a test as part
808 of a sequence like the following:
809
810 ~~~~~~~~
811 boot_id = host.get_boot_id()
812 # trigger sleep on the host
813 host.test_wait_for_sleep()
814 # trigger resume on the host
815 host.test_wait_for_resume(boot_id)
816 ~~~~~~~~
817
818 @exception TestFail The host did not go to sleep within
819 the allowed time.
820 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -0800821 if not self.ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700822 raise error.TestFail(
823 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700824 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700825
826
827 def test_wait_for_resume(self, old_boot_id):
828 """Wait for the client to resume from low-power sleep mode.
829
830 The `old_boot_id` parameter should be the value from
831 `get_boot_id()` obtained prior to entering sleep mode. A
832 `TestFail` exception is raised if the boot id changes.
833
834 See @ref test_wait_for_sleep for more on this function's
835 usage.
836
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800837 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700838 target host went to sleep.
839
840 @exception TestFail The host did not respond within the
841 allowed time.
842 @exception TestFail The host responded, but the boot id test
843 indicated a reboot rather than a sleep
844 cycle.
845 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700846 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700847 raise error.TestFail(
848 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700849 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700850 else:
851 new_boot_id = self.get_boot_id()
852 if new_boot_id != old_boot_id:
853 raise error.TestFail(
854 'client rebooted, but sleep was expected'
855 ' (old boot %s, new boot %s)'
856 % (old_boot_id, new_boot_id))
857
858
859 def test_wait_for_shutdown(self):
860 """Wait for the client to shut down.
861
862 The test for "has shut down" can't distinguish a system that
863 is merely asleep; to confirm that the unit was down, it is
864 necessary to force boot, and then call test_wait_for_boot().
865
866 This function is expected to be called from a test as part
867 of a sequence like the following:
868
869 ~~~~~~~~
870 boot_id = host.get_boot_id()
871 # trigger shutdown on the host
872 host.test_wait_for_shutdown()
873 # trigger boot on the host
874 host.test_wait_for_boot(boot_id)
875 ~~~~~~~~
876
877 @exception TestFail The host did not shut down within the
878 allowed time.
879 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -0800880 if not self.ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700881 raise error.TestFail(
882 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700883 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700884
885
886 def test_wait_for_boot(self, old_boot_id=None):
887 """Wait for the client to boot from cold power.
888
889 The `old_boot_id` parameter should be the value from
890 `get_boot_id()` obtained prior to shutting down. A
891 `TestFail` exception is raised if the boot id does not
892 change. The boot id test is omitted if `old_boot_id` is not
893 specified.
894
895 See @ref test_wait_for_shutdown for more on this function's
896 usage.
897
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800898 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700899 shut down.
900
901 @exception TestFail The host did not respond within the
902 allowed time.
903 @exception TestFail The host responded, but the boot id test
904 indicated that there was no reboot.
905 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700906 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700907 raise error.TestFail(
908 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700909 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700910 elif old_boot_id:
911 if self.get_boot_id() == old_boot_id:
912 raise error.TestFail(
913 'client is back up, but did not reboot'
914 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700915
916
917 @staticmethod
918 def check_for_rpm_support(hostname):
919 """For a given hostname, return whether or not it is powered by an RPM.
920
921 @return None if this host does not follows the defined naming format
922 for RPM powered DUT's in the lab. If it does follow the format,
923 it returns a regular expression MatchObject instead.
924 """
Richard Barnette82c35912012-11-20 10:09:10 -0800925 return re.match(SiteHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -0700926
927
928 def has_power(self):
929 """For this host, return whether or not it is powered by an RPM.
930
931 @return True if this host is in the CROS lab and follows the defined
932 naming format.
933 """
934 return SiteHost.check_for_rpm_support(self.hostname)
935
936
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800937 def _set_power(self, state, power_method):
938 """Sets the power to the host via RPM, Servo or manual.
939
940 @param state Specifies which power state to set to DUT
941 @param power_method Specifies which method of power control to
942 use. By default "RPM" will be used. Valid values
943 are the strings "RPM", "manual", "servoj10".
944
945 """
946 ACCEPTABLE_STATES = ['ON', 'OFF']
947
948 if state.upper() not in ACCEPTABLE_STATES:
949 raise error.TestError('State must be one of: %s.'
950 % (ACCEPTABLE_STATES,))
951
952 if power_method == self.POWER_CONTROL_SERVO:
953 logging.info('Setting servo port J10 to %s', state)
954 self.servo.set('prtctl3_pwren', state.lower())
955 time.sleep(self._USB_POWER_TIMEOUT)
956 elif power_method == self.POWER_CONTROL_MANUAL:
957 logging.info('You have %d seconds to set the AC power to %s.',
958 self._POWER_CYCLE_TIMEOUT, state)
959 time.sleep(self._POWER_CYCLE_TIMEOUT)
960 else:
961 if not self.has_power():
962 raise error.TestFail('DUT does not have RPM connected.')
963 rpm_client.set_power(self.hostname, state.upper())
Simran Basid5e5e272012-09-24 15:23:59 -0700964
965
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800966 def power_off(self, power_method=POWER_CONTROL_RPM):
967 """Turn off power to this host via RPM, Servo or manual.
968
969 @param power_method Specifies which method of power control to
970 use. By default "RPM" will be used. Valid values
971 are the strings "RPM", "manual", "servoj10".
972
973 """
974 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -0700975
976
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800977 def power_on(self, power_method=POWER_CONTROL_RPM):
978 """Turn on power to this host via RPM, Servo or manual.
979
980 @param power_method Specifies which method of power control to
981 use. By default "RPM" will be used. Valid values
982 are the strings "RPM", "manual", "servoj10".
983
984 """
985 self._set_power('ON', power_method)
986
987
988 def power_cycle(self, power_method=POWER_CONTROL_RPM):
989 """Cycle power to this host by turning it OFF, then ON.
990
991 @param power_method Specifies which method of power control to
992 use. By default "RPM" will be used. Valid values
993 are the strings "RPM", "manual", "servoj10".
994
995 """
996 if power_method in (self.POWER_CONTROL_SERVO,
997 self.POWER_CONTROL_MANUAL):
998 self.power_off(power_method=power_method)
999 time.sleep(self._POWER_CYCLE_TIMEOUT)
1000 self.power_on(power_method=power_method)
1001 else:
1002 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001003
1004
1005 def get_platform(self):
1006 """Determine the correct platform label for this host.
1007
1008 @returns a string representing this host's platform.
1009 """
1010 crossystem = utils.Crossystem(self)
1011 crossystem.init()
1012 # Extract fwid value and use the leading part as the platform id.
1013 # fwid generally follow the format of {platform}.{firmware version}
1014 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1015 platform = crossystem.fwid().split('.')[0].lower()
1016 # Newer platforms start with 'Google_' while the older ones do not.
1017 return platform.replace('google_', '')
1018
1019
Aviv Keshet74c89a92013-02-04 15:18:30 -08001020 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07001021 def get_board(self):
1022 """Determine the correct board label for this host.
1023
1024 @returns a string representing this host's board.
1025 """
1026 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1027 run_method=self.run)
1028 board = release_info['CHROMEOS_RELEASE_BOARD']
1029 # Devices in the lab generally have the correct board name but our own
1030 # development devices have {board_name}-signed-{key_type}. The board
1031 # name may also begin with 'x86-' which we need to keep.
1032 if 'x86' not in board:
1033 return 'board:%s' % board.split('-')[0]
1034 return 'board:%s' % '-'.join(board.split('-')[0:2])
1035
1036
Aviv Keshet74c89a92013-02-04 15:18:30 -08001037 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001038 def has_lightsensor(self):
1039 """Determine the correct board label for this host.
1040
1041 @returns the string 'lightsensor' if this host has a lightsensor or
1042 None if it does not.
1043 """
1044 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08001045 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07001046 try:
1047 # Run the search cmd following the symlinks. Stderr_tee is set to
1048 # None as there can be a symlink loop, but the command will still
1049 # execute correctly with a few messages printed to stderr.
1050 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
1051 return 'lightsensor'
1052 except error.AutoservRunError:
1053 # egrep exited with a return code of 1 meaning none of the possible
1054 # lightsensor files existed.
1055 return None
1056
1057
Aviv Keshet74c89a92013-02-04 15:18:30 -08001058 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001059 def has_bluetooth(self):
1060 """Determine the correct board label for this host.
1061
1062 @returns the string 'bluetooth' if this host has bluetooth or
1063 None if it does not.
1064 """
1065 try:
1066 self.run('test -d /sys/class/bluetooth/hci0')
1067 # test exited with a return code of 0.
1068 return 'bluetooth'
1069 except error.AutoservRunError:
1070 # test exited with a return code 1 meaning the directory did not
1071 # exist.
1072 return None
1073
1074
1075 def get_labels(self):
1076 """Return a list of labels for this given host.
1077
1078 This is the main way to retrieve all the automatic labels for a host
1079 as it will run through all the currently implemented label functions.
1080 """
1081 labels = []
Richard Barnette82c35912012-11-20 10:09:10 -08001082 for label_function in self._LABEL_FUNCTIONS:
Simran Basic6f1f7a2012-10-16 10:47:46 -07001083 label = label_function(self)
1084 if label:
1085 labels.append(label)
1086 return labels