blob: c4eeeb1e1431aa445fcbd7488aaba2a1d2b3f4d9 [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
J. Richard Barnette964fba02012-10-24 17:34:29 -0700115 @staticmethod
116 def get_servo_arguments(arglist):
117 servo_args = {}
118 for arg in ('servo_host', 'servo_port'):
119 if arg in arglist:
120 servo_args[arg] = arglist[arg]
121 return servo_args
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700122
J. Richard Barnette964fba02012-10-24 17:34:29 -0700123
124 def _initialize(self, hostname, servo_args=None, *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700125 """Initialize superclasses, and |self.servo|.
126
127 For creating the host servo object, there are three
128 possibilities: First, if the host is a lab system known to
129 have a servo board, we connect to that servo unconditionally.
130 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700131 servo features for testing, it will pass settings for
132 `servo_host`, `servo_port`, or both. If neither of these
133 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700134
135 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700136 super(SiteHost, self)._initialize(hostname=hostname,
137 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700138 # self.env is a dictionary of environment variable settings
139 # to be exported for commands run on the host.
140 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
141 # errors that might happen.
142 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700143 self._xmlrpc_proxy_map = {}
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700144 self.servo = servo.Servo.get_lab_servo(hostname)
J. Richard Barnette964fba02012-10-24 17:34:29 -0700145 if not self.servo and servo_args:
146 self.servo = servo.Servo(**servo_args)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700147
148
Chris Sosaa3ac2152012-05-23 22:23:13 -0700149 def machine_install(self, update_url=None, force_update=False,
150 local_devserver=False):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700151 if not update_url and self._parser.options.image:
152 update_url = self._parser.options.image
153 elif not update_url:
154 raise autoupdater.ChromiumOSError(
155 'Update failed. No update URL provided.')
156
157 # Attempt to update the system.
Chris Sosaa3ac2152012-05-23 22:23:13 -0700158 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
159 local_devserver=local_devserver)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700160 if updater.run_update(force_update):
161 # Figure out active and inactive kernel.
162 active_kernel, inactive_kernel = updater.get_kernel_state()
163
164 # Ensure inactive kernel has higher priority than active.
165 if (updater.get_kernel_priority(inactive_kernel)
166 < updater.get_kernel_priority(active_kernel)):
167 raise autoupdater.ChromiumOSError(
168 'Update failed. The priority of the inactive kernel'
169 ' partition is less than that of the active kernel'
170 ' partition.')
171
Scott Zawalski21902002012-09-19 17:57:00 -0400172 update_engine_log = '/var/log/update_engine.log'
173 logging.info('Dumping %s', update_engine_log)
174 self.run('cat %s' % update_engine_log)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700175 # Updater has returned, successfully, reboot the host.
176 self.reboot(timeout=60, wait=True)
Chris Sosae146ed82012-09-19 17:58:36 -0700177 # Touch the lab machine file to leave a marker that distinguishes
178 # this image from other test images.
179 self.run('touch %s' % self.LAB_MACHINE_FILE)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700180
181 # Following the reboot, verify the correct version.
182 updater.check_version()
183
184 # Figure out newly active kernel.
185 new_active_kernel, _ = updater.get_kernel_state()
186
187 # Ensure that previously inactive kernel is now the active kernel.
188 if new_active_kernel != inactive_kernel:
189 raise autoupdater.ChromiumOSError(
190 'Update failed. New kernel partition is not active after'
191 ' boot.')
192
193 host_attributes = site_host_attributes.HostAttributes(self.hostname)
194 if host_attributes.has_chromeos_firmware:
195 # Wait until tries == 0 and success, or until timeout.
196 utils.poll_for_condition(
197 lambda: (updater.get_kernel_tries(new_active_kernel) == 0
198 and updater.get_kernel_success(new_active_kernel)),
199 exception=autoupdater.ChromiumOSError(
200 'Update failed. Timed out waiting for system to mark'
201 ' new kernel as successful.'),
202 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
203
204 # TODO(dalecurtis): Hack for R12 builds to make sure BVT runs of
205 # platform_Shutdown pass correctly.
206 if updater.update_version.startswith('0.12'):
207 self.reboot(timeout=60, wait=True)
208
209 # Mark host as recently updated. Hosts are rebooted at the end of
210 # every test cycle which will remove the file.
211 self.run('touch %s' % self._JUST_UPDATED_FLAG)
212
213 # Clean up any old autotest directories which may be lying around.
214 for path in global_config.global_config.get_config_value(
215 'AUTOSERV', 'client_autodir_paths', type=list):
216 self.run('rm -rf ' + path)
217
218
219 def has_just_updated(self):
220 """Indicates whether the host was updated within this boot."""
221 # Check for the existence of the just updated flag file.
222 return self.run(
223 '[ -f %s ] && echo T || echo F'
224 % self._JUST_UPDATED_FLAG).stdout.strip() == 'T'
225
226
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700227 def close(self):
228 super(SiteHost, self).close()
229 self.xmlrpc_disconnect_all()
230
231
Simran Basi154f5582012-10-23 16:27:11 -0700232 # TODO (sbasi) crosbug.com/35656
233 # Renamed the sitehost cleanup method so we don't go down this pathway.
234 # def cleanup(self):
235 def cleanup_poweron(self):
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700236 """Special cleanup method to make sure hosts always get power back."""
Chris Sosa9479fcd2012-10-09 13:44:22 -0700237 super(SiteHost, self).cleanup()
Simran Basid5e5e272012-09-24 15:23:59 -0700238 if self.has_power():
Simran Basifd23fb22012-10-22 17:56:22 -0700239 try:
240 self.power_on()
241 except RemotePowerException:
242 # If cleanup has completed but there was an issue with the RPM
243 # Infrastructure, log an error message rather than fail cleanup
244 logging.error('Failed to turn Power On for this host after '
245 'cleanup through the RPM Infrastructure.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700246
247
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700248 def reboot(self, **dargs):
249 """
250 This function reboots the site host. The more generic
251 RemoteHost.reboot() performs sync and sleeps for 5
252 seconds. This is not necessary for Chrome OS devices as the
253 sync should be finished in a short time during the reboot
254 command.
255 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800256 if 'reboot_cmd' not in dargs:
257 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
258 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700259 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800260 if 'fastsync' not in dargs:
261 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700262 super(SiteHost, self).reboot(**dargs)
263
264
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700265 def verify_software(self):
266 """Ensure the stateful partition has space for Autotest and updates.
267
268 Similar to what is done by AbstractSSH, except instead of checking the
269 Autotest installation path, just check the stateful partition.
270
271 Checking the stateful partition is preferable in case it has been wiped,
272 resulting in an Autotest installation path which doesn't exist and isn't
273 writable. We still want to pass verify in this state since the partition
274 will be recovered with the next install.
275 """
276 super(SiteHost, self).verify_software()
277 self.check_diskspace(
278 '/mnt/stateful_partition',
279 global_config.global_config.get_config_value(
280 'SERVER', 'gb_diskspace_required', type=int,
281 default=20))
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700282
283
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700284 def xmlrpc_connect(self, command, port, cleanup=None):
285 """Connect to an XMLRPC server on the host.
286
287 The `command` argument should be a simple shell command that
288 starts an XMLRPC server on the given `port`. The command
289 must not daemonize, and must terminate cleanly on SIGTERM.
290 The command is started in the background on the host, and a
291 local XMLRPC client for the server is created and returned
292 to the caller.
293
294 Note that the process of creating an XMLRPC client makes no
295 attempt to connect to the remote server; the caller is
296 responsible for determining whether the server is running
297 correctly, and is ready to serve requests.
298
299 @param command Shell command to start the server.
300 @param port Port number on which the server is expected to
301 be serving.
302 """
303 self.xmlrpc_disconnect(port)
304
305 # Chrome OS on the target closes down most external ports
306 # for security. We could open the port, but doing that
307 # would conflict with security tests that check that only
308 # expected ports are open. So, to get to the port on the
309 # target we use an ssh tunnel.
310 local_port = utils.get_unused_port()
311 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
312 ssh_cmd = make_ssh_command(opts=tunnel_options)
313 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
314 logging.debug('Full tunnel command: %s', tunnel_cmd)
315 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
316 logging.debug('Started XMLRPC tunnel, local = %d'
317 ' remote = %d, pid = %d',
318 local_port, port, tunnel_proc.pid)
319
320 # Start the server on the host. Redirection in the command
321 # below is necessary, because 'ssh' won't terminate until
322 # background child processes close stdin, stdout, and
323 # stderr.
324 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
325 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
326 logging.debug('Started XMLRPC server on host %s, pid = %s',
327 self.hostname, remote_pid)
328
329 self._xmlrpc_proxy_map[port] = (cleanup, tunnel_proc)
330 rpc_url = 'http://localhost:%d' % local_port
331 return xmlrpclib.ServerProxy(rpc_url, allow_none=True)
332
333
334 def xmlrpc_disconnect(self, port):
335 """Disconnect from an XMLRPC server on the host.
336
337 Terminates the remote XMLRPC server previously started for
338 the given `port`. Also closes the local ssh tunnel created
339 for the connection to the host. This function does not
340 directly alter the state of a previously returned XMLRPC
341 client object; however disconnection will cause all
342 subsequent calls to methods on the object to fail.
343
344 This function does nothing if requested to disconnect a port
345 that was not previously connected via `self.xmlrpc_connect()`
346
347 @param port Port number passed to a previous call to
348 `xmlrpc_connect()`
349 """
350 if port not in self._xmlrpc_proxy_map:
351 return
352 entry = self._xmlrpc_proxy_map[port]
353 remote_name = entry[0]
354 tunnel_proc = entry[1]
355 if remote_name:
356 # We use 'pkill' to find our target process rather than
357 # a PID, because the host may have rebooted since
358 # connecting, and we don't want to kill an innocent
359 # process with the same PID.
360 #
361 # 'pkill' helpfully exits with status 1 if no target
362 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -0700363 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700364 # status.
365 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
366
367 if tunnel_proc.poll() is None:
368 tunnel_proc.terminate()
369 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
370 else:
371 logging.debug('Tunnel pid %d terminated early, status %d',
372 tunnel_proc.pid, tunnel_proc.returncode)
373 del self._xmlrpc_proxy_map[port]
374
375
376 def xmlrpc_disconnect_all(self):
377 """Disconnect all known XMLRPC proxy ports."""
378 for port in self._xmlrpc_proxy_map.keys():
379 self.xmlrpc_disconnect(port)
380
381
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700382 def _ping_is_up(self):
383 """Ping the host once, and return whether it responded."""
384 return utils.ping(self.hostname, tries=1, deadline=1) == 0
385
386
387 def _ping_wait_down(self, timeout):
388 """Wait until the host no longer responds to `ping`.
389
390 @param timeout Minimum time to allow before declaring the
391 host to be non-responsive.
392 """
393
394 # This function is a slightly faster version of wait_down().
395 #
396 # In AbstractSSHHost.wait_down(), `ssh` is used to determine
397 # whether the host is down. In some situations (mine, at
398 # least), `ssh` can take over a minute to determine that the
399 # host is down. The `ping` command answers the question
400 # faster, so we use that here instead.
401 #
402 # There is no equivalent for wait_up(), because a target that
403 # answers to `ping` won't necessarily respond to `ssh`.
404 end_time = time.time() + timeout
405 while time.time() <= end_time:
406 if not self._ping_is_up():
407 return True
408
409 # If the timeout is short relative to the run time of
410 # _ping_is_up(), we might be prone to false failures for
411 # lack of checking frequently enough. To be safe, we make
412 # one last check _after_ the deadline.
413 return not self._ping_is_up()
414
415
416 def test_wait_for_sleep(self):
417 """Wait for the client to enter low-power sleep mode.
418
419 The test for "is asleep" can't distinguish a system that is
420 powered off; to confirm that the unit was asleep, it is
421 necessary to force resume, and then call
422 `test_wait_for_resume()`.
423
424 This function is expected to be called from a test as part
425 of a sequence like the following:
426
427 ~~~~~~~~
428 boot_id = host.get_boot_id()
429 # trigger sleep on the host
430 host.test_wait_for_sleep()
431 # trigger resume on the host
432 host.test_wait_for_resume(boot_id)
433 ~~~~~~~~
434
435 @exception TestFail The host did not go to sleep within
436 the allowed time.
437 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700438 if not self._ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700439 raise error.TestFail(
440 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700441 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700442
443
444 def test_wait_for_resume(self, old_boot_id):
445 """Wait for the client to resume from low-power sleep mode.
446
447 The `old_boot_id` parameter should be the value from
448 `get_boot_id()` obtained prior to entering sleep mode. A
449 `TestFail` exception is raised if the boot id changes.
450
451 See @ref test_wait_for_sleep for more on this function's
452 usage.
453
454 @param[in] old_boot_id A boot id value obtained before the
455 target host went to sleep.
456
457 @exception TestFail The host did not respond within the
458 allowed time.
459 @exception TestFail The host responded, but the boot id test
460 indicated a reboot rather than a sleep
461 cycle.
462 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700463 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700464 raise error.TestFail(
465 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700466 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700467 else:
468 new_boot_id = self.get_boot_id()
469 if new_boot_id != old_boot_id:
470 raise error.TestFail(
471 'client rebooted, but sleep was expected'
472 ' (old boot %s, new boot %s)'
473 % (old_boot_id, new_boot_id))
474
475
476 def test_wait_for_shutdown(self):
477 """Wait for the client to shut down.
478
479 The test for "has shut down" can't distinguish a system that
480 is merely asleep; to confirm that the unit was down, it is
481 necessary to force boot, and then call test_wait_for_boot().
482
483 This function is expected to be called from a test as part
484 of a sequence like the following:
485
486 ~~~~~~~~
487 boot_id = host.get_boot_id()
488 # trigger shutdown on the host
489 host.test_wait_for_shutdown()
490 # trigger boot on the host
491 host.test_wait_for_boot(boot_id)
492 ~~~~~~~~
493
494 @exception TestFail The host did not shut down within the
495 allowed time.
496 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700497 if not self._ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700498 raise error.TestFail(
499 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700500 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700501
502
503 def test_wait_for_boot(self, old_boot_id=None):
504 """Wait for the client to boot from cold power.
505
506 The `old_boot_id` parameter should be the value from
507 `get_boot_id()` obtained prior to shutting down. A
508 `TestFail` exception is raised if the boot id does not
509 change. The boot id test is omitted if `old_boot_id` is not
510 specified.
511
512 See @ref test_wait_for_shutdown for more on this function's
513 usage.
514
515 @param[in] old_boot_id A boot id value obtained before the
516 shut down.
517
518 @exception TestFail The host did not respond within the
519 allowed time.
520 @exception TestFail The host responded, but the boot id test
521 indicated that there was no reboot.
522 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700523 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700524 raise error.TestFail(
525 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700526 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700527 elif old_boot_id:
528 if self.get_boot_id() == old_boot_id:
529 raise error.TestFail(
530 'client is back up, but did not reboot'
531 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -0700532
533
534 @staticmethod
535 def check_for_rpm_support(hostname):
536 """For a given hostname, return whether or not it is powered by an RPM.
537
538 @return None if this host does not follows the defined naming format
539 for RPM powered DUT's in the lab. If it does follow the format,
540 it returns a regular expression MatchObject instead.
541 """
542 return re.match(SiteHost.RPM_HOSTNAME_REGEX, hostname)
543
544
545 def has_power(self):
546 """For this host, return whether or not it is powered by an RPM.
547
548 @return True if this host is in the CROS lab and follows the defined
549 naming format.
550 """
551 return SiteHost.check_for_rpm_support(self.hostname)
552
553
554 def _set_power(self, new_state):
555 client = xmlrpclib.ServerProxy(RPM_FRONTEND_URI, verbose=False)
556 if not client.queue_request(self.hostname, new_state):
557 raise RemotePowerException('Failed to change outlet status for'
558 'host: %s to state: %s', self.hostname,
559 new_state)
560
561
562 def power_off(self):
563 self._set_power('OFF')
564
565
566 def power_on(self):
567 self._set_power('ON')
568
569
570 def power_cycle(self):
571 self._set_power('CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -0700572
573
574 def get_platform(self):
575 """Determine the correct platform label for this host.
576
577 @returns a string representing this host's platform.
578 """
579 crossystem = utils.Crossystem(self)
580 crossystem.init()
581 # Extract fwid value and use the leading part as the platform id.
582 # fwid generally follow the format of {platform}.{firmware version}
583 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
584 platform = crossystem.fwid().split('.')[0].lower()
585 # Newer platforms start with 'Google_' while the older ones do not.
586 return platform.replace('google_', '')
587
588
589 @add_function_to_list(LABEL_FUNCTIONS)
590 def get_board(self):
591 """Determine the correct board label for this host.
592
593 @returns a string representing this host's board.
594 """
595 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
596 run_method=self.run)
597 board = release_info['CHROMEOS_RELEASE_BOARD']
598 # Devices in the lab generally have the correct board name but our own
599 # development devices have {board_name}-signed-{key_type}. The board
600 # name may also begin with 'x86-' which we need to keep.
601 if 'x86' not in board:
602 return 'board:%s' % board.split('-')[0]
603 return 'board:%s' % '-'.join(board.split('-')[0:2])
604
605
606 @add_function_to_list(LABEL_FUNCTIONS)
607 def has_lightsensor(self):
608 """Determine the correct board label for this host.
609
610 @returns the string 'lightsensor' if this host has a lightsensor or
611 None if it does not.
612 """
613 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
614 self.LIGHTSENSOR_SEARCH_DIR, '|'.join(self.LIGHTSENSOR_FILES))
615 try:
616 # Run the search cmd following the symlinks. Stderr_tee is set to
617 # None as there can be a symlink loop, but the command will still
618 # execute correctly with a few messages printed to stderr.
619 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
620 return 'lightsensor'
621 except error.AutoservRunError:
622 # egrep exited with a return code of 1 meaning none of the possible
623 # lightsensor files existed.
624 return None
625
626
627 @add_function_to_list(LABEL_FUNCTIONS)
628 def has_bluetooth(self):
629 """Determine the correct board label for this host.
630
631 @returns the string 'bluetooth' if this host has bluetooth or
632 None if it does not.
633 """
634 try:
635 self.run('test -d /sys/class/bluetooth/hci0')
636 # test exited with a return code of 0.
637 return 'bluetooth'
638 except error.AutoservRunError:
639 # test exited with a return code 1 meaning the directory did not
640 # exist.
641 return None
642
643
644 def get_labels(self):
645 """Return a list of labels for this given host.
646
647 This is the main way to retrieve all the automatic labels for a host
648 as it will run through all the currently implemented label functions.
649 """
650 labels = []
651 for label_function in self.LABEL_FUNCTIONS:
652 label = label_function(self)
653 if label:
654 labels.append(label)
655 return labels