blob: 1c222e6d608a4336c281cdaf2610e9eb670a4f75 [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
6import subprocess
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07007import time
J. Richard Barnette1d78b012012-05-15 13:56:30 -07008import xmlrpclib
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07009
J. Richard Barnette45e93de2012-04-11 17:24:15 -070010from autotest_lib.client.bin import utils
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070011from autotest_lib.client.common_lib import global_config, error
J. Richard Barnette45e93de2012-04-11 17:24:15 -070012from autotest_lib.client.common_lib.cros import autoupdater
13from autotest_lib.server import autoserv_parser
14from autotest_lib.server import site_host_attributes
15from autotest_lib.server import site_remote_power
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
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070020def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
21 connect_timeout=None, alive_interval=None):
22 """Override default make_ssh_command to use options tuned for Chrome OS.
23
24 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070025 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
26 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070027
Dale Curtisaa5eedb2011-08-23 16:18:52 -070028 - ServerAliveInterval=180; which causes SSH to ping connection every
29 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
30 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
31 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070032
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070033 - ServerAliveCountMax=3; consistency with remote_access.sh.
34
35 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
36 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070037
38 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
39 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070040
41 - SSH protocol forced to 2; needed for ServerAliveInterval.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070042 """
43 base_command = ('/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no'
44 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070045 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
46 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
47 ' -o Protocol=2 -l %s -p %d')
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070048 return base_command % (opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -070049
50
51class SiteHost(remote.RemoteHost):
52 """Chromium OS specific subclass of Host."""
53
54 _parser = autoserv_parser.autoserv_parser
55
56 # Time to wait for new kernel to be marked successful.
Chris Masone163cead2012-05-16 11:49:48 -070057 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -070058
59 # Ephemeral file to indicate that an update has just occurred.
60 _JUST_UPDATED_FLAG = '/tmp/just_updated'
61
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070062 # Timeout values associated with various Chrome OS state
63 # changes. In general, the timeouts are the maximum time to
64 # allow between some event X, and the time that the unit is
65 # on (or off) the network. Note that "time to get on the
66 # network" is typically longer than the time to complete the
67 # operation.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070068 #
69 # TODO(jrbarnette): None of these times have been thoroughly
70 # tested empirically; if timeouts are a problem, increasing the
71 # time limit really might be the right answer.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -070072 #
73 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
74 # RESUME_TIMEOUT: Time to allow for resume after suspend.
75 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
76 # other things, this includes time for the 30 second dev-mode
77 # screen delay,
78 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
79 # including the 30 second dev-mode delay.
80 # SHUTDOWN_TIMEOUT: Time to allow to shut down.
81 # REBOOT_TIMEOUT: Combination of shutdown and reboot times.
82
83 SLEEP_TIMEOUT = 2
84 RESUME_TIMEOUT = 5
85 BOOT_TIMEOUT = 45
86 USB_BOOT_TIMEOUT = 150
87 SHUTDOWN_TIMEOUT = 5
88 REBOOT_TIMEOUT = SHUTDOWN_TIMEOUT + BOOT_TIMEOUT
Chris Sosae146ed82012-09-19 17:58:36 -070089 LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070090
91
J. Richard Barnette55fb8062012-05-23 10:29:31 -070092 def _initialize(self, hostname, servo_host=None, servo_port=None,
93 *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -070094 """Initialize superclasses, and |self.servo|.
95
96 For creating the host servo object, there are three
97 possibilities: First, if the host is a lab system known to
98 have a servo board, we connect to that servo unconditionally.
99 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700100 servo features for testing, it will pass settings for
101 `servo_host`, `servo_port`, or both. If neither of these
102 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700103
104 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700105 super(SiteHost, self)._initialize(hostname=hostname,
106 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700107 # self.env is a dictionary of environment variable settings
108 # to be exported for commands run on the host.
109 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
110 # errors that might happen.
111 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700112 self._xmlrpc_proxy_map = {}
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700113 self.servo = servo.Servo.get_lab_servo(hostname)
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700114 if not self.servo:
115 # The Servo constructor generally doesn't accept 'None'
116 # for its parameters.
117 if servo_host is not None:
118 if servo_port is not None:
119 self.servo = servo.Servo(servo_host=servo_host,
120 servo_port=servo_port)
121 else:
122 self.servo = servo.Servo(servo_host=servo_host)
123 elif servo_port is not None:
124 self.servo = servo.Servo(servo_port=servo_port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700125
126
Chris Sosaa3ac2152012-05-23 22:23:13 -0700127 def machine_install(self, update_url=None, force_update=False,
128 local_devserver=False):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700129 if not update_url and self._parser.options.image:
130 update_url = self._parser.options.image
131 elif not update_url:
132 raise autoupdater.ChromiumOSError(
133 'Update failed. No update URL provided.')
134
135 # Attempt to update the system.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700136 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
137 local_devserver=local_devserver)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700138 if updater.run_update(force_update):
139 # Figure out active and inactive kernel.
140 active_kernel, inactive_kernel = updater.get_kernel_state()
141
142 # Ensure inactive kernel has higher priority than active.
143 if (updater.get_kernel_priority(inactive_kernel)
144 < updater.get_kernel_priority(active_kernel)):
145 raise autoupdater.ChromiumOSError(
146 'Update failed. The priority of the inactive kernel'
147 ' partition is less than that of the active kernel'
148 ' partition.')
149
Scott Zawalski21902002012-09-19 17:57:00 -0400150 update_engine_log = '/var/log/update_engine.log'
151 logging.info('Dumping %s', update_engine_log)
152 self.run('cat %s' % update_engine_log)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700153 # Updater has returned, successfully, reboot the host.
154 self.reboot(timeout=60, wait=True)
Chris Sosae146ed82012-09-19 17:58:36 -0700155 # Touch the lab machine file to leave a marker that distinguishes
156 # this image from other test images.
157 self.run('touch %s' % self.LAB_MACHINE_FILE)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700158
159 # Following the reboot, verify the correct version.
160 updater.check_version()
161
162 # Figure out newly active kernel.
163 new_active_kernel, _ = updater.get_kernel_state()
164
165 # Ensure that previously inactive kernel is now the active kernel.
166 if new_active_kernel != inactive_kernel:
167 raise autoupdater.ChromiumOSError(
168 'Update failed. New kernel partition is not active after'
169 ' boot.')
170
171 host_attributes = site_host_attributes.HostAttributes(self.hostname)
172 if host_attributes.has_chromeos_firmware:
173 # Wait until tries == 0 and success, or until timeout.
174 utils.poll_for_condition(
175 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
176 and updater.get_kernel_success(new_active_kernel)),
177 exception=autoupdater.ChromiumOSError(
178 'Update failed. Timed out waiting for system to mark'
179 ' new kernel as successful.'),
180 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
181
182 # TODO(dalecurtis): Hack for R12 builds to make sure BVT runs of
183 # platform_Shutdown pass correctly.
184 if updater.update_version.startswith('0.12'):
185 self.reboot(timeout=60, wait=True)
186
187 # Mark host as recently updated. Hosts are rebooted at the end of
188 # every test cycle which will remove the file.
189 self.run('touch %s' % self._JUST_UPDATED_FLAG)
190
191 # Clean up any old autotest directories which may be lying around.
192 for path in global_config.global_config.get_config_value(
193 'AUTOSERV', 'client_autodir_paths', type=list):
194 self.run('rm -rf ' + path)
195
196
197 def has_just_updated(self):
198 """Indicates whether the host was updated within this boot."""
199 # Check for the existence of the just updated flag file.
200 return self.run(
201 '[ -f %s ] && echo T || echo F'
202 % self._JUST_UPDATED_FLAG).stdout.strip() == 'T'
203
204
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700205 def close(self):
206 super(SiteHost, self).close()
207 self.xmlrpc_disconnect_all()
208
209
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700210 def cleanup(self):
211 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700212 super(SiteHost, self).cleanup()
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700213 remote_power = site_remote_power.RemotePower(self.hostname)
214 if remote_power:
215 remote_power.set_power_on()
216
217
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700218 def reboot(self, **dargs):
219 """
220 This function reboots the site host. The more generic
221 RemoteHost.reboot() performs sync and sleeps for 5
222 seconds. This is not necessary for Chrome OS devices as the
223 sync should be finished in a short time during the reboot
224 command.
225 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800226 if 'reboot_cmd' not in dargs:
227 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
228 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700229 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800230 if 'fastsync' not in dargs:
231 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700232 super(SiteHost, self).reboot(**dargs)
233
234
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700235 def verify_software(self):
236 """Ensure the stateful partition has space for Autotest and updates.
237
238 Similar to what is done by AbstractSSH, except instead of checking the
239 Autotest installation path, just check the stateful partition.
240
241 Checking the stateful partition is preferable in case it has been wiped,
242 resulting in an Autotest installation path which doesn't exist and isn't
243 writable. We still want to pass verify in this state since the partition
244 will be recovered with the next install.
245 """
246 super(SiteHost, self).verify_software()
247 self.check_diskspace(
248 '/mnt/stateful_partition',
249 global_config.global_config.get_config_value(
250 'SERVER', 'gb_diskspace_required', type=int,
251 default=20))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700252
253
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700254 def xmlrpc_connect(self, command, port, cleanup=None):
255 """Connect to an XMLRPC server on the host.
256
257 The `command` argument should be a simple shell command that
258 starts an XMLRPC server on the given `port`. The command
259 must not daemonize, and must terminate cleanly on SIGTERM.
260 The command is started in the background on the host, and a
261 local XMLRPC client for the server is created and returned
262 to the caller.
263
264 Note that the process of creating an XMLRPC client makes no
265 attempt to connect to the remote server; the caller is
266 responsible for determining whether the server is running
267 correctly, and is ready to serve requests.
268
269 @param command Shell command to start the server.
270 @param port Port number on which the server is expected to
271 be serving.
272 """
273 self.xmlrpc_disconnect(port)
274
275 # Chrome OS on the target closes down most external ports
276 # for security. We could open the port, but doing that
277 # would conflict with security tests that check that only
278 # expected ports are open. So, to get to the port on the
279 # target we use an ssh tunnel.
280 local_port = utils.get_unused_port()
281 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
282 ssh_cmd = make_ssh_command(opts=tunnel_options)
283 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
284 logging.debug('Full tunnel command: %s', tunnel_cmd)
285 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
286 logging.debug('Started XMLRPC tunnel, local = %d'
287 ' remote = %d, pid = %d',
288 local_port, port, tunnel_proc.pid)
289
290 # Start the server on the host. Redirection in the command
291 # below is necessary, because 'ssh' won't terminate until
292 # background child processes close stdin, stdout, and
293 # stderr.
294 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
295 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
296 logging.debug('Started XMLRPC server on host %s, pid = %s',
297 self.hostname, remote_pid)
298
299 self._xmlrpc_proxy_map[port] = (cleanup, tunnel_proc)
300 rpc_url = 'http://localhost:%d' % local_port
301 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
302
303
304 def xmlrpc_disconnect(self, port):
305 """Disconnect from an XMLRPC server on the host.
306
307 Terminates the remote XMLRPC server previously started for
308 the given `port`. Also closes the local ssh tunnel created
309 for the connection to the host. This function does not
310 directly alter the state of a previously returned XMLRPC
311 client object; however disconnection will cause all
312 subsequent calls to methods on the object to fail.
313
314 This function does nothing if requested to disconnect a port
315 that was not previously connected via `self.xmlrpc_connect()`
316
317 @param port Port number passed to a previous call to
318 `xmlrpc_connect()`
319 """
320 if port not in self._xmlrpc_proxy_map:
321 return
322 entry = self._xmlrpc_proxy_map[port]
323 remote_name = entry[0]
324 tunnel_proc = entry[1]
325 if remote_name:
326 # We use 'pkill' to find our target process rather than
327 # a PID, because the host may have rebooted since
328 # connecting, and we don't want to kill an innocent
329 # process with the same PID.
330 #
331 # 'pkill' helpfully exits with status 1 if no target
332 # process is found, for which run() will throw an
333 # exception. We don't want that, so we ignore the
334 # status.
335 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
336
337 if tunnel_proc.poll() is None:
338 tunnel_proc.terminate()
339 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
340 else:
341 logging.debug('Tunnel pid %d terminated early, status %d',
342 tunnel_proc.pid, tunnel_proc.returncode)
343 del self._xmlrpc_proxy_map[port]
344
345
346 def xmlrpc_disconnect_all(self):
347 """Disconnect all known XMLRPC proxy ports."""
348 for port in self._xmlrpc_proxy_map.keys():
349 self.xmlrpc_disconnect(port)
350
351
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700352 def _ping_is_up(self):
353 """Ping the host once, and return whether it responded."""
354 return utils.ping(self.hostname, tries=1, deadline=1) == 0
355
356
357 def _ping_wait_down(self, timeout):
358 """Wait until the host no longer responds to `ping`.
359
360 @param timeout Minimum time to allow before declaring the
361 host to be non-responsive.
362 """
363
364 # This function is a slightly faster version of wait_down().
365 #
366 # In AbstractSSHHost.wait_down(), `ssh` is used to determine
367 # whether the host is down. In some situations (mine, at
368 # least), `ssh` can take over a minute to determine that the
369 # host is down. The `ping` command answers the question
370 # faster, so we use that here instead.
371 #
372 # There is no equivalent for wait_up(), because a target that
373 # answers to `ping` won't necessarily respond to `ssh`.
374 end_time = time.time() + timeout
375 while time.time() <= end_time:
376 if not self._ping_is_up():
377 return True
378
379 # If the timeout is short relative to the run time of
380 # _ping_is_up(), we might be prone to false failures for
381 # lack of checking frequently enough. To be safe, we make
382 # one last check _after_ the deadline.
383 return not self._ping_is_up()
384
385
386 def test_wait_for_sleep(self):
387 """Wait for the client to enter low-power sleep mode.
388
389 The test for "is asleep" can't distinguish a system that is
390 powered off; to confirm that the unit was asleep, it is
391 necessary to force resume, and then call
392 `test_wait_for_resume()`.
393
394 This function is expected to be called from a test as part
395 of a sequence like the following:
396
397 ~~~~~~~~
398 boot_id = host.get_boot_id()
399 # trigger sleep on the host
400 host.test_wait_for_sleep()
401 # trigger resume on the host
402 host.test_wait_for_resume(boot_id)
403 ~~~~~~~~
404
405 @exception TestFail The host did not go to sleep within
406 the allowed time.
407 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700408 if not self._ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700409 raise error.TestFail(
410 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700411 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700412
413
414 def test_wait_for_resume(self, old_boot_id):
415 """Wait for the client to resume from low-power sleep mode.
416
417 The `old_boot_id` parameter should be the value from
418 `get_boot_id()` obtained prior to entering sleep mode. A
419 `TestFail` exception is raised if the boot id changes.
420
421 See @ref test_wait_for_sleep for more on this function's
422 usage.
423
424 @param[in] old_boot_id A boot id value obtained before the
425 target host went to sleep.
426
427 @exception TestFail The host did not respond within the
428 allowed time.
429 @exception TestFail The host responded, but the boot id test
430 indicated a reboot rather than a sleep
431 cycle.
432 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700433 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700434 raise error.TestFail(
435 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700436 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700437 else:
438 new_boot_id = self.get_boot_id()
439 if new_boot_id != old_boot_id:
440 raise error.TestFail(
441 'client rebooted, but sleep was expected'
442 ' (old boot %s, new boot %s)'
443 % (old_boot_id, new_boot_id))
444
445
446 def test_wait_for_shutdown(self):
447 """Wait for the client to shut down.
448
449 The test for "has shut down" can't distinguish a system that
450 is merely asleep; to confirm that the unit was down, it is
451 necessary to force boot, and then call test_wait_for_boot().
452
453 This function is expected to be called from a test as part
454 of a sequence like the following:
455
456 ~~~~~~~~
457 boot_id = host.get_boot_id()
458 # trigger shutdown on the host
459 host.test_wait_for_shutdown()
460 # trigger boot on the host
461 host.test_wait_for_boot(boot_id)
462 ~~~~~~~~
463
464 @exception TestFail The host did not shut down within the
465 allowed time.
466 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700467 if not self._ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700468 raise error.TestFail(
469 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700470 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700471
472
473 def test_wait_for_boot(self, old_boot_id=None):
474 """Wait for the client to boot from cold power.
475
476 The `old_boot_id` parameter should be the value from
477 `get_boot_id()` obtained prior to shutting down. A
478 `TestFail` exception is raised if the boot id does not
479 change. The boot id test is omitted if `old_boot_id` is not
480 specified.
481
482 See @ref test_wait_for_shutdown for more on this function's
483 usage.
484
485 @param[in] old_boot_id A boot id value obtained before the
486 shut down.
487
488 @exception TestFail The host did not respond within the
489 allowed time.
490 @exception TestFail The host responded, but the boot id test
491 indicated that there was no reboot.
492 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700493 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700494 raise error.TestFail(
495 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700496 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700497 elif old_boot_id:
498 if self.get_boot_id() == old_boot_id:
499 raise error.TestFail(
500 'client is back up, but did not reboot'
501 ' (boot %s)' % old_boot_id)