blob: 31a57c8942350bdf4871536abe672d5c165dd87a [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
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070012from autotest_lib.client.common_lib import global_config, error
J. Richard Barnette45e93de2012-04-11 17:24:15 -070013from autotest_lib.client.common_lib.cros import autoupdater
14from autotest_lib.server import autoserv_parser
15from autotest_lib.server import site_host_attributes
J. Richard Barnette67ccb872012-04-19 16:34:56 -070016from autotest_lib.server.cros import servo
J. Richard Barnette45e93de2012-04-11 17:24:15 -070017from autotest_lib.server.hosts import remote
J. Richard Barnette24adbf42012-04-11 15:04:53 -070018
19
Simran Basid5e5e272012-09-24 15:23:59 -070020RPM_FRONTEND_URI = global_config.global_config.get_config_value('CROS',
21 'rpm_frontend_uri', type=str, default='')
22
23
24class RemotePowerException(Exception):
25 """This is raised when we fail to set the state of the device's outlet."""
26 pass
27
28
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070029def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
30 connect_timeout=None, alive_interval=None):
31 """Override default make_ssh_command to use options tuned for Chrome OS.
32
33 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070034 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
35 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070036
Dale Curtisaa5eedb2011-08-23 16:18:52 -070037 - ServerAliveInterval=180; which causes SSH to ping connection every
38 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
39 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
40 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070041
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070042 - ServerAliveCountMax=3; consistency with remote_access.sh.
43
44 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
45 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070046
47 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
48 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070049
50 - SSH protocol forced to 2; needed for ServerAliveInterval.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070051 """
52 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
53 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070054 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
55 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
56 ' -o Protocol=2 -l %s -p %d')
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070057 return base_command % (opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070058
59
Simran Basic6f1f7a2012-10-16 10:47:46 -070060def add_function_to_list(functions_list):
61 """Decorator used to group functions together into the provided list."""
62 def add_func(func):
63 functions_list.append(func)
64 return func
65 return add_func
66
67
J. Richard Barnette45e93de2012-04-11 17:24:15 -070068class SiteHost(remote.RemoteHost):
69 """Chromium OS specific subclass of Host."""
70
71 _parser = autoserv_parser.autoserv_parser
72
73 # Time to wait for new kernel to be marked successful.
Chris Masone163cead2012-05-16 11:49:48 -070074 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -070075
76 # Ephemeral file to indicate that an update has just occurred.
77 _JUST_UPDATED_FLAG = '/tmp/just_updated'
78
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070079 # Timeout values associated with various Chrome OS state
80 # changes. In general, the timeouts are the maximum time to
81 # allow between some event X, and the time that the unit is
82 # on (or off) the network. Note that "time to get on the
83 # network" is typically longer than the time to complete the
84 # operation.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070085 #
86 # TODO(jrbarnette): None of these times have been thoroughly
87 # tested empirically; if timeouts are a problem, increasing the
88 # time limit really might be the right answer.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070089 #
90 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
91 # RESUME_TIMEOUT: Time to allow for resume after suspend.
92 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
93 # other things, this includes time for the 30 second dev-mode
94 # screen delay,
95 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
96 # including the 30 second dev-mode delay.
97 # SHUTDOWN_TIMEOUT: Time to allow to shut down.
98 # 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
Chris Sosae146ed82012-09-19 17:58:36 -0700106 LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Simran Basid5e5e272012-09-24 15:23:59 -0700107 RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
108 'host[0-9]+')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700109 LIGHTSENSOR_FILES = ['in_illuminance0_input',
110 'in_illuminance0_raw',
111 'illuminance0_input']
112 LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
113 LABEL_FUNCTIONS = []
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700114
115
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700116 def _initialize(self, hostname, servo_host=None, servo_port=None,
117 *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700118 """Initialize superclasses, and |self.servo|.
119
120 For creating the host servo object, there are three
121 possibilities: First, if the host is a lab system known to
122 have a servo board, we connect to that servo unconditionally.
123 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700124 servo features for testing, it will pass settings for
125 `servo_host`, `servo_port`, or both. If neither of these
126 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700127
128 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700129 super(SiteHost, self)._initialize(hostname=hostname,
130 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700131 # self.env is a dictionary of environment variable settings
132 # to be exported for commands run on the host.
133 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
134 # errors that might happen.
135 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700136 self._xmlrpc_proxy_map = {}
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700137 self.servo = servo.Servo.get_lab_servo(hostname)
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700138 if not self.servo:
139 # The Servo constructor generally doesn't accept 'None'
140 # for its parameters.
141 if servo_host is not None:
142 if servo_port is not None:
143 self.servo = servo.Servo(servo_host=servo_host,
144 servo_port=servo_port)
145 else:
146 self.servo = servo.Servo(servo_host=servo_host)
147 elif servo_port is not None:
148 self.servo = servo.Servo(servo_port=servo_port)
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
206 # TODO(dalecurtis): Hack for R12 builds to make sure BVT runs of
207 # platform_Shutdown pass correctly.
208 if updater.update_version.startswith('0.12'):
209 self.reboot(timeout=60, wait=True)
210
211 # Mark host as recently updated. Hosts are rebooted at the end of
212 # every test cycle which will remove the file.
213 self.run('touch %s' % self._JUST_UPDATED_FLAG)
214
215 # Clean up any old autotest directories which may be lying around.
216 for path in global_config.global_config.get_config_value(
217 'AUTOSERV', 'client_autodir_paths', type=list):
218 self.run('rm -rf ' + path)
219
220
221 def has_just_updated(self):
222 """Indicates whether the host was updated within this boot."""
223 # Check for the existence of the just updated flag file.
224 return self.run(
225 '[ -f %s ] && echo T || echo F'
226 % self._JUST_UPDATED_FLAG).stdout.strip() == 'T'
227
228
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700229 def close(self):
230 super(SiteHost, self).close()
231 self.xmlrpc_disconnect_all()
232
233
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700234 def cleanup(self):
235 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700236 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700237 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700238 try:
239 self.power_on()
240 except RemotePowerException:
241 # If cleanup has completed but there was an issue with the RPM
242 # Infrastructure, log an error message rather than fail cleanup
243 logging.error('Failed to turn Power On for this host after '
244 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700245
246
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700247 def reboot(self, **dargs):
248 """
249 This function reboots the site host. The more generic
250 RemoteHost.reboot() performs sync and sleeps for 5
251 seconds. This is not necessary for Chrome OS devices as the
252 sync should be finished in a short time during the reboot
253 command.
254 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800255 if 'reboot_cmd' not in dargs:
256 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
257 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700258 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800259 if 'fastsync' not in dargs:
260 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700261 super(SiteHost, self).reboot(**dargs)
262
263
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700264 def verify_software(self):
265 """Ensure the stateful partition has space for Autotest and updates.
266
267 Similar to what is done by AbstractSSH, except instead of checking the
268 Autotest installation path, just check the stateful partition.
269
270 Checking the stateful partition is preferable in case it has been wiped,
271 resulting in an Autotest installation path which doesn't exist and isn't
272 writable. We still want to pass verify in this state since the partition
273 will be recovered with the next install.
274 """
275 super(SiteHost, self).verify_software()
276 self.check_diskspace(
277 '/mnt/stateful_partition',
278 global_config.global_config.get_config_value(
279 'SERVER', 'gb_diskspace_required', type=int,
280 default=20))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700281
282
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700283 def xmlrpc_connect(self, command, port, cleanup=None):
284 """Connect to an XMLRPC server on the host.
285
286 The `command` argument should be a simple shell command that
287 starts an XMLRPC server on the given `port`. The command
288 must not daemonize, and must terminate cleanly on SIGTERM.
289 The command is started in the background on the host, and a
290 local XMLRPC client for the server is created and returned
291 to the caller.
292
293 Note that the process of creating an XMLRPC client makes no
294 attempt to connect to the remote server; the caller is
295 responsible for determining whether the server is running
296 correctly, and is ready to serve requests.
297
298 @param command Shell command to start the server.
299 @param port Port number on which the server is expected to
300 be serving.
301 """
302 self.xmlrpc_disconnect(port)
303
304 # Chrome OS on the target closes down most external ports
305 # for security. We could open the port, but doing that
306 # would conflict with security tests that check that only
307 # expected ports are open. So, to get to the port on the
308 # target we use an ssh tunnel.
309 local_port = utils.get_unused_port()
310 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
311 ssh_cmd = make_ssh_command(opts=tunnel_options)
312 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
313 logging.debug('Full tunnel command: %s', tunnel_cmd)
314 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
315 logging.debug('Started XMLRPC tunnel, local = %d'
316 ' remote = %d, pid = %d',
317 local_port, port, tunnel_proc.pid)
318
319 # Start the server on the host. Redirection in the command
320 # below is necessary, because 'ssh' won't terminate until
321 # background child processes close stdin, stdout, and
322 # stderr.
323 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
324 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
325 logging.debug('Started XMLRPC server on host %s, pid = %s',
326 self.hostname, remote_pid)
327
328 self._xmlrpc_proxy_map[port] = (cleanup, tunnel_proc)
329 rpc_url = 'http://localhost:%d' % local_port
330 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
331
332
333 def xmlrpc_disconnect(self, port):
334 """Disconnect from an XMLRPC server on the host.
335
336 Terminates the remote XMLRPC server previously started for
337 the given `port`. Also closes the local ssh tunnel created
338 for the connection to the host. This function does not
339 directly alter the state of a previously returned XMLRPC
340 client object; however disconnection will cause all
341 subsequent calls to methods on the object to fail.
342
343 This function does nothing if requested to disconnect a port
344 that was not previously connected via `self.xmlrpc_connect()`
345
346 @param port Port number passed to a previous call to
347 `xmlrpc_connect()`
348 """
349 if port not in self._xmlrpc_proxy_map:
350 return
351 entry = self._xmlrpc_proxy_map[port]
352 remote_name = entry[0]
353 tunnel_proc = entry[1]
354 if remote_name:
355 # We use 'pkill' to find our target process rather than
356 # a PID, because the host may have rebooted since
357 # connecting, and we don't want to kill an innocent
358 # process with the same PID.
359 #
360 # 'pkill' helpfully exits with status 1 if no target
361 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700362 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700363 # status.
364 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
365
366 if tunnel_proc.poll() is None:
367 tunnel_proc.terminate()
368 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
369 else:
370 logging.debug('Tunnel pid %d terminated early, status %d',
371 tunnel_proc.pid, tunnel_proc.returncode)
372 del self._xmlrpc_proxy_map[port]
373
374
375 def xmlrpc_disconnect_all(self):
376 """Disconnect all known XMLRPC proxy ports."""
377 for port in self._xmlrpc_proxy_map.keys():
378 self.xmlrpc_disconnect(port)
379
380
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700381 def _ping_is_up(self):
382 """Ping the host once, and return whether it responded."""
383 return utils.ping(self.hostname, tries=1, deadline=1) == 0
384
385
386 def _ping_wait_down(self, timeout):
387 """Wait until the host no longer responds to `ping`.
388
389 @param timeout Minimum time to allow before declaring the
390 host to be non-responsive.
391 """
392
393 # This function is a slightly faster version of wait_down().
394 #
395 # In AbstractSSHHost.wait_down(), `ssh` is used to determine
396 # whether the host is down. In some situations (mine, at
397 # least), `ssh` can take over a minute to determine that the
398 # host is down. The `ping` command answers the question
399 # faster, so we use that here instead.
400 #
401 # There is no equivalent for wait_up(), because a target that
402 # answers to `ping` won't necessarily respond to `ssh`.
403 end_time = time.time() + timeout
404 while time.time() <= end_time:
405 if not self._ping_is_up():
406 return True
407
408 # If the timeout is short relative to the run time of
409 # _ping_is_up(), we might be prone to false failures for
410 # lack of checking frequently enough. To be safe, we make
411 # one last check _after_ the deadline.
412 return not self._ping_is_up()
413
414
415 def test_wait_for_sleep(self):
416 """Wait for the client to enter low-power sleep mode.
417
418 The test for "is asleep" can't distinguish a system that is
419 powered off; to confirm that the unit was asleep, it is
420 necessary to force resume, and then call
421 `test_wait_for_resume()`.
422
423 This function is expected to be called from a test as part
424 of a sequence like the following:
425
426 ~~~~~~~~
427 boot_id = host.get_boot_id()
428 # trigger sleep on the host
429 host.test_wait_for_sleep()
430 # trigger resume on the host
431 host.test_wait_for_resume(boot_id)
432 ~~~~~~~~
433
434 @exception TestFail The host did not go to sleep within
435 the allowed time.
436 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700437 if not self._ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700438 raise error.TestFail(
439 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700440 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700441
442
443 def test_wait_for_resume(self, old_boot_id):
444 """Wait for the client to resume from low-power sleep mode.
445
446 The `old_boot_id` parameter should be the value from
447 `get_boot_id()` obtained prior to entering sleep mode. A
448 `TestFail` exception is raised if the boot id changes.
449
450 See @ref test_wait_for_sleep for more on this function's
451 usage.
452
453 @param[in] old_boot_id A boot id value obtained before the
454 target host went to sleep.
455
456 @exception TestFail The host did not respond within the
457 allowed time.
458 @exception TestFail The host responded, but the boot id test
459 indicated a reboot rather than a sleep
460 cycle.
461 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700462 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700463 raise error.TestFail(
464 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700465 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700466 else:
467 new_boot_id = self.get_boot_id()
468 if new_boot_id != old_boot_id:
469 raise error.TestFail(
470 'client rebooted, but sleep was expected'
471 ' (old boot %s, new boot %s)'
472 % (old_boot_id, new_boot_id))
473
474
475 def test_wait_for_shutdown(self):
476 """Wait for the client to shut down.
477
478 The test for "has shut down" can't distinguish a system that
479 is merely asleep; to confirm that the unit was down, it is
480 necessary to force boot, and then call test_wait_for_boot().
481
482 This function is expected to be called from a test as part
483 of a sequence like the following:
484
485 ~~~~~~~~
486 boot_id = host.get_boot_id()
487 # trigger shutdown on the host
488 host.test_wait_for_shutdown()
489 # trigger boot on the host
490 host.test_wait_for_boot(boot_id)
491 ~~~~~~~~
492
493 @exception TestFail The host did not shut down within the
494 allowed time.
495 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700496 if not self._ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700497 raise error.TestFail(
498 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700499 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700500
501
502 def test_wait_for_boot(self, old_boot_id=None):
503 """Wait for the client to boot from cold power.
504
505 The `old_boot_id` parameter should be the value from
506 `get_boot_id()` obtained prior to shutting down. A
507 `TestFail` exception is raised if the boot id does not
508 change. The boot id test is omitted if `old_boot_id` is not
509 specified.
510
511 See @ref test_wait_for_shutdown for more on this function's
512 usage.
513
514 @param[in] old_boot_id A boot id value obtained before the
515 shut down.
516
517 @exception TestFail The host did not respond within the
518 allowed time.
519 @exception TestFail The host responded, but the boot id test
520 indicated that there was no reboot.
521 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700522 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700523 raise error.TestFail(
524 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700525 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700526 elif old_boot_id:
527 if self.get_boot_id() == old_boot_id:
528 raise error.TestFail(
529 'client is back up, but did not reboot'
530 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700531
532
533 @staticmethod
534 def check_for_rpm_support(hostname):
535 """For a given hostname, return whether or not it is powered by an RPM.
536
537 @return None if this host does not follows the defined naming format
538 for RPM powered DUT's in the lab. If it does follow the format,
539 it returns a regular expression MatchObject instead.
540 """
541 return re.match(SiteHost.RPM_HOSTNAME_REGEX, hostname)
542
543
544 def has_power(self):
545 """For this host, return whether or not it is powered by an RPM.
546
547 @return True if this host is in the CROS lab and follows the defined
548 naming format.
549 """
550 return SiteHost.check_for_rpm_support(self.hostname)
551
552
553 def _set_power(self, new_state):
554 client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
555 if not client.queue_request(self.hostname, new_state):
556 raise RemotePowerException('Failed to change outlet status for'
557 'host: %s to state: %s', self.hostname,
558 new_state)
559
560
561 def power_off(self):
562 self._set_power('OFF')
563
564
565 def power_on(self):
566 self._set_power('ON')
567
568
569 def power_cycle(self):
570 self._set_power('CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700571
572
573 def get_platform(self):
574 """Determine the correct platform label for this host.
575
576 @returns a string representing this host's platform.
577 """
578 crossystem = utils.Crossystem(self)
579 crossystem.init()
580 # Extract fwid value and use the leading part as the platform id.
581 # fwid generally follow the format of {platform}.{firmware version}
582 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
583 platform = crossystem.fwid().split('.')[0].lower()
584 # Newer platforms start with 'Google_' while the older ones do not.
585 return platform.replace('google_', '')
586
587
588 @add_function_to_list(LABEL_FUNCTIONS)
589 def get_board(self):
590 """Determine the correct board label for this host.
591
592 @returns a string representing this host's board.
593 """
594 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
595 run_method=self.run)
596 board = release_info['CHROMEOS_RELEASE_BOARD']
597 # Devices in the lab generally have the correct board name but our own
598 # development devices have {board_name}-signed-{key_type}. The board
599 # name may also begin with 'x86-' which we need to keep.
600 if 'x86' not in board:
601 return 'board:%s' % board.split('-')[0]
602 return 'board:%s' % '-'.join(board.split('-')[0:2])
603
604
605 @add_function_to_list(LABEL_FUNCTIONS)
606 def has_lightsensor(self):
607 """Determine the correct board label for this host.
608
609 @returns the string 'lightsensor' if this host has a lightsensor or
610 None if it does not.
611 """
612 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
613 self.LIGHTSENSOR_SEARCH_DIR, '|'.join(self.LIGHTSENSOR_FILES))
614 try:
615 # Run the search cmd following the symlinks. Stderr_tee is set to
616 # None as there can be a symlink loop, but the command will still
617 # execute correctly with a few messages printed to stderr.
618 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
619 return 'lightsensor'
620 except error.AutoservRunError:
621 # egrep exited with a return code of 1 meaning none of the possible
622 # lightsensor files existed.
623 return None
624
625
626 @add_function_to_list(LABEL_FUNCTIONS)
627 def has_bluetooth(self):
628 """Determine the correct board label for this host.
629
630 @returns the string 'bluetooth' if this host has bluetooth or
631 None if it does not.
632 """
633 try:
634 self.run('test -d /sys/class/bluetooth/hci0')
635 # test exited with a return code of 0.
636 return 'bluetooth'
637 except error.AutoservRunError:
638 # test exited with a return code 1 meaning the directory did not
639 # exist.
640 return None
641
642
643 def get_labels(self):
644 """Return a list of labels for this given host.
645
646 This is the main way to retrieve all the automatic labels for a host
647 as it will run through all the currently implemented label functions.
648 """
649 labels = []
650 for label_function in self.LABEL_FUNCTIONS:
651 label = label_function(self)
652 if label:
653 labels.append(label)
654 return labels