blob: 730f88d38dc910b758dfbfd3ef796eb8bec537f9 [file] [log] [blame]
J. Richard Barnette24adbf42012-04-11 15:04:53 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Dale Curtisaa5eedb2011-08-23 16:18:52 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
J. Richard Barnette1d78b012012-05-15 13:56:30 -07005import logging
Simran Basid5e5e272012-09-24 15:23:59 -07006import re
J. Richard Barnette1d78b012012-05-15 13:56:30 -07007import subprocess
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07008import time
J. Richard Barnette1d78b012012-05-15 13:56:30 -07009import xmlrpclib
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070010
J. Richard Barnette45e93de2012-04-11 17:24:15 -070011from autotest_lib.client.bin import utils
Richard Barnette0c73ffc2012-11-19 15:21:18 -080012from autotest_lib.client.common_lib import error
13from autotest_lib.client.common_lib import global_config
J. Richard Barnette45e93de2012-04-11 17:24:15 -070014from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette0c73ffc2012-11-19 15:21:18 -080015from autotest_lib.client.cros import constants as cros_constants
J. Richard Barnette45e93de2012-04-11 17:24:15 -070016from autotest_lib.server import autoserv_parser
Chris Sosaf4d43ff2012-10-30 11:21:05 -070017from autotest_lib.server import autotest
J. Richard Barnette45e93de2012-04-11 17:24:15 -070018from autotest_lib.server import site_host_attributes
J. Richard Barnette67ccb872012-04-19 16:34:56 -070019from autotest_lib.server.cros import servo
J. Richard Barnette45e93de2012-04-11 17:24:15 -070020from autotest_lib.server.hosts import remote
Simran Basidcff4252012-11-20 16:13:20 -080021from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070022
23
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070024def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
25 connect_timeout=None, alive_interval=None):
26 """Override default make_ssh_command to use options tuned for Chrome OS.
27
28 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070029 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
30 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070031
Dale Curtisaa5eedb2011-08-23 16:18:52 -070032 - ServerAliveInterval=180; which causes SSH to ping connection every
33 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
34 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
35 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070036
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070037 - ServerAliveCountMax=3; consistency with remote_access.sh.
38
39 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
40 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070041
42 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
43 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070044
45 - SSH protocol forced to 2; needed for ServerAliveInterval.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070046 """
47 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
48 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070049 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
50 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
51 ' -o Protocol=2 -l %s -p %d')
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070052 return base_command % (opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070053
54
Simran Basic6f1f7a2012-10-16 10:47:46 -070055def add_function_to_list(functions_list):
56 """Decorator used to group functions together into the provided list."""
57 def add_func(func):
58 functions_list.append(func)
59 return func
60 return add_func
61
62
J. Richard Barnette45e93de2012-04-11 17:24:15 -070063class SiteHost(remote.RemoteHost):
64 """Chromium OS specific subclass of Host."""
65
66 _parser = autoserv_parser.autoserv_parser
67
Richard Barnette0c73ffc2012-11-19 15:21:18 -080068 # Time to wait for new kernel to be marked successful after
69 # auto update.
Chris Masone163cead2012-05-16 11:49:48 -070070 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -070071
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070072 # Timeout values associated with various Chrome OS state
Richard Barnette0c73ffc2012-11-19 15:21:18 -080073 # changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070074 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080075 # In general, a good rule of thumb is that the timeout can be up
76 # to twice the typical measured value on the slowest platform.
77 # The times here have not necessarily been empirically tested to
78 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070079 #
80 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080081 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
82 # time to restart the netwowrk.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070083 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080084 # other things, this must account for the 30 second dev-mode
85 # screen delay and time to start the network,
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070086 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080087 # including the 30 second dev-mode delay and time to start the
88 # network,
89 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070090 # REBOOT_TIMEOUT: Combination of shutdown and reboot times.
91
92 SLEEP_TIMEOUT = 2
93 RESUME_TIMEOUT = 5
94 BOOT_TIMEOUT = 45
95 USB_BOOT_TIMEOUT = 150
96 SHUTDOWN_TIMEOUT = 5
97 REBOOT_TIMEOUT = SHUTDOWN_TIMEOUT + BOOT_TIMEOUT
Richard Barnette0c73ffc2012-11-19 15:21:18 -080098
99
Chris Sosae146ed82012-09-19 17:58:36 -0700100 LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Simran Basid5e5e272012-09-24 15:23:59 -0700101 RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
102 'host[0-9]+')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700103 LIGHTSENSOR_FILES = ['in_illuminance0_input',
104 'in_illuminance0_raw',
105 'illuminance0_input']
106 LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
107 LABEL_FUNCTIONS = []
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700108
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800109
J. Richard Barnette964fba02012-10-24 17:34:29 -0700110 @staticmethod
111 def get_servo_arguments(arglist):
112 servo_args = {}
113 for arg in ('servo_host', 'servo_port'):
114 if arg in arglist:
115 servo_args[arg] = arglist[arg]
116 return servo_args
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700117
J. Richard Barnette964fba02012-10-24 17:34:29 -0700118
119 def _initialize(self, hostname, servo_args=None, *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700120 """Initialize superclasses, and |self.servo|.
121
122 For creating the host servo object, there are three
123 possibilities: First, if the host is a lab system known to
124 have a servo board, we connect to that servo unconditionally.
125 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700126 servo features for testing, it will pass settings for
127 `servo_host`, `servo_port`, or both. If neither of these
128 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700129
130 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700131 super(SiteHost, self)._initialize(hostname=hostname,
132 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700133 # self.env is a dictionary of environment variable settings
134 # to be exported for commands run on the host.
135 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
136 # errors that might happen.
137 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700138 self._xmlrpc_proxy_map = {}
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700139 self.servo = servo.Servo.get_lab_servo(hostname)
J. Richard Barnettead7da482012-10-30 16:46:52 -0700140 if not self.servo and servo_args is not None:
J. Richard Barnette964fba02012-10-24 17:34:29 -0700141 self.servo = servo.Servo(**servo_args)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700142
143
Chris Sosaa3ac2152012-05-23 22:23:13 -0700144 def machine_install(self, update_url=None, force_update=False,
145 local_devserver=False):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700146 if not update_url and self._parser.options.image:
147 update_url = self._parser.options.image
148 elif not update_url:
149 raise autoupdater.ChromiumOSError(
150 'Update failed. No update URL provided.')
151
152 # Attempt to update the system.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700153 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
154 local_devserver=local_devserver)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700155 if updater.run_update(force_update):
156 # Figure out active and inactive kernel.
157 active_kernel, inactive_kernel = updater.get_kernel_state()
158
159 # Ensure inactive kernel has higher priority than active.
160 if (updater.get_kernel_priority(inactive_kernel)
161 < updater.get_kernel_priority(active_kernel)):
162 raise autoupdater.ChromiumOSError(
163 'Update failed. The priority of the inactive kernel'
164 ' partition is less than that of the active kernel'
165 ' partition.')
166
Scott Zawalski21902002012-09-19 17:57:00 -0400167 update_engine_log = '/var/log/update_engine.log'
168 logging.info('Dumping %s', update_engine_log)
169 self.run('cat %s' % update_engine_log)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800170 # Updater has returned successfully; reboot the host.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700171 self.reboot(timeout=60, wait=True)
Chris Sosae146ed82012-09-19 17:58:36 -0700172 # Touch the lab machine file to leave a marker that distinguishes
173 # this image from other test images.
174 self.run('touch %s' % self.LAB_MACHINE_FILE)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700175
176 # Following the reboot, verify the correct version.
177 updater.check_version()
178
179 # Figure out newly active kernel.
180 new_active_kernel, _ = updater.get_kernel_state()
181
182 # Ensure that previously inactive kernel is now the active kernel.
183 if new_active_kernel != inactive_kernel:
184 raise autoupdater.ChromiumOSError(
185 'Update failed. New kernel partition is not active after'
186 ' boot.')
187
188 host_attributes = site_host_attributes.HostAttributes(self.hostname)
189 if host_attributes.has_chromeos_firmware:
190 # Wait until tries == 0 and success, or until timeout.
191 utils.poll_for_condition(
192 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
193 and updater.get_kernel_success(new_active_kernel)),
194 exception=autoupdater.ChromiumOSError(
195 'Update failed. Timed out waiting for system to mark'
196 ' new kernel as successful.'),
197 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
198
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700199 # Clean up any old autotest directories which may be lying around.
200 for path in global_config.global_config.get_config_value(
201 'AUTOSERV', 'client_autodir_paths', type=list):
202 self.run('rm -rf ' + path)
203
204
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700205 def close(self):
206 super(SiteHost, self).close()
207 self.xmlrpc_disconnect_all()
208
209
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700210 def cleanup(self):
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700211 client_at = autotest.Autotest(self)
Scott Zawalskibb091fc2012-11-15 10:11:49 -0500212 self.run('rm -f %s' % cros_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500213 try:
214 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
215 '_clear_login_prompt_state')
216 self.run('restart ui')
217 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
218 '_wait_for_login_prompt')
219 except error.AutoservRunError:
220 logging.warn('Unable to restart ui, rebooting device.')
221 # Since restarting the UI fails fall back to normal Autotest
222 # cleanup routines, i.e. reboot the machine.
223 super(SiteHost, self).cleanup()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700224
225
Simran Basi154f5582012-10-23 16:27:11 -0700226 # TODO (sbasi) crosbug.com/35656
227 # Renamed the sitehost cleanup method so we don't go down this pathway.
228 # def cleanup(self):
229 def cleanup_poweron(self):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700230 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700231 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700232 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700233 try:
234 self.power_on()
235 except RemotePowerException:
236 # If cleanup has completed but there was an issue with the RPM
237 # Infrastructure, log an error message rather than fail cleanup
238 logging.error('Failed to turn Power On for this host after '
239 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700240
241
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700242 def reboot(self, **dargs):
243 """
244 This function reboots the site host. The more generic
245 RemoteHost.reboot() performs sync and sleeps for 5
246 seconds. This is not necessary for Chrome OS devices as the
247 sync should be finished in a short time during the reboot
248 command.
249 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800250 if 'reboot_cmd' not in dargs:
251 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
252 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700253 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800254 if 'fastsync' not in dargs:
255 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700256 super(SiteHost, self).reboot(**dargs)
257
258
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700259 def verify_software(self):
260 """Ensure the stateful partition has space for Autotest and updates.
261
262 Similar to what is done by AbstractSSH, except instead of checking the
263 Autotest installation path, just check the stateful partition.
264
265 Checking the stateful partition is preferable in case it has been wiped,
266 resulting in an Autotest installation path which doesn't exist and isn't
267 writable. We still want to pass verify in this state since the partition
268 will be recovered with the next install.
269 """
270 super(SiteHost, self).verify_software()
271 self.check_diskspace(
272 '/mnt/stateful_partition',
273 global_config.global_config.get_config_value(
274 'SERVER', 'gb_diskspace_required', type=int,
275 default=20))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700276
277
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700278 def xmlrpc_connect(self, command, port, cleanup=None):
279 """Connect to an XMLRPC server on the host.
280
281 The `command` argument should be a simple shell command that
282 starts an XMLRPC server on the given `port`. The command
283 must not daemonize, and must terminate cleanly on SIGTERM.
284 The command is started in the background on the host, and a
285 local XMLRPC client for the server is created and returned
286 to the caller.
287
288 Note that the process of creating an XMLRPC client makes no
289 attempt to connect to the remote server; the caller is
290 responsible for determining whether the server is running
291 correctly, and is ready to serve requests.
292
293 @param command Shell command to start the server.
294 @param port Port number on which the server is expected to
295 be serving.
296 """
297 self.xmlrpc_disconnect(port)
298
299 # Chrome OS on the target closes down most external ports
300 # for security. We could open the port, but doing that
301 # would conflict with security tests that check that only
302 # expected ports are open. So, to get to the port on the
303 # target we use an ssh tunnel.
304 local_port = utils.get_unused_port()
305 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
306 ssh_cmd = make_ssh_command(opts=tunnel_options)
307 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
308 logging.debug('Full tunnel command: %s', tunnel_cmd)
309 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
310 logging.debug('Started XMLRPC tunnel, local = %d'
311 ' remote = %d, pid = %d',
312 local_port, port, tunnel_proc.pid)
313
314 # Start the server on the host. Redirection in the command
315 # below is necessary, because 'ssh' won't terminate until
316 # background child processes close stdin, stdout, and
317 # stderr.
318 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
319 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
320 logging.debug('Started XMLRPC server on host %s, pid = %s',
321 self.hostname, remote_pid)
322
323 self._xmlrpc_proxy_map[port] = (cleanup, tunnel_proc)
324 rpc_url = 'http://localhost:%d' % local_port
325 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
326
327
328 def xmlrpc_disconnect(self, port):
329 """Disconnect from an XMLRPC server on the host.
330
331 Terminates the remote XMLRPC server previously started for
332 the given `port`. Also closes the local ssh tunnel created
333 for the connection to the host. This function does not
334 directly alter the state of a previously returned XMLRPC
335 client object; however disconnection will cause all
336 subsequent calls to methods on the object to fail.
337
338 This function does nothing if requested to disconnect a port
339 that was not previously connected via `self.xmlrpc_connect()`
340
341 @param port Port number passed to a previous call to
342 `xmlrpc_connect()`
343 """
344 if port not in self._xmlrpc_proxy_map:
345 return
346 entry = self._xmlrpc_proxy_map[port]
347 remote_name = entry[0]
348 tunnel_proc = entry[1]
349 if remote_name:
350 # We use 'pkill' to find our target process rather than
351 # a PID, because the host may have rebooted since
352 # connecting, and we don't want to kill an innocent
353 # process with the same PID.
354 #
355 # 'pkill' helpfully exits with status 1 if no target
356 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700357 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700358 # status.
359 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
360
361 if tunnel_proc.poll() is None:
362 tunnel_proc.terminate()
363 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
364 else:
365 logging.debug('Tunnel pid %d terminated early, status %d',
366 tunnel_proc.pid, tunnel_proc.returncode)
367 del self._xmlrpc_proxy_map[port]
368
369
370 def xmlrpc_disconnect_all(self):
371 """Disconnect all known XMLRPC proxy ports."""
372 for port in self._xmlrpc_proxy_map.keys():
373 self.xmlrpc_disconnect(port)
374
375
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700376 def _ping_is_up(self):
377 """Ping the host once, and return whether it responded."""
378 return utils.ping(self.hostname, tries=1, deadline=1) == 0
379
380
381 def _ping_wait_down(self, timeout):
382 """Wait until the host no longer responds to `ping`.
383
384 @param timeout Minimum time to allow before declaring the
385 host to be non-responsive.
386 """
387
388 # This function is a slightly faster version of wait_down().
389 #
390 # In AbstractSSHHost.wait_down(), `ssh` is used to determine
391 # whether the host is down. In some situations (mine, at
392 # least), `ssh` can take over a minute to determine that the
393 # host is down. The `ping` command answers the question
394 # faster, so we use that here instead.
395 #
396 # There is no equivalent for wait_up(), because a target that
397 # answers to `ping` won't necessarily respond to `ssh`.
398 end_time = time.time() + timeout
399 while time.time() <= end_time:
400 if not self._ping_is_up():
401 return True
402
403 # If the timeout is short relative to the run time of
404 # _ping_is_up(), we might be prone to false failures for
405 # lack of checking frequently enough. To be safe, we make
406 # one last check _after_ the deadline.
407 return not self._ping_is_up()
408
409
410 def test_wait_for_sleep(self):
411 """Wait for the client to enter low-power sleep mode.
412
413 The test for "is asleep" can't distinguish a system that is
414 powered off; to confirm that the unit was asleep, it is
415 necessary to force resume, and then call
416 `test_wait_for_resume()`.
417
418 This function is expected to be called from a test as part
419 of a sequence like the following:
420
421 ~~~~~~~~
422 boot_id = host.get_boot_id()
423 # trigger sleep on the host
424 host.test_wait_for_sleep()
425 # trigger resume on the host
426 host.test_wait_for_resume(boot_id)
427 ~~~~~~~~
428
429 @exception TestFail The host did not go to sleep within
430 the allowed time.
431 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700432 if not self._ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700433 raise error.TestFail(
434 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700435 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700436
437
438 def test_wait_for_resume(self, old_boot_id):
439 """Wait for the client to resume from low-power sleep mode.
440
441 The `old_boot_id` parameter should be the value from
442 `get_boot_id()` obtained prior to entering sleep mode. A
443 `TestFail` exception is raised if the boot id changes.
444
445 See @ref test_wait_for_sleep for more on this function's
446 usage.
447
448 @param[in] old_boot_id A boot id value obtained before the
449 target host went to sleep.
450
451 @exception TestFail The host did not respond within the
452 allowed time.
453 @exception TestFail The host responded, but the boot id test
454 indicated a reboot rather than a sleep
455 cycle.
456 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700457 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700458 raise error.TestFail(
459 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700460 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700461 else:
462 new_boot_id = self.get_boot_id()
463 if new_boot_id != old_boot_id:
464 raise error.TestFail(
465 'client rebooted, but sleep was expected'
466 ' (old boot %s, new boot %s)'
467 % (old_boot_id, new_boot_id))
468
469
470 def test_wait_for_shutdown(self):
471 """Wait for the client to shut down.
472
473 The test for "has shut down" can't distinguish a system that
474 is merely asleep; to confirm that the unit was down, it is
475 necessary to force boot, and then call test_wait_for_boot().
476
477 This function is expected to be called from a test as part
478 of a sequence like the following:
479
480 ~~~~~~~~
481 boot_id = host.get_boot_id()
482 # trigger shutdown on the host
483 host.test_wait_for_shutdown()
484 # trigger boot on the host
485 host.test_wait_for_boot(boot_id)
486 ~~~~~~~~
487
488 @exception TestFail The host did not shut down within the
489 allowed time.
490 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700491 if not self._ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700492 raise error.TestFail(
493 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700494 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700495
496
497 def test_wait_for_boot(self, old_boot_id=None):
498 """Wait for the client to boot from cold power.
499
500 The `old_boot_id` parameter should be the value from
501 `get_boot_id()` obtained prior to shutting down. A
502 `TestFail` exception is raised if the boot id does not
503 change. The boot id test is omitted if `old_boot_id` is not
504 specified.
505
506 See @ref test_wait_for_shutdown for more on this function's
507 usage.
508
509 @param[in] old_boot_id A boot id value obtained before the
510 shut down.
511
512 @exception TestFail The host did not respond within the
513 allowed time.
514 @exception TestFail The host responded, but the boot id test
515 indicated that there was no reboot.
516 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700517 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700518 raise error.TestFail(
519 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700520 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700521 elif old_boot_id:
522 if self.get_boot_id() == old_boot_id:
523 raise error.TestFail(
524 'client is back up, but did not reboot'
525 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700526
527
528 @staticmethod
529 def check_for_rpm_support(hostname):
530 """For a given hostname, return whether or not it is powered by an RPM.
531
532 @return None if this host does not follows the defined naming format
533 for RPM powered DUT's in the lab. If it does follow the format,
534 it returns a regular expression MatchObject instead.
535 """
536 return re.match(SiteHost.RPM_HOSTNAME_REGEX, hostname)
537
538
539 def has_power(self):
540 """For this host, return whether or not it is powered by an RPM.
541
542 @return True if this host is in the CROS lab and follows the defined
543 naming format.
544 """
545 return SiteHost.check_for_rpm_support(self.hostname)
546
547
Simran Basid5e5e272012-09-24 15:23:59 -0700548 def power_off(self):
Simran Basidcff4252012-11-20 16:13:20 -0800549 rpm_client.set_power(self.hostname, 'OFF')
Simran Basid5e5e272012-09-24 15:23:59 -0700550
551
552 def power_on(self):
Simran Basidcff4252012-11-20 16:13:20 -0800553 rpm_client.set_power(self.hostname, 'ON')
Simran Basid5e5e272012-09-24 15:23:59 -0700554
555
556 def power_cycle(self):
Simran Basidcff4252012-11-20 16:13:20 -0800557 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700558
559
560 def get_platform(self):
561 """Determine the correct platform label for this host.
562
563 @returns a string representing this host's platform.
564 """
565 crossystem = utils.Crossystem(self)
566 crossystem.init()
567 # Extract fwid value and use the leading part as the platform id.
568 # fwid generally follow the format of {platform}.{firmware version}
569 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
570 platform = crossystem.fwid().split('.')[0].lower()
571 # Newer platforms start with 'Google_' while the older ones do not.
572 return platform.replace('google_', '')
573
574
575 @add_function_to_list(LABEL_FUNCTIONS)
576 def get_board(self):
577 """Determine the correct board label for this host.
578
579 @returns a string representing this host's board.
580 """
581 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
582 run_method=self.run)
583 board = release_info['CHROMEOS_RELEASE_BOARD']
584 # Devices in the lab generally have the correct board name but our own
585 # development devices have {board_name}-signed-{key_type}. The board
586 # name may also begin with 'x86-' which we need to keep.
587 if 'x86' not in board:
588 return 'board:%s' % board.split('-')[0]
589 return 'board:%s' % '-'.join(board.split('-')[0:2])
590
591
592 @add_function_to_list(LABEL_FUNCTIONS)
593 def has_lightsensor(self):
594 """Determine the correct board label for this host.
595
596 @returns the string 'lightsensor' if this host has a lightsensor or
597 None if it does not.
598 """
599 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
600 self.LIGHTSENSOR_SEARCH_DIR, '|'.join(self.LIGHTSENSOR_FILES))
601 try:
602 # Run the search cmd following the symlinks. Stderr_tee is set to
603 # None as there can be a symlink loop, but the command will still
604 # execute correctly with a few messages printed to stderr.
605 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
606 return 'lightsensor'
607 except error.AutoservRunError:
608 # egrep exited with a return code of 1 meaning none of the possible
609 # lightsensor files existed.
610 return None
611
612
613 @add_function_to_list(LABEL_FUNCTIONS)
614 def has_bluetooth(self):
615 """Determine the correct board label for this host.
616
617 @returns the string 'bluetooth' if this host has bluetooth or
618 None if it does not.
619 """
620 try:
621 self.run('test -d /sys/class/bluetooth/hci0')
622 # test exited with a return code of 0.
623 return 'bluetooth'
624 except error.AutoservRunError:
625 # test exited with a return code 1 meaning the directory did not
626 # exist.
627 return None
628
629
630 def get_labels(self):
631 """Return a list of labels for this given host.
632
633 This is the main way to retrieve all the automatic labels for a host
634 as it will run through all the currently implemented label functions.
635 """
636 labels = []
637 for label_function in self.LABEL_FUNCTIONS:
638 label = label_function(self)
639 if label:
640 labels.append(label)
641 return labels