blob: 3a813857a17b76351e42d50c62ced36581fb4106 [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
J. Richard Barnette24adbf42012-04-11 15:04:53 -070021
22
Simran Basid5e5e272012-09-24 15:23:59 -070023RPM_FRONTEND_URI = global_config.global_config.get_config_value('CROS',
24 'rpm_frontend_uri', type=str, default='')
25
26
27class RemotePowerException(Exception):
28 """This is raised when we fail to set the state of the device's outlet."""
29 pass
30
31
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070032def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
33 connect_timeout=None, alive_interval=None):
34 """Override default make_ssh_command to use options tuned for Chrome OS.
35
36 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070037 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
38 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070039
Dale Curtisaa5eedb2011-08-23 16:18:52 -070040 - ServerAliveInterval=180; which causes SSH to ping connection every
41 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
42 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
43 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070044
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070045 - ServerAliveCountMax=3; consistency with remote_access.sh.
46
47 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
48 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070049
50 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
51 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070052
53 - SSH protocol forced to 2; needed for ServerAliveInterval.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070054 """
55 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
56 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070057 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
58 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
59 ' -o Protocol=2 -l %s -p %d')
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070060 return base_command % (opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070061
62
Simran Basic6f1f7a2012-10-16 10:47:46 -070063def add_function_to_list(functions_list):
64 """Decorator used to group functions together into the provided list."""
65 def add_func(func):
66 functions_list.append(func)
67 return func
68 return add_func
69
70
J. Richard Barnette45e93de2012-04-11 17:24:15 -070071class SiteHost(remote.RemoteHost):
72 """Chromium OS specific subclass of Host."""
73
74 _parser = autoserv_parser.autoserv_parser
75
Richard Barnette0c73ffc2012-11-19 15:21:18 -080076 # Time to wait for new kernel to be marked successful after
77 # auto update.
Chris Masone163cead2012-05-16 11:49:48 -070078 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -070079
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070080 # Timeout values associated with various Chrome OS state
Richard Barnette0c73ffc2012-11-19 15:21:18 -080081 # changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070082 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -080083 # In general, a good rule of thumb is that the timeout can be up
84 # to twice the typical measured value on the slowest platform.
85 # The times here have not necessarily been empirically tested to
86 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070087 #
88 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -080089 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
90 # time to restart the netwowrk.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070091 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -080092 # other things, this must account for the 30 second dev-mode
93 # screen delay and time to start the network,
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070094 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -080095 # including the 30 second dev-mode delay and time to start the
96 # network,
97 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070098 # REBOOT_TIMEOUT: Combination of shutdown and reboot times.
99
100 SLEEP_TIMEOUT = 2
101 RESUME_TIMEOUT = 5
102 BOOT_TIMEOUT = 45
103 USB_BOOT_TIMEOUT = 150
104 SHUTDOWN_TIMEOUT = 5
105 REBOOT_TIMEOUT = SHUTDOWN_TIMEOUT + BOOT_TIMEOUT
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800106
107
Chris Sosae146ed82012-09-19 17:58:36 -0700108 LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Simran Basid5e5e272012-09-24 15:23:59 -0700109 RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
110 'host[0-9]+')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700111 LIGHTSENSOR_FILES = ['in_illuminance0_input',
112 'in_illuminance0_raw',
113 'illuminance0_input']
114 LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
115 LABEL_FUNCTIONS = []
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700116
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800117
J. Richard Barnette964fba02012-10-24 17:34:29 -0700118 @staticmethod
119 def get_servo_arguments(arglist):
120 servo_args = {}
121 for arg in ('servo_host', 'servo_port'):
122 if arg in arglist:
123 servo_args[arg] = arglist[arg]
124 return servo_args
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700125
J. Richard Barnette964fba02012-10-24 17:34:29 -0700126
127 def _initialize(self, hostname, servo_args=None, *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700128 """Initialize superclasses, and |self.servo|.
129
130 For creating the host servo object, there are three
131 possibilities: First, if the host is a lab system known to
132 have a servo board, we connect to that servo unconditionally.
133 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700134 servo features for testing, it will pass settings for
135 `servo_host`, `servo_port`, or both. If neither of these
136 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700137
138 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700139 super(SiteHost, self)._initialize(hostname=hostname,
140 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700141 # self.env is a dictionary of environment variable settings
142 # to be exported for commands run on the host.
143 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
144 # errors that might happen.
145 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700146 self._xmlrpc_proxy_map = {}
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700147 self.servo = servo.Servo.get_lab_servo(hostname)
J. Richard Barnettead7da482012-10-30 16:46:52 -0700148 if not self.servo and servo_args is not None:
J. Richard Barnette964fba02012-10-24 17:34:29 -0700149 self.servo = servo.Servo(**servo_args)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700150
151
Chris Sosaa3ac2152012-05-23 22:23:13 -0700152 def machine_install(self, update_url=None, force_update=False,
153 local_devserver=False):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700154 if not update_url and self._parser.options.image:
155 update_url = self._parser.options.image
156 elif not update_url:
157 raise autoupdater.ChromiumOSError(
158 'Update failed. No update URL provided.')
159
160 # Attempt to update the system.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700161 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
162 local_devserver=local_devserver)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700163 if updater.run_update(force_update):
164 # Figure out active and inactive kernel.
165 active_kernel, inactive_kernel = updater.get_kernel_state()
166
167 # Ensure inactive kernel has higher priority than active.
168 if (updater.get_kernel_priority(inactive_kernel)
169 < updater.get_kernel_priority(active_kernel)):
170 raise autoupdater.ChromiumOSError(
171 'Update failed. The priority of the inactive kernel'
172 ' partition is less than that of the active kernel'
173 ' partition.')
174
Scott Zawalski21902002012-09-19 17:57:00 -0400175 update_engine_log = '/var/log/update_engine.log'
176 logging.info('Dumping %s', update_engine_log)
177 self.run('cat %s' % update_engine_log)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800178 # Updater has returned successfully; reboot the host.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700179 self.reboot(timeout=60, wait=True)
Chris Sosae146ed82012-09-19 17:58:36 -0700180 # Touch the lab machine file to leave a marker that distinguishes
181 # this image from other test images.
182 self.run('touch %s' % self.LAB_MACHINE_FILE)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700183
184 # Following the reboot, verify the correct version.
185 updater.check_version()
186
187 # Figure out newly active kernel.
188 new_active_kernel, _ = updater.get_kernel_state()
189
190 # Ensure that previously inactive kernel is now the active kernel.
191 if new_active_kernel != inactive_kernel:
192 raise autoupdater.ChromiumOSError(
193 'Update failed. New kernel partition is not active after'
194 ' boot.')
195
196 host_attributes = site_host_attributes.HostAttributes(self.hostname)
197 if host_attributes.has_chromeos_firmware:
198 # Wait until tries == 0 and success, or until timeout.
199 utils.poll_for_condition(
200 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
201 and updater.get_kernel_success(new_active_kernel)),
202 exception=autoupdater.ChromiumOSError(
203 'Update failed. Timed out waiting for system to mark'
204 ' new kernel as successful.'),
205 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
206
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700207 # Clean up any old autotest directories which may be lying around.
208 for path in global_config.global_config.get_config_value(
209 'AUTOSERV', 'client_autodir_paths', type=list):
210 self.run('rm -rf ' + path)
211
212
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700213 def close(self):
214 super(SiteHost, self).close()
215 self.xmlrpc_disconnect_all()
216
217
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700218 def cleanup(self):
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700219 client_at = autotest.Autotest(self)
Scott Zawalskibb091fc2012-11-15 10:11:49 -0500220 self.run('rm -f %s' % cros_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500221 try:
222 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
223 '_clear_login_prompt_state')
224 self.run('restart ui')
225 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
226 '_wait_for_login_prompt')
227 except error.AutoservRunError:
228 logging.warn('Unable to restart ui, rebooting device.')
229 # Since restarting the UI fails fall back to normal Autotest
230 # cleanup routines, i.e. reboot the machine.
231 super(SiteHost, self).cleanup()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700232
233
Simran Basi154f5582012-10-23 16:27:11 -0700234 # TODO (sbasi) crosbug.com/35656
235 # Renamed the sitehost cleanup method so we don't go down this pathway.
236 # def cleanup(self):
237 def cleanup_poweron(self):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700238 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700239 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700240 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700241 try:
242 self.power_on()
243 except RemotePowerException:
244 # If cleanup has completed but there was an issue with the RPM
245 # Infrastructure, log an error message rather than fail cleanup
246 logging.error('Failed to turn Power On for this host after '
247 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700248
249
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700250 def reboot(self, **dargs):
251 """
252 This function reboots the site host. The more generic
253 RemoteHost.reboot() performs sync and sleeps for 5
254 seconds. This is not necessary for Chrome OS devices as the
255 sync should be finished in a short time during the reboot
256 command.
257 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800258 if 'reboot_cmd' not in dargs:
259 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
260 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700261 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800262 if 'fastsync' not in dargs:
263 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700264 super(SiteHost, self).reboot(**dargs)
265
266
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700267 def verify_software(self):
268 """Ensure the stateful partition has space for Autotest and updates.
269
270 Similar to what is done by AbstractSSH, except instead of checking the
271 Autotest installation path, just check the stateful partition.
272
273 Checking the stateful partition is preferable in case it has been wiped,
274 resulting in an Autotest installation path which doesn't exist and isn't
275 writable. We still want to pass verify in this state since the partition
276 will be recovered with the next install.
277 """
278 super(SiteHost, self).verify_software()
279 self.check_diskspace(
280 '/mnt/stateful_partition',
281 global_config.global_config.get_config_value(
282 'SERVER', 'gb_diskspace_required', type=int,
283 default=20))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700284
285
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700286 def xmlrpc_connect(self, command, port, cleanup=None):
287 """Connect to an XMLRPC server on the host.
288
289 The `command` argument should be a simple shell command that
290 starts an XMLRPC server on the given `port`. The command
291 must not daemonize, and must terminate cleanly on SIGTERM.
292 The command is started in the background on the host, and a
293 local XMLRPC client for the server is created and returned
294 to the caller.
295
296 Note that the process of creating an XMLRPC client makes no
297 attempt to connect to the remote server; the caller is
298 responsible for determining whether the server is running
299 correctly, and is ready to serve requests.
300
301 @param command Shell command to start the server.
302 @param port Port number on which the server is expected to
303 be serving.
304 """
305 self.xmlrpc_disconnect(port)
306
307 # Chrome OS on the target closes down most external ports
308 # for security. We could open the port, but doing that
309 # would conflict with security tests that check that only
310 # expected ports are open. So, to get to the port on the
311 # target we use an ssh tunnel.
312 local_port = utils.get_unused_port()
313 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
314 ssh_cmd = make_ssh_command(opts=tunnel_options)
315 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
316 logging.debug('Full tunnel command: %s', tunnel_cmd)
317 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
318 logging.debug('Started XMLRPC tunnel, local = %d'
319 ' remote = %d, pid = %d',
320 local_port, port, tunnel_proc.pid)
321
322 # Start the server on the host. Redirection in the command
323 # below is necessary, because 'ssh' won't terminate until
324 # background child processes close stdin, stdout, and
325 # stderr.
326 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
327 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
328 logging.debug('Started XMLRPC server on host %s, pid = %s',
329 self.hostname, remote_pid)
330
331 self._xmlrpc_proxy_map[port] = (cleanup, tunnel_proc)
332 rpc_url = 'http://localhost:%d' % local_port
333 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
334
335
336 def xmlrpc_disconnect(self, port):
337 """Disconnect from an XMLRPC server on the host.
338
339 Terminates the remote XMLRPC server previously started for
340 the given `port`. Also closes the local ssh tunnel created
341 for the connection to the host. This function does not
342 directly alter the state of a previously returned XMLRPC
343 client object; however disconnection will cause all
344 subsequent calls to methods on the object to fail.
345
346 This function does nothing if requested to disconnect a port
347 that was not previously connected via `self.xmlrpc_connect()`
348
349 @param port Port number passed to a previous call to
350 `xmlrpc_connect()`
351 """
352 if port not in self._xmlrpc_proxy_map:
353 return
354 entry = self._xmlrpc_proxy_map[port]
355 remote_name = entry[0]
356 tunnel_proc = entry[1]
357 if remote_name:
358 # We use 'pkill' to find our target process rather than
359 # a PID, because the host may have rebooted since
360 # connecting, and we don't want to kill an innocent
361 # process with the same PID.
362 #
363 # 'pkill' helpfully exits with status 1 if no target
364 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700365 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700366 # status.
367 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
368
369 if tunnel_proc.poll() is None:
370 tunnel_proc.terminate()
371 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
372 else:
373 logging.debug('Tunnel pid %d terminated early, status %d',
374 tunnel_proc.pid, tunnel_proc.returncode)
375 del self._xmlrpc_proxy_map[port]
376
377
378 def xmlrpc_disconnect_all(self):
379 """Disconnect all known XMLRPC proxy ports."""
380 for port in self._xmlrpc_proxy_map.keys():
381 self.xmlrpc_disconnect(port)
382
383
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700384 def _ping_is_up(self):
385 """Ping the host once, and return whether it responded."""
386 return utils.ping(self.hostname, tries=1, deadline=1) == 0
387
388
389 def _ping_wait_down(self, timeout):
390 """Wait until the host no longer responds to `ping`.
391
392 @param timeout Minimum time to allow before declaring the
393 host to be non-responsive.
394 """
395
396 # This function is a slightly faster version of wait_down().
397 #
398 # In AbstractSSHHost.wait_down(), `ssh` is used to determine
399 # whether the host is down. In some situations (mine, at
400 # least), `ssh` can take over a minute to determine that the
401 # host is down. The `ping` command answers the question
402 # faster, so we use that here instead.
403 #
404 # There is no equivalent for wait_up(), because a target that
405 # answers to `ping` won't necessarily respond to `ssh`.
406 end_time = time.time() + timeout
407 while time.time() <= end_time:
408 if not self._ping_is_up():
409 return True
410
411 # If the timeout is short relative to the run time of
412 # _ping_is_up(), we might be prone to false failures for
413 # lack of checking frequently enough. To be safe, we make
414 # one last check _after_ the deadline.
415 return not self._ping_is_up()
416
417
418 def test_wait_for_sleep(self):
419 """Wait for the client to enter low-power sleep mode.
420
421 The test for "is asleep" can't distinguish a system that is
422 powered off; to confirm that the unit was asleep, it is
423 necessary to force resume, and then call
424 `test_wait_for_resume()`.
425
426 This function is expected to be called from a test as part
427 of a sequence like the following:
428
429 ~~~~~~~~
430 boot_id = host.get_boot_id()
431 # trigger sleep on the host
432 host.test_wait_for_sleep()
433 # trigger resume on the host
434 host.test_wait_for_resume(boot_id)
435 ~~~~~~~~
436
437 @exception TestFail The host did not go to sleep within
438 the allowed time.
439 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700440 if not self._ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700441 raise error.TestFail(
442 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700443 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700444
445
446 def test_wait_for_resume(self, old_boot_id):
447 """Wait for the client to resume from low-power sleep mode.
448
449 The `old_boot_id` parameter should be the value from
450 `get_boot_id()` obtained prior to entering sleep mode. A
451 `TestFail` exception is raised if the boot id changes.
452
453 See @ref test_wait_for_sleep for more on this function's
454 usage.
455
456 @param[in] old_boot_id A boot id value obtained before the
457 target host went to sleep.
458
459 @exception TestFail The host did not respond within the
460 allowed time.
461 @exception TestFail The host responded, but the boot id test
462 indicated a reboot rather than a sleep
463 cycle.
464 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700465 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700466 raise error.TestFail(
467 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700468 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700469 else:
470 new_boot_id = self.get_boot_id()
471 if new_boot_id != old_boot_id:
472 raise error.TestFail(
473 'client rebooted, but sleep was expected'
474 ' (old boot %s, new boot %s)'
475 % (old_boot_id, new_boot_id))
476
477
478 def test_wait_for_shutdown(self):
479 """Wait for the client to shut down.
480
481 The test for "has shut down" can't distinguish a system that
482 is merely asleep; to confirm that the unit was down, it is
483 necessary to force boot, and then call test_wait_for_boot().
484
485 This function is expected to be called from a test as part
486 of a sequence like the following:
487
488 ~~~~~~~~
489 boot_id = host.get_boot_id()
490 # trigger shutdown on the host
491 host.test_wait_for_shutdown()
492 # trigger boot on the host
493 host.test_wait_for_boot(boot_id)
494 ~~~~~~~~
495
496 @exception TestFail The host did not shut down within the
497 allowed time.
498 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700499 if not self._ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700500 raise error.TestFail(
501 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700502 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700503
504
505 def test_wait_for_boot(self, old_boot_id=None):
506 """Wait for the client to boot from cold power.
507
508 The `old_boot_id` parameter should be the value from
509 `get_boot_id()` obtained prior to shutting down. A
510 `TestFail` exception is raised if the boot id does not
511 change. The boot id test is omitted if `old_boot_id` is not
512 specified.
513
514 See @ref test_wait_for_shutdown for more on this function's
515 usage.
516
517 @param[in] old_boot_id A boot id value obtained before the
518 shut down.
519
520 @exception TestFail The host did not respond within the
521 allowed time.
522 @exception TestFail The host responded, but the boot id test
523 indicated that there was no reboot.
524 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700525 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700526 raise error.TestFail(
527 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700528 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700529 elif old_boot_id:
530 if self.get_boot_id() == old_boot_id:
531 raise error.TestFail(
532 'client is back up, but did not reboot'
533 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700534
535
536 @staticmethod
537 def check_for_rpm_support(hostname):
538 """For a given hostname, return whether or not it is powered by an RPM.
539
540 @return None if this host does not follows the defined naming format
541 for RPM powered DUT's in the lab. If it does follow the format,
542 it returns a regular expression MatchObject instead.
543 """
544 return re.match(SiteHost.RPM_HOSTNAME_REGEX, hostname)
545
546
547 def has_power(self):
548 """For this host, return whether or not it is powered by an RPM.
549
550 @return True if this host is in the CROS lab and follows the defined
551 naming format.
552 """
553 return SiteHost.check_for_rpm_support(self.hostname)
554
555
556 def _set_power(self, new_state):
557 client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
558 if not client.queue_request(self.hostname, new_state):
Simran Basi7adc94f2012-11-20 14:10:40 -0800559 error_msg = ('Failed to change outlet status for host: %s to '
560 'state: %s.' % (self.hostname, new_state))
561 logging.error(error_msg)
562 raise RemotePowerException(error_msg)
563
Simran Basid5e5e272012-09-24 15:23:59 -0700564
565
566 def power_off(self):
567 self._set_power('OFF')
568
569
570 def power_on(self):
571 self._set_power('ON')
572
573
574 def power_cycle(self):
575 self._set_power('CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700576
577
578 def get_platform(self):
579 """Determine the correct platform label for this host.
580
581 @returns a string representing this host's platform.
582 """
583 crossystem = utils.Crossystem(self)
584 crossystem.init()
585 # Extract fwid value and use the leading part as the platform id.
586 # fwid generally follow the format of {platform}.{firmware version}
587 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
588 platform = crossystem.fwid().split('.')[0].lower()
589 # Newer platforms start with 'Google_' while the older ones do not.
590 return platform.replace('google_', '')
591
592
593 @add_function_to_list(LABEL_FUNCTIONS)
594 def get_board(self):
595 """Determine the correct board label for this host.
596
597 @returns a string representing this host's board.
598 """
599 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
600 run_method=self.run)
601 board = release_info['CHROMEOS_RELEASE_BOARD']
602 # Devices in the lab generally have the correct board name but our own
603 # development devices have {board_name}-signed-{key_type}. The board
604 # name may also begin with 'x86-' which we need to keep.
605 if 'x86' not in board:
606 return 'board:%s' % board.split('-')[0]
607 return 'board:%s' % '-'.join(board.split('-')[0:2])
608
609
610 @add_function_to_list(LABEL_FUNCTIONS)
611 def has_lightsensor(self):
612 """Determine the correct board label for this host.
613
614 @returns the string 'lightsensor' if this host has a lightsensor or
615 None if it does not.
616 """
617 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
618 self.LIGHTSENSOR_SEARCH_DIR, '|'.join(self.LIGHTSENSOR_FILES))
619 try:
620 # Run the search cmd following the symlinks. Stderr_tee is set to
621 # None as there can be a symlink loop, but the command will still
622 # execute correctly with a few messages printed to stderr.
623 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
624 return 'lightsensor'
625 except error.AutoservRunError:
626 # egrep exited with a return code of 1 meaning none of the possible
627 # lightsensor files existed.
628 return None
629
630
631 @add_function_to_list(LABEL_FUNCTIONS)
632 def has_bluetooth(self):
633 """Determine the correct board label for this host.
634
635 @returns the string 'bluetooth' if this host has bluetooth or
636 None if it does not.
637 """
638 try:
639 self.run('test -d /sys/class/bluetooth/hci0')
640 # test exited with a return code of 0.
641 return 'bluetooth'
642 except error.AutoservRunError:
643 # test exited with a return code 1 meaning the directory did not
644 # exist.
645 return None
646
647
648 def get_labels(self):
649 """Return a list of labels for this given host.
650
651 This is the main way to retrieve all the automatic labels for a host
652 as it will run through all the currently implemented label functions.
653 """
654 labels = []
655 for label_function in self.LABEL_FUNCTIONS:
656 label = label_function(self)
657 if label:
658 labels.append(label)
659 return labels