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