blob: 1e0cdaf84ea67808941e40941b81b865fb9a38e0 [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
60class SiteHost(remote.RemoteHost):
61 """Chromium OS specific subclass of Host."""
62
63 _parser = autoserv_parser.autoserv_parser
64
65 # Time to wait for new kernel to be marked successful.
Chris Masone163cead2012-05-16 11:49:48 -070066 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -070067
68 # Ephemeral file to indicate that an update has just occurred.
69 _JUST_UPDATED_FLAG = '/tmp/just_updated'
70
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070071 # Timeout values associated with various Chrome OS state
72 # changes. In general, the timeouts are the maximum time to
73 # allow between some event X, and the time that the unit is
74 # on (or off) the network. Note that "time to get on the
75 # network" is typically longer than the time to complete the
76 # operation.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070077 #
78 # TODO(jrbarnette): None of these times have been thoroughly
79 # tested empirically; if timeouts are a problem, increasing the
80 # time limit really might be the right answer.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070081 #
82 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
83 # RESUME_TIMEOUT: Time to allow for resume after suspend.
84 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
85 # other things, this includes time for the 30 second dev-mode
86 # screen delay,
87 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
88 # including the 30 second dev-mode delay.
89 # SHUTDOWN_TIMEOUT: Time to allow to shut down.
90 # 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
Chris Sosae146ed82012-09-19 17:58:36 -070098 LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
Simran Basid5e5e272012-09-24 15:23:59 -070099 RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
100 'host[0-9]+')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700101
102
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700103 def _initialize(self, hostname, servo_host=None, servo_port=None,
104 *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700105 """Initialize superclasses, and |self.servo|.
106
107 For creating the host servo object, there are three
108 possibilities: First, if the host is a lab system known to
109 have a servo board, we connect to that servo unconditionally.
110 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700111 servo features for testing, it will pass settings for
112 `servo_host`, `servo_port`, or both. If neither of these
113 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700114
115 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700116 super(SiteHost, self)._initialize(hostname=hostname,
117 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700118 # self.env is a dictionary of environment variable settings
119 # to be exported for commands run on the host.
120 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
121 # errors that might happen.
122 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700123 self._xmlrpc_proxy_map = {}
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700124 self.servo = servo.Servo.get_lab_servo(hostname)
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700125 if not self.servo:
126 # The Servo constructor generally doesn't accept 'None'
127 # for its parameters.
128 if servo_host is not None:
129 if servo_port is not None:
130 self.servo = servo.Servo(servo_host=servo_host,
131 servo_port=servo_port)
132 else:
133 self.servo = servo.Servo(servo_host=servo_host)
134 elif servo_port is not None:
135 self.servo = servo.Servo(servo_port=servo_port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700136
137
Chris Sosaa3ac2152012-05-23 22:23:13 -0700138 def machine_install(self, update_url=None, force_update=False,
139 local_devserver=False):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700140 if not update_url and self._parser.options.image:
141 update_url = self._parser.options.image
142 elif not update_url:
143 raise autoupdater.ChromiumOSError(
144 'Update failed. No update URL provided.')
145
146 # Attempt to update the system.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700147 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
148 local_devserver=local_devserver)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700149 if updater.run_update(force_update):
150 # Figure out active and inactive kernel.
151 active_kernel, inactive_kernel = updater.get_kernel_state()
152
153 # Ensure inactive kernel has higher priority than active.
154 if (updater.get_kernel_priority(inactive_kernel)
155 < updater.get_kernel_priority(active_kernel)):
156 raise autoupdater.ChromiumOSError(
157 'Update failed. The priority of the inactive kernel'
158 ' partition is less than that of the active kernel'
159 ' partition.')
160
Scott Zawalski21902002012-09-19 17:57:00 -0400161 update_engine_log = '/var/log/update_engine.log'
162 logging.info('Dumping %s', update_engine_log)
163 self.run('cat %s' % update_engine_log)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700164 # Updater has returned, successfully, reboot the host.
165 self.reboot(timeout=60, wait=True)
Chris Sosae146ed82012-09-19 17:58:36 -0700166 # Touch the lab machine file to leave a marker that distinguishes
167 # this image from other test images.
168 self.run('touch %s' % self.LAB_MACHINE_FILE)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700169
170 # Following the reboot, verify the correct version.
171 updater.check_version()
172
173 # Figure out newly active kernel.
174 new_active_kernel, _ = updater.get_kernel_state()
175
176 # Ensure that previously inactive kernel is now the active kernel.
177 if new_active_kernel != inactive_kernel:
178 raise autoupdater.ChromiumOSError(
179 'Update failed. New kernel partition is not active after'
180 ' boot.')
181
182 host_attributes = site_host_attributes.HostAttributes(self.hostname)
183 if host_attributes.has_chromeos_firmware:
184 # Wait until tries == 0 and success, or until timeout.
185 utils.poll_for_condition(
186 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
187 and updater.get_kernel_success(new_active_kernel)),
188 exception=autoupdater.ChromiumOSError(
189 'Update failed. Timed out waiting for system to mark'
190 ' new kernel as successful.'),
191 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
192
193 # TODO(dalecurtis): Hack for R12 builds to make sure BVT runs of
194 # platform_Shutdown pass correctly.
195 if updater.update_version.startswith('0.12'):
196 self.reboot(timeout=60, wait=True)
197
198 # Mark host as recently updated. Hosts are rebooted at the end of
199 # every test cycle which will remove the file.
200 self.run('touch %s' % self._JUST_UPDATED_FLAG)
201
202 # Clean up any old autotest directories which may be lying around.
203 for path in global_config.global_config.get_config_value(
204 'AUTOSERV', 'client_autodir_paths', type=list):
205 self.run('rm -rf ' + path)
206
207
208 def has_just_updated(self):
209 """Indicates whether the host was updated within this boot."""
210 # Check for the existence of the just updated flag file.
211 return self.run(
212 '[ -f %s ] && echo T || echo F'
213 % self._JUST_UPDATED_FLAG).stdout.strip() == 'T'
214
215
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700216 def close(self):
217 super(SiteHost, self).close()
218 self.xmlrpc_disconnect_all()
219
220
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700221 def cleanup(self):
222 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700223 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700224 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700225 try:
226 self.power_on()
227 except RemotePowerException:
228 # If cleanup has completed but there was an issue with the RPM
229 # Infrastructure, log an error message rather than fail cleanup
230 logging.error('Failed to turn Power On for this host after '
231 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700232
233
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700234 def reboot(self, **dargs):
235 """
236 This function reboots the site host. The more generic
237 RemoteHost.reboot() performs sync and sleeps for 5
238 seconds. This is not necessary for Chrome OS devices as the
239 sync should be finished in a short time during the reboot
240 command.
241 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800242 if 'reboot_cmd' not in dargs:
243 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
244 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700245 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800246 if 'fastsync' not in dargs:
247 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700248 super(SiteHost, self).reboot(**dargs)
249
250
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700251 def verify_software(self):
252 """Ensure the stateful partition has space for Autotest and updates.
253
254 Similar to what is done by AbstractSSH, except instead of checking the
255 Autotest installation path, just check the stateful partition.
256
257 Checking the stateful partition is preferable in case it has been wiped,
258 resulting in an Autotest installation path which doesn't exist and isn't
259 writable. We still want to pass verify in this state since the partition
260 will be recovered with the next install.
261 """
262 super(SiteHost, self).verify_software()
263 self.check_diskspace(
264 '/mnt/stateful_partition',
265 global_config.global_config.get_config_value(
266 'SERVER', 'gb_diskspace_required', type=int,
267 default=20))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700268
269
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700270 def xmlrpc_connect(self, command, port, cleanup=None):
271 """Connect to an XMLRPC server on the host.
272
273 The `command` argument should be a simple shell command that
274 starts an XMLRPC server on the given `port`. The command
275 must not daemonize, and must terminate cleanly on SIGTERM.
276 The command is started in the background on the host, and a
277 local XMLRPC client for the server is created and returned
278 to the caller.
279
280 Note that the process of creating an XMLRPC client makes no
281 attempt to connect to the remote server; the caller is
282 responsible for determining whether the server is running
283 correctly, and is ready to serve requests.
284
285 @param command Shell command to start the server.
286 @param port Port number on which the server is expected to
287 be serving.
288 """
289 self.xmlrpc_disconnect(port)
290
291 # Chrome OS on the target closes down most external ports
292 # for security. We could open the port, but doing that
293 # would conflict with security tests that check that only
294 # expected ports are open. So, to get to the port on the
295 # target we use an ssh tunnel.
296 local_port = utils.get_unused_port()
297 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
298 ssh_cmd = make_ssh_command(opts=tunnel_options)
299 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
300 logging.debug('Full tunnel command: %s', tunnel_cmd)
301 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
302 logging.debug('Started XMLRPC tunnel, local = %d'
303 ' remote = %d, pid = %d',
304 local_port, port, tunnel_proc.pid)
305
306 # Start the server on the host. Redirection in the command
307 # below is necessary, because 'ssh' won't terminate until
308 # background child processes close stdin, stdout, and
309 # stderr.
310 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
311 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
312 logging.debug('Started XMLRPC server on host %s, pid = %s',
313 self.hostname, remote_pid)
314
315 self._xmlrpc_proxy_map[port] = (cleanup, tunnel_proc)
316 rpc_url = 'http://localhost:%d' % local_port
317 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
318
319
320 def xmlrpc_disconnect(self, port):
321 """Disconnect from an XMLRPC server on the host.
322
323 Terminates the remote XMLRPC server previously started for
324 the given `port`. Also closes the local ssh tunnel created
325 for the connection to the host. This function does not
326 directly alter the state of a previously returned XMLRPC
327 client object; however disconnection will cause all
328 subsequent calls to methods on the object to fail.
329
330 This function does nothing if requested to disconnect a port
331 that was not previously connected via `self.xmlrpc_connect()`
332
333 @param port Port number passed to a previous call to
334 `xmlrpc_connect()`
335 """
336 if port not in self._xmlrpc_proxy_map:
337 return
338 entry = self._xmlrpc_proxy_map[port]
339 remote_name = entry[0]
340 tunnel_proc = entry[1]
341 if remote_name:
342 # We use 'pkill' to find our target process rather than
343 # a PID, because the host may have rebooted since
344 # connecting, and we don't want to kill an innocent
345 # process with the same PID.
346 #
347 # 'pkill' helpfully exits with status 1 if no target
348 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700349 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700350 # status.
351 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
352
353 if tunnel_proc.poll() is None:
354 tunnel_proc.terminate()
355 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
356 else:
357 logging.debug('Tunnel pid %d terminated early, status %d',
358 tunnel_proc.pid, tunnel_proc.returncode)
359 del self._xmlrpc_proxy_map[port]
360
361
362 def xmlrpc_disconnect_all(self):
363 """Disconnect all known XMLRPC proxy ports."""
364 for port in self._xmlrpc_proxy_map.keys():
365 self.xmlrpc_disconnect(port)
366
367
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700368 def _ping_is_up(self):
369 """Ping the host once, and return whether it responded."""
370 return utils.ping(self.hostname, tries=1, deadline=1) == 0
371
372
373 def _ping_wait_down(self, timeout):
374 """Wait until the host no longer responds to `ping`.
375
376 @param timeout Minimum time to allow before declaring the
377 host to be non-responsive.
378 """
379
380 # This function is a slightly faster version of wait_down().
381 #
382 # In AbstractSSHHost.wait_down(), `ssh` is used to determine
383 # whether the host is down. In some situations (mine, at
384 # least), `ssh` can take over a minute to determine that the
385 # host is down. The `ping` command answers the question
386 # faster, so we use that here instead.
387 #
388 # There is no equivalent for wait_up(), because a target that
389 # answers to `ping` won't necessarily respond to `ssh`.
390 end_time = time.time() + timeout
391 while time.time() <= end_time:
392 if not self._ping_is_up():
393 return True
394
395 # If the timeout is short relative to the run time of
396 # _ping_is_up(), we might be prone to false failures for
397 # lack of checking frequently enough. To be safe, we make
398 # one last check _after_ the deadline.
399 return not self._ping_is_up()
400
401
402 def test_wait_for_sleep(self):
403 """Wait for the client to enter low-power sleep mode.
404
405 The test for "is asleep" can't distinguish a system that is
406 powered off; to confirm that the unit was asleep, it is
407 necessary to force resume, and then call
408 `test_wait_for_resume()`.
409
410 This function is expected to be called from a test as part
411 of a sequence like the following:
412
413 ~~~~~~~~
414 boot_id = host.get_boot_id()
415 # trigger sleep on the host
416 host.test_wait_for_sleep()
417 # trigger resume on the host
418 host.test_wait_for_resume(boot_id)
419 ~~~~~~~~
420
421 @exception TestFail The host did not go to sleep within
422 the allowed time.
423 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700424 if not self._ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700425 raise error.TestFail(
426 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700427 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700428
429
430 def test_wait_for_resume(self, old_boot_id):
431 """Wait for the client to resume from low-power sleep mode.
432
433 The `old_boot_id` parameter should be the value from
434 `get_boot_id()` obtained prior to entering sleep mode. A
435 `TestFail` exception is raised if the boot id changes.
436
437 See @ref test_wait_for_sleep for more on this function's
438 usage.
439
440 @param[in] old_boot_id A boot id value obtained before the
441 target host went to sleep.
442
443 @exception TestFail The host did not respond within the
444 allowed time.
445 @exception TestFail The host responded, but the boot id test
446 indicated a reboot rather than a sleep
447 cycle.
448 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700449 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700450 raise error.TestFail(
451 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700452 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700453 else:
454 new_boot_id = self.get_boot_id()
455 if new_boot_id != old_boot_id:
456 raise error.TestFail(
457 'client rebooted, but sleep was expected'
458 ' (old boot %s, new boot %s)'
459 % (old_boot_id, new_boot_id))
460
461
462 def test_wait_for_shutdown(self):
463 """Wait for the client to shut down.
464
465 The test for "has shut down" can't distinguish a system that
466 is merely asleep; to confirm that the unit was down, it is
467 necessary to force boot, and then call test_wait_for_boot().
468
469 This function is expected to be called from a test as part
470 of a sequence like the following:
471
472 ~~~~~~~~
473 boot_id = host.get_boot_id()
474 # trigger shutdown on the host
475 host.test_wait_for_shutdown()
476 # trigger boot on the host
477 host.test_wait_for_boot(boot_id)
478 ~~~~~~~~
479
480 @exception TestFail The host did not shut down within the
481 allowed time.
482 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700483 if not self._ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700484 raise error.TestFail(
485 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700486 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700487
488
489 def test_wait_for_boot(self, old_boot_id=None):
490 """Wait for the client to boot from cold power.
491
492 The `old_boot_id` parameter should be the value from
493 `get_boot_id()` obtained prior to shutting down. A
494 `TestFail` exception is raised if the boot id does not
495 change. The boot id test is omitted if `old_boot_id` is not
496 specified.
497
498 See @ref test_wait_for_shutdown for more on this function's
499 usage.
500
501 @param[in] old_boot_id A boot id value obtained before the
502 shut down.
503
504 @exception TestFail The host did not respond within the
505 allowed time.
506 @exception TestFail The host responded, but the boot id test
507 indicated that there was no reboot.
508 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700509 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700510 raise error.TestFail(
511 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700512 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700513 elif old_boot_id:
514 if self.get_boot_id() == old_boot_id:
515 raise error.TestFail(
516 'client is back up, but did not reboot'
517 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700518
519
520 @staticmethod
521 def check_for_rpm_support(hostname):
522 """For a given hostname, return whether or not it is powered by an RPM.
523
524 @return None if this host does not follows the defined naming format
525 for RPM powered DUT's in the lab. If it does follow the format,
526 it returns a regular expression MatchObject instead.
527 """
528 return re.match(SiteHost.RPM_HOSTNAME_REGEX, hostname)
529
530
531 def has_power(self):
532 """For this host, return whether or not it is powered by an RPM.
533
534 @return True if this host is in the CROS lab and follows the defined
535 naming format.
536 """
537 return SiteHost.check_for_rpm_support(self.hostname)
538
539
540 def _set_power(self, new_state):
541 client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
542 if not client.queue_request(self.hostname, new_state):
543 raise RemotePowerException('Failed to change outlet status for'
544 'host: %s to state: %s', self.hostname,
545 new_state)
546
547
548 def power_off(self):
549 self._set_power('OFF')
550
551
552 def power_on(self):
553 self._set_power('ON')
554
555
556 def power_cycle(self):
557 self._set_power('CYCLE')