blob: 6561508cb140b801e99bc593eb004a3a5a4fc176 [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
Scott Zawalski82464ef2012-11-12 17:32:23 -050012from autotest_lib.client.cros import constants as cros_constants
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070013from autotest_lib.client.common_lib import global_config, error
J. Richard Barnette45e93de2012-04-11 17:24:15 -070014from autotest_lib.client.common_lib.cros import autoupdater
15from autotest_lib.server import autoserv_parser
Chris Sosaf4d43ff2012-10-30 11:21:05 -070016from autotest_lib.server import autotest
J. Richard Barnette45e93de2012-04-11 17:24:15 -070017from autotest_lib.server import site_host_attributes
J. Richard Barnette67ccb872012-04-19 16:34:56 -070018from autotest_lib.server.cros import servo
J. Richard Barnette45e93de2012-04-11 17:24:15 -070019from autotest_lib.server.hosts import remote
J. Richard Barnette24adbf42012-04-11 15:04:53 -070020
21
Simran Basid5e5e272012-09-24 15:23:59 -070022RPM_FRONTEND_URI = global_config.global_config.get_config_value('CROS',
23 'rpm_frontend_uri', type=str, default='')
24
25
26class RemotePowerException(Exception):
27 """This is raised when we fail to set the state of the device's outlet."""
28 pass
29
30
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070031def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
32 connect_timeout=None, alive_interval=None):
33 """Override default make_ssh_command to use options tuned for Chrome OS.
34
35 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070036 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
37 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070038
Dale Curtisaa5eedb2011-08-23 16:18:52 -070039 - ServerAliveInterval=180; which causes SSH to ping connection every
40 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
41 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
42 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070043
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070044 - ServerAliveCountMax=3; consistency with remote_access.sh.
45
46 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
47 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070048
49 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
50 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070051
52 - SSH protocol forced to 2; needed for ServerAliveInterval.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070053 """
54 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
55 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070056 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
57 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
58 ' -o Protocol=2 -l %s -p %d')
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070059 return base_command % (opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070060
61
Simran Basic6f1f7a2012-10-16 10:47:46 -070062def add_function_to_list(functions_list):
63 """Decorator used to group functions together into the provided list."""
64 def add_func(func):
65 functions_list.append(func)
66 return func
67 return add_func
68
69
J. Richard Barnette45e93de2012-04-11 17:24:15 -070070class SiteHost(remote.RemoteHost):
71 """Chromium OS specific subclass of Host."""
72
73 _parser = autoserv_parser.autoserv_parser
74
75 # Time to wait for new kernel to be marked successful.
Chris Masone163cead2012-05-16 11:49:48 -070076 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -070077
78 # Ephemeral file to indicate that an update has just occurred.
79 _JUST_UPDATED_FLAG = '/tmp/just_updated'
80
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070081 # Timeout values associated with various Chrome OS state
82 # changes. In general, the timeouts are the maximum time to
83 # allow between some event X, and the time that the unit is
84 # on (or off) the network. Note that "time to get on the
85 # network" is typically longer than the time to complete the
86 # operation.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070087 #
88 # TODO(jrbarnette): None of these times have been thoroughly
89 # tested empirically; if timeouts are a problem, increasing the
90 # time limit really might be the right answer.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070091 #
92 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
93 # RESUME_TIMEOUT: Time to allow for resume after suspend.
94 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
95 # other things, this includes time for the 30 second dev-mode
96 # screen delay,
97 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
98 # including the 30 second dev-mode delay.
99 # SHUTDOWN_TIMEOUT: Time to allow to shut down.
100 # REBOOT_TIMEOUT: Combination of shutdown and reboot times.
101
102 SLEEP_TIMEOUT = 2
103 RESUME_TIMEOUT = 5
104 BOOT_TIMEOUT = 45
105 USB_BOOT_TIMEOUT = 150
106 SHUTDOWN_TIMEOUT = 5
107 REBOOT_TIMEOUT = SHUTDOWN_TIMEOUT + BOOT_TIMEOUT
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
J. Richard Barnette964fba02012-10-24 17:34:29 -0700117 @staticmethod
118 def get_servo_arguments(arglist):
119 servo_args = {}
120 for arg in ('servo_host', 'servo_port'):
121 if arg in arglist:
122 servo_args[arg] = arglist[arg]
123 return servo_args
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700124
J. Richard Barnette964fba02012-10-24 17:34:29 -0700125
126 def _initialize(self, hostname, servo_args=None, *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700127 """Initialize superclasses, and |self.servo|.
128
129 For creating the host servo object, there are three
130 possibilities: First, if the host is a lab system known to
131 have a servo board, we connect to that servo unconditionally.
132 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700133 servo features for testing, it will pass settings for
134 `servo_host`, `servo_port`, or both. If neither of these
135 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700136
137 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700138 super(SiteHost, self)._initialize(hostname=hostname,
139 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700140 # self.env is a dictionary of environment variable settings
141 # to be exported for commands run on the host.
142 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
143 # errors that might happen.
144 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700145 self._xmlrpc_proxy_map = {}
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700146 self.servo = servo.Servo.get_lab_servo(hostname)
J. Richard Barnettead7da482012-10-30 16:46:52 -0700147 if not self.servo and servo_args is not None:
J. Richard Barnette964fba02012-10-24 17:34:29 -0700148 self.servo = servo.Servo(**servo_args)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700149
150
Chris Sosaa3ac2152012-05-23 22:23:13 -0700151 def machine_install(self, update_url=None, force_update=False,
152 local_devserver=False):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700153 if not update_url and self._parser.options.image:
154 update_url = self._parser.options.image
155 elif not update_url:
156 raise autoupdater.ChromiumOSError(
157 'Update failed. No update URL provided.')
158
159 # Attempt to update the system.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700160 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
161 local_devserver=local_devserver)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700162 if updater.run_update(force_update):
163 # Figure out active and inactive kernel.
164 active_kernel, inactive_kernel = updater.get_kernel_state()
165
166 # Ensure inactive kernel has higher priority than active.
167 if (updater.get_kernel_priority(inactive_kernel)
168 < updater.get_kernel_priority(active_kernel)):
169 raise autoupdater.ChromiumOSError(
170 'Update failed. The priority of the inactive kernel'
171 ' partition is less than that of the active kernel'
172 ' partition.')
173
Scott Zawalski21902002012-09-19 17:57:00 -0400174 update_engine_log = '/var/log/update_engine.log'
175 logging.info('Dumping %s', update_engine_log)
176 self.run('cat %s' % update_engine_log)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700177 # Updater has returned, successfully, reboot the host.
178 self.reboot(timeout=60, wait=True)
Chris Sosae146ed82012-09-19 17:58:36 -0700179 # Touch the lab machine file to leave a marker that distinguishes
180 # this image from other test images.
181 self.run('touch %s' % self.LAB_MACHINE_FILE)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700182
183 # Following the reboot, verify the correct version.
184 updater.check_version()
185
186 # Figure out newly active kernel.
187 new_active_kernel, _ = updater.get_kernel_state()
188
189 # Ensure that previously inactive kernel is now the active kernel.
190 if new_active_kernel != inactive_kernel:
191 raise autoupdater.ChromiumOSError(
192 'Update failed. New kernel partition is not active after'
193 ' boot.')
194
195 host_attributes = site_host_attributes.HostAttributes(self.hostname)
196 if host_attributes.has_chromeos_firmware:
197 # Wait until tries == 0 and success, or until timeout.
198 utils.poll_for_condition(
199 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
200 and updater.get_kernel_success(new_active_kernel)),
201 exception=autoupdater.ChromiumOSError(
202 'Update failed. Timed out waiting for system to mark'
203 ' new kernel as successful.'),
204 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
205
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700206 # Mark host as recently updated. Hosts are rebooted at the end of
207 # every test cycle which will remove the file.
208 self.run('touch %s' % self._JUST_UPDATED_FLAG)
209
210 # Clean up any old autotest directories which may be lying around.
211 for path in global_config.global_config.get_config_value(
212 'AUTOSERV', 'client_autodir_paths', type=list):
213 self.run('rm -rf ' + path)
214
215
216 def has_just_updated(self):
217 """Indicates whether the host was updated within this boot."""
218 # Check for the existence of the just updated flag file.
219 return self.run(
220 '[ -f %s ] && echo T || echo F'
221 % self._JUST_UPDATED_FLAG).stdout.strip() == 'T'
222
223
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700224 def close(self):
225 super(SiteHost, self).close()
226 self.xmlrpc_disconnect_all()
227
228
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700229 def cleanup(self):
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700230 client_at = autotest.Autotest(self)
Scott Zawalskibb091fc2012-11-15 10:11:49 -0500231 self.run('rm -f %s' % cros_constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500232 try:
233 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
234 '_clear_login_prompt_state')
235 self.run('restart ui')
236 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
237 '_wait_for_login_prompt')
238 except error.AutoservRunError:
239 logging.warn('Unable to restart ui, rebooting device.')
240 # Since restarting the UI fails fall back to normal Autotest
241 # cleanup routines, i.e. reboot the machine.
242 super(SiteHost, self).cleanup()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700243
244
Simran Basi154f5582012-10-23 16:27:11 -0700245 # TODO (sbasi) crosbug.com/35656
246 # Renamed the sitehost cleanup method so we don't go down this pathway.
247 # def cleanup(self):
248 def cleanup_poweron(self):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700249 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700250 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700251 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700252 try:
253 self.power_on()
254 except RemotePowerException:
255 # If cleanup has completed but there was an issue with the RPM
256 # Infrastructure, log an error message rather than fail cleanup
257 logging.error('Failed to turn Power On for this host after '
258 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700259
260
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700261 def reboot(self, **dargs):
262 """
263 This function reboots the site host. The more generic
264 RemoteHost.reboot() performs sync and sleeps for 5
265 seconds. This is not necessary for Chrome OS devices as the
266 sync should be finished in a short time during the reboot
267 command.
268 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800269 if 'reboot_cmd' not in dargs:
270 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
271 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700272 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800273 if 'fastsync' not in dargs:
274 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700275 super(SiteHost, self).reboot(**dargs)
276
277
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700278 def verify_software(self):
279 """Ensure the stateful partition has space for Autotest and updates.
280
281 Similar to what is done by AbstractSSH, except instead of checking the
282 Autotest installation path, just check the stateful partition.
283
284 Checking the stateful partition is preferable in case it has been wiped,
285 resulting in an Autotest installation path which doesn't exist and isn't
286 writable. We still want to pass verify in this state since the partition
287 will be recovered with the next install.
288 """
289 super(SiteHost, self).verify_software()
290 self.check_diskspace(
291 '/mnt/stateful_partition',
292 global_config.global_config.get_config_value(
293 'SERVER', 'gb_diskspace_required', type=int,
294 default=20))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700295
296
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700297 def xmlrpc_connect(self, command, port, cleanup=None):
298 """Connect to an XMLRPC server on the host.
299
300 The `command` argument should be a simple shell command that
301 starts an XMLRPC server on the given `port`. The command
302 must not daemonize, and must terminate cleanly on SIGTERM.
303 The command is started in the background on the host, and a
304 local XMLRPC client for the server is created and returned
305 to the caller.
306
307 Note that the process of creating an XMLRPC client makes no
308 attempt to connect to the remote server; the caller is
309 responsible for determining whether the server is running
310 correctly, and is ready to serve requests.
311
312 @param command Shell command to start the server.
313 @param port Port number on which the server is expected to
314 be serving.
315 """
316 self.xmlrpc_disconnect(port)
317
318 # Chrome OS on the target closes down most external ports
319 # for security. We could open the port, but doing that
320 # would conflict with security tests that check that only
321 # expected ports are open. So, to get to the port on the
322 # target we use an ssh tunnel.
323 local_port = utils.get_unused_port()
324 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
325 ssh_cmd = make_ssh_command(opts=tunnel_options)
326 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
327 logging.debug('Full tunnel command: %s', tunnel_cmd)
328 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
329 logging.debug('Started XMLRPC tunnel, local = %d'
330 ' remote = %d, pid = %d',
331 local_port, port, tunnel_proc.pid)
332
333 # Start the server on the host. Redirection in the command
334 # below is necessary, because 'ssh' won't terminate until
335 # background child processes close stdin, stdout, and
336 # stderr.
337 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
338 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
339 logging.debug('Started XMLRPC server on host %s, pid = %s',
340 self.hostname, remote_pid)
341
342 self._xmlrpc_proxy_map[port] = (cleanup, tunnel_proc)
343 rpc_url = 'http://localhost:%d' % local_port
344 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
345
346
347 def xmlrpc_disconnect(self, port):
348 """Disconnect from an XMLRPC server on the host.
349
350 Terminates the remote XMLRPC server previously started for
351 the given `port`. Also closes the local ssh tunnel created
352 for the connection to the host. This function does not
353 directly alter the state of a previously returned XMLRPC
354 client object; however disconnection will cause all
355 subsequent calls to methods on the object to fail.
356
357 This function does nothing if requested to disconnect a port
358 that was not previously connected via `self.xmlrpc_connect()`
359
360 @param port Port number passed to a previous call to
361 `xmlrpc_connect()`
362 """
363 if port not in self._xmlrpc_proxy_map:
364 return
365 entry = self._xmlrpc_proxy_map[port]
366 remote_name = entry[0]
367 tunnel_proc = entry[1]
368 if remote_name:
369 # We use 'pkill' to find our target process rather than
370 # a PID, because the host may have rebooted since
371 # connecting, and we don't want to kill an innocent
372 # process with the same PID.
373 #
374 # 'pkill' helpfully exits with status 1 if no target
375 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700376 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700377 # status.
378 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
379
380 if tunnel_proc.poll() is None:
381 tunnel_proc.terminate()
382 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
383 else:
384 logging.debug('Tunnel pid %d terminated early, status %d',
385 tunnel_proc.pid, tunnel_proc.returncode)
386 del self._xmlrpc_proxy_map[port]
387
388
389 def xmlrpc_disconnect_all(self):
390 """Disconnect all known XMLRPC proxy ports."""
391 for port in self._xmlrpc_proxy_map.keys():
392 self.xmlrpc_disconnect(port)
393
394
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700395 def _ping_is_up(self):
396 """Ping the host once, and return whether it responded."""
397 return utils.ping(self.hostname, tries=1, deadline=1) == 0
398
399
400 def _ping_wait_down(self, timeout):
401 """Wait until the host no longer responds to `ping`.
402
403 @param timeout Minimum time to allow before declaring the
404 host to be non-responsive.
405 """
406
407 # This function is a slightly faster version of wait_down().
408 #
409 # In AbstractSSHHost.wait_down(), `ssh` is used to determine
410 # whether the host is down. In some situations (mine, at
411 # least), `ssh` can take over a minute to determine that the
412 # host is down. The `ping` command answers the question
413 # faster, so we use that here instead.
414 #
415 # There is no equivalent for wait_up(), because a target that
416 # answers to `ping` won't necessarily respond to `ssh`.
417 end_time = time.time() + timeout
418 while time.time() <= end_time:
419 if not self._ping_is_up():
420 return True
421
422 # If the timeout is short relative to the run time of
423 # _ping_is_up(), we might be prone to false failures for
424 # lack of checking frequently enough. To be safe, we make
425 # one last check _after_ the deadline.
426 return not self._ping_is_up()
427
428
429 def test_wait_for_sleep(self):
430 """Wait for the client to enter low-power sleep mode.
431
432 The test for "is asleep" can't distinguish a system that is
433 powered off; to confirm that the unit was asleep, it is
434 necessary to force resume, and then call
435 `test_wait_for_resume()`.
436
437 This function is expected to be called from a test as part
438 of a sequence like the following:
439
440 ~~~~~~~~
441 boot_id = host.get_boot_id()
442 # trigger sleep on the host
443 host.test_wait_for_sleep()
444 # trigger resume on the host
445 host.test_wait_for_resume(boot_id)
446 ~~~~~~~~
447
448 @exception TestFail The host did not go to sleep within
449 the allowed time.
450 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700451 if not self._ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700452 raise error.TestFail(
453 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700454 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700455
456
457 def test_wait_for_resume(self, old_boot_id):
458 """Wait for the client to resume from low-power sleep mode.
459
460 The `old_boot_id` parameter should be the value from
461 `get_boot_id()` obtained prior to entering sleep mode. A
462 `TestFail` exception is raised if the boot id changes.
463
464 See @ref test_wait_for_sleep for more on this function's
465 usage.
466
467 @param[in] old_boot_id A boot id value obtained before the
468 target host went to sleep.
469
470 @exception TestFail The host did not respond within the
471 allowed time.
472 @exception TestFail The host responded, but the boot id test
473 indicated a reboot rather than a sleep
474 cycle.
475 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700476 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700477 raise error.TestFail(
478 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700479 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700480 else:
481 new_boot_id = self.get_boot_id()
482 if new_boot_id != old_boot_id:
483 raise error.TestFail(
484 'client rebooted, but sleep was expected'
485 ' (old boot %s, new boot %s)'
486 % (old_boot_id, new_boot_id))
487
488
489 def test_wait_for_shutdown(self):
490 """Wait for the client to shut down.
491
492 The test for "has shut down" can't distinguish a system that
493 is merely asleep; to confirm that the unit was down, it is
494 necessary to force boot, and then call test_wait_for_boot().
495
496 This function is expected to be called from a test as part
497 of a sequence like the following:
498
499 ~~~~~~~~
500 boot_id = host.get_boot_id()
501 # trigger shutdown on the host
502 host.test_wait_for_shutdown()
503 # trigger boot on the host
504 host.test_wait_for_boot(boot_id)
505 ~~~~~~~~
506
507 @exception TestFail The host did not shut down within the
508 allowed time.
509 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700510 if not self._ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700511 raise error.TestFail(
512 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700513 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700514
515
516 def test_wait_for_boot(self, old_boot_id=None):
517 """Wait for the client to boot from cold power.
518
519 The `old_boot_id` parameter should be the value from
520 `get_boot_id()` obtained prior to shutting down. A
521 `TestFail` exception is raised if the boot id does not
522 change. The boot id test is omitted if `old_boot_id` is not
523 specified.
524
525 See @ref test_wait_for_shutdown for more on this function's
526 usage.
527
528 @param[in] old_boot_id A boot id value obtained before the
529 shut down.
530
531 @exception TestFail The host did not respond within the
532 allowed time.
533 @exception TestFail The host responded, but the boot id test
534 indicated that there was no reboot.
535 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700536 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700537 raise error.TestFail(
538 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700539 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700540 elif old_boot_id:
541 if self.get_boot_id() == old_boot_id:
542 raise error.TestFail(
543 'client is back up, but did not reboot'
544 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700545
546
547 @staticmethod
548 def check_for_rpm_support(hostname):
549 """For a given hostname, return whether or not it is powered by an RPM.
550
551 @return None if this host does not follows the defined naming format
552 for RPM powered DUT's in the lab. If it does follow the format,
553 it returns a regular expression MatchObject instead.
554 """
555 return re.match(SiteHost.RPM_HOSTNAME_REGEX, hostname)
556
557
558 def has_power(self):
559 """For this host, return whether or not it is powered by an RPM.
560
561 @return True if this host is in the CROS lab and follows the defined
562 naming format.
563 """
564 return SiteHost.check_for_rpm_support(self.hostname)
565
566
567 def _set_power(self, new_state):
568 client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
569 if not client.queue_request(self.hostname, new_state):
570 raise RemotePowerException('Failed to change outlet status for'
571 'host: %s to state: %s', self.hostname,
572 new_state)
573
574
575 def power_off(self):
576 self._set_power('OFF')
577
578
579 def power_on(self):
580 self._set_power('ON')
581
582
583 def power_cycle(self):
584 self._set_power('CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700585
586
587 def get_platform(self):
588 """Determine the correct platform label for this host.
589
590 @returns a string representing this host's platform.
591 """
592 crossystem = utils.Crossystem(self)
593 crossystem.init()
594 # Extract fwid value and use the leading part as the platform id.
595 # fwid generally follow the format of {platform}.{firmware version}
596 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
597 platform = crossystem.fwid().split('.')[0].lower()
598 # Newer platforms start with 'Google_' while the older ones do not.
599 return platform.replace('google_', '')
600
601
602 @add_function_to_list(LABEL_FUNCTIONS)
603 def get_board(self):
604 """Determine the correct board label for this host.
605
606 @returns a string representing this host's board.
607 """
608 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
609 run_method=self.run)
610 board = release_info['CHROMEOS_RELEASE_BOARD']
611 # Devices in the lab generally have the correct board name but our own
612 # development devices have {board_name}-signed-{key_type}. The board
613 # name may also begin with 'x86-' which we need to keep.
614 if 'x86' not in board:
615 return 'board:%s' % board.split('-')[0]
616 return 'board:%s' % '-'.join(board.split('-')[0:2])
617
618
619 @add_function_to_list(LABEL_FUNCTIONS)
620 def has_lightsensor(self):
621 """Determine the correct board label for this host.
622
623 @returns the string 'lightsensor' if this host has a lightsensor or
624 None if it does not.
625 """
626 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
627 self.LIGHTSENSOR_SEARCH_DIR, '|'.join(self.LIGHTSENSOR_FILES))
628 try:
629 # Run the search cmd following the symlinks. Stderr_tee is set to
630 # None as there can be a symlink loop, but the command will still
631 # execute correctly with a few messages printed to stderr.
632 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
633 return 'lightsensor'
634 except error.AutoservRunError:
635 # egrep exited with a return code of 1 meaning none of the possible
636 # lightsensor files existed.
637 return None
638
639
640 @add_function_to_list(LABEL_FUNCTIONS)
641 def has_bluetooth(self):
642 """Determine the correct board label for this host.
643
644 @returns the string 'bluetooth' if this host has bluetooth or
645 None if it does not.
646 """
647 try:
648 self.run('test -d /sys/class/bluetooth/hci0')
649 # test exited with a return code of 0.
650 return 'bluetooth'
651 except error.AutoservRunError:
652 # test exited with a return code 1 meaning the directory did not
653 # exist.
654 return None
655
656
657 def get_labels(self):
658 """Return a list of labels for this given host.
659
660 This is the main way to retrieve all the automatic labels for a host
661 as it will run through all the currently implemented label functions.
662 """
663 labels = []
664 for label_function in self.LABEL_FUNCTIONS:
665 label = label_function(self)
666 if label:
667 labels.append(label)
668 return labels