blob: 8cccf6d8069286309702ec408987a4331ea79d72 [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
Aviv Keshet74c89a92013-02-04 15:18:30 -08005import functools
Christopher Wiley0ed712b2013-04-09 15:25:12 -07006import httplib
J. Richard Barnette1d78b012012-05-15 13:56:30 -07007import logging
Dan Shi0f466e82013-02-22 15:44:58 -08008import os
Simran Basid5e5e272012-09-24 15:23:59 -07009import re
Christopher Wileyd78249a2013-03-01 13:05:31 -080010import socket
J. Richard Barnette1d78b012012-05-15 13:56:30 -070011import subprocess
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070012import time
J. Richard Barnette1d78b012012-05-15 13:56:30 -070013import xmlrpclib
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070014
J. Richard Barnette45e93de2012-04-11 17:24:15 -070015from autotest_lib.client.bin import utils
Richard Barnette0c73ffc2012-11-19 15:21:18 -080016from autotest_lib.client.common_lib import error
17from autotest_lib.client.common_lib import global_config
beeps687243d2013-07-18 15:29:27 -070018from autotest_lib.client.common_lib import site_utils
J. Richard Barnette45e93de2012-04-11 17:24:15 -070019from autotest_lib.client.common_lib.cros import autoupdater
Richard Barnette03a0c132012-11-05 12:40:35 -080020from autotest_lib.client.common_lib.cros import dev_server
Christopher Wileyd78249a2013-03-01 13:05:31 -080021from autotest_lib.client.common_lib.cros import retry
Richard Barnette82c35912012-11-20 10:09:10 -080022from autotest_lib.client.cros import constants
J. Richard Barnette45e93de2012-04-11 17:24:15 -070023from autotest_lib.server import autoserv_parser
Chris Sosaf4d43ff2012-10-30 11:21:05 -070024from autotest_lib.server import autotest
Dan Shia1ecd5c2013-06-06 11:21:31 -070025from autotest_lib.server import utils as server_utils
Scott Zawalski89c44dd2013-02-26 09:28:02 -050026from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
Simran Basi5e6339a2013-03-21 11:34:32 -070027from autotest_lib.server.cros.dynamic_suite import tools, frontend_wrappers
J. Richard Barnette75487572013-03-08 12:47:50 -080028from autotest_lib.server.cros.servo import servo
J. Richard Barnette45e93de2012-04-11 17:24:15 -070029from autotest_lib.server.hosts import remote
beeps687243d2013-07-18 15:29:27 -070030from autotest_lib.site_utils.graphite import stats
Simran Basidcff4252012-11-20 16:13:20 -080031from autotest_lib.site_utils.rpm_control_system import rpm_client
Simran Basid5e5e272012-09-24 15:23:59 -070032
33
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -080034def _make_servo_hostname(hostname):
35 host_parts = hostname.split('.')
36 host_parts[0] = host_parts[0] + '-servo'
37 return '.'.join(host_parts)
38
39
40def _get_lab_servo(target_hostname):
41 """Instantiate a Servo for |target_hostname| in the lab.
42
43 Assuming that |target_hostname| is a device in the CrOS test
44 lab, create and return a Servo object pointed at the servo
45 attached to that DUT. The servo in the test lab is assumed
46 to already have servod up and running on it.
47
48 @param target_hostname: device whose servo we want to target.
49 @return an appropriately configured Servo instance.
50 """
51 servo_host = _make_servo_hostname(target_hostname)
52 if utils.host_is_in_lab_zone(servo_host):
53 try:
J. Richard Barnetted5f807a2013-02-11 16:51:00 -080054 return servo.Servo(servo_host=servo_host)
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -080055 except: # pylint: disable=W0702
56 # TODO(jrbarnette): Long-term, if we can't get to
57 # a servo in the lab, we want to fail, so we should
58 # pass any exceptions along. Short-term, we're not
59 # ready to rely on servo, so we ignore failures.
60 pass
61 return None
62
Aviv Keshet18ee3142013-08-12 15:01:51 -070063GLOBAL_SSH_COMMAND_OPTIONS = ''
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -080064
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070065def make_ssh_command(user='root', port=22, opts='', hosts_file=None,
66 connect_timeout=None, alive_interval=None):
67 """Override default make_ssh_command to use options tuned for Chrome OS.
68
69 Tuning changes:
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070070 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH connection
71 failure. Consistency with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070072
Dale Curtisaa5eedb2011-08-23 16:18:52 -070073 - ServerAliveInterval=180; which causes SSH to ping connection every
74 180 seconds. In conjunction with ServerAliveCountMax ensures that if the
75 connection dies, Autotest will bail out quickly. Originally tried 60 secs,
76 but saw frequent job ABORTS where the test completed successfully.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070077
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070078 - ServerAliveCountMax=3; consistency with remote_access.sh.
79
80 - ConnectAttempts=4; reduce flakiness in connection errors; consistency
81 with remote_access.sh.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070082
83 - UserKnownHostsFile=/dev/null; we don't care about the keys. Host keys
84 change with every new installation, don't waste memory/space saving them.
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070085
86 - SSH protocol forced to 2; needed for ServerAliveInterval.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -080087
88 @param user User name to use for the ssh connection.
89 @param port Port on the target host to use for ssh connection.
90 @param opts Additional options to the ssh command.
91 @param hosts_file Ignored.
92 @param connect_timeout Ignored.
93 @param alive_interval Ignored.
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070094 """
Aviv Keshet18ee3142013-08-12 15:01:51 -070095 base_command = ('/usr/bin/ssh -a -x %s %s -o StrictHostKeyChecking=no'
Dale Curtiscb7bfaf2011-06-07 16:21:57 -070096 ' -o UserKnownHostsFile=/dev/null -o BatchMode=yes'
Chris Sosaf7fcd6e2011-09-27 17:30:47 -070097 ' -o ConnectTimeout=30 -o ServerAliveInterval=180'
98 ' -o ServerAliveCountMax=3 -o ConnectionAttempts=4'
99 ' -o Protocol=2 -l %s -p %d')
Aviv Keshet18ee3142013-08-12 15:01:51 -0700100 return base_command % (GLOBAL_SSH_COMMAND_OPTIONS, opts, user, port)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700101
102
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800103
Aviv Keshet74c89a92013-02-04 15:18:30 -0800104def add_label_detector(label_function_list, label_list=None, label=None):
105 """Decorator used to group functions together into the provided list.
106 @param label_function_list: List of label detecting functions to add
107 decorated function to.
108 @param label_list: List of detectable labels to add detectable labels to.
109 (Default: None)
110 @param label: Label string that is detectable by this detection function
111 (Default: None)
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800112 """
Simran Basic6f1f7a2012-10-16 10:47:46 -0700113 def add_func(func):
Aviv Keshet74c89a92013-02-04 15:18:30 -0800114 """
115 @param func: The function to be added as a detector.
116 """
117 label_function_list.append(func)
118 if label and label_list is not None:
119 label_list.append(label)
Simran Basic6f1f7a2012-10-16 10:47:46 -0700120 return func
121 return add_func
122
123
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700124class SiteHost(remote.RemoteHost):
125 """Chromium OS specific subclass of Host."""
126
127 _parser = autoserv_parser.autoserv_parser
Scott Zawalski62bacae2013-03-05 10:40:32 -0500128 _AFE = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700129
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800130 # Time to wait for new kernel to be marked successful after
131 # auto update.
Chris Masone163cead2012-05-16 11:49:48 -0700132 _KERNEL_UPDATE_TIMEOUT = 120
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700133
Richard Barnette03a0c132012-11-05 12:40:35 -0800134 # Timeout values (in seconds) associated with various Chrome OS
135 # state changes.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700136 #
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800137 # In general, a good rule of thumb is that the timeout can be up
138 # to twice the typical measured value on the slowest platform.
139 # The times here have not necessarily been empirically tested to
140 # meet this criterion.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700141 #
142 # SLEEP_TIMEOUT: Time to allow for suspend to memory.
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800143 # RESUME_TIMEOUT: Time to allow for resume after suspend, plus
144 # time to restart the netwowrk.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700145 # BOOT_TIMEOUT: Time to allow for boot from power off. Among
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800146 # other things, this must account for the 30 second dev-mode
J. Richard Barnetted4649c62013-03-06 17:42:27 -0800147 # screen delay and time to start the network.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700148 # USB_BOOT_TIMEOUT: Time to allow for boot from a USB device,
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800149 # including the 30 second dev-mode delay and time to start the
J. Richard Barnetted4649c62013-03-06 17:42:27 -0800150 # network.
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800151 # SHUTDOWN_TIMEOUT: Time to allow for shut down.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700152 # REBOOT_TIMEOUT: How long to wait for a reboot.
Richard Barnette03a0c132012-11-05 12:40:35 -0800153 # _INSTALL_TIMEOUT: Time to allow for chromeos-install.
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700154
155 SLEEP_TIMEOUT = 2
J. Richard Barnetted4649c62013-03-06 17:42:27 -0800156 RESUME_TIMEOUT = 10
J. Richard Barnettefbcc7122013-07-24 18:24:59 -0700157 BOOT_TIMEOUT = 60
J. Richard Barnetteeb69d722012-06-18 17:29:44 -0700158 USB_BOOT_TIMEOUT = 150
Chris Sosab76e0ee2013-05-22 16:55:41 -0700159
160 # We have a long timeout to ensure we don't flakily fail due to other
161 # issues. Shorter timeouts are vetted in platform_RebootAfterUpdate.
162 REBOOT_TIMEOUT = 300
163
Richard Barnette03a0c132012-11-05 12:40:35 -0800164 _INSTALL_TIMEOUT = 240
165
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800166 # _USB_POWER_TIMEOUT: Time to allow for USB to power toggle ON and OFF.
167 # _POWER_CYCLE_TIMEOUT: Time to allow for manual power cycle.
168 _USB_POWER_TIMEOUT = 5
169 _POWER_CYCLE_TIMEOUT = 10
170
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800171
Richard Barnette82c35912012-11-20 10:09:10 -0800172 _RPM_RECOVERY_BOARDS = global_config.global_config.get_config_value('CROS',
173 'rpm_recovery_boards', type=str).split(',')
174
175 _MAX_POWER_CYCLE_ATTEMPTS = 6
176 _LAB_MACHINE_FILE = '/mnt/stateful_partition/.labmachine'
177 _RPM_HOSTNAME_REGEX = ('chromeos[0-9]+(-row[0-9]+)?-rack[0-9]+[a-z]*-'
178 'host[0-9]+')
179 _LIGHTSENSOR_FILES = ['in_illuminance0_input',
180 'in_illuminance0_raw',
181 'illuminance0_input']
182 _LIGHTSENSOR_SEARCH_DIR = '/sys/bus/iio/devices'
183 _LABEL_FUNCTIONS = []
Aviv Keshet74c89a92013-02-04 15:18:30 -0800184 _DETECTABLE_LABELS = []
185 label_decorator = functools.partial(add_label_detector, _LABEL_FUNCTIONS,
186 _DETECTABLE_LABELS)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700187
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -0800188 # Constants used in ping_wait_up() and ping_wait_down().
189 #
190 # _PING_WAIT_COUNT is the approximate number of polling
191 # cycles to use when waiting for a host state change.
192 #
193 # _PING_STATUS_DOWN and _PING_STATUS_UP are names used
194 # for arguments to the internal _ping_wait_for_status()
195 # method.
196 _PING_WAIT_COUNT = 40
197 _PING_STATUS_DOWN = False
198 _PING_STATUS_UP = True
199
Ismail Noorbasha07fdb612013-02-14 14:13:31 -0800200 # Allowed values for the power_method argument.
201
202 # POWER_CONTROL_RPM: Passed as default arg for power_off/on/cycle() methods.
203 # POWER_CONTROL_SERVO: Used in set_power() and power_cycle() methods.
204 # POWER_CONTROL_MANUAL: Used in set_power() and power_cycle() methods.
205 POWER_CONTROL_RPM = 'RPM'
206 POWER_CONTROL_SERVO = 'servoj10'
207 POWER_CONTROL_MANUAL = 'manual'
208
209 POWER_CONTROL_VALID_ARGS = (POWER_CONTROL_RPM,
210 POWER_CONTROL_SERVO,
211 POWER_CONTROL_MANUAL)
Richard Barnette0c73ffc2012-11-19 15:21:18 -0800212
Simran Basi5e6339a2013-03-21 11:34:32 -0700213 _RPM_OUTLET_CHANGED = 'outlet_changed'
214
beeps687243d2013-07-18 15:29:27 -0700215
J. Richard Barnette964fba02012-10-24 17:34:29 -0700216 @staticmethod
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800217 def get_servo_arguments(args_dict):
218 """Extract servo options from `args_dict` and return the result.
219
220 Take the provided dictionary of argument options and return
221 a subset that represent standard arguments needed to
222 construct a servo object for a host. The intent is to
223 provide standard argument processing from run_remote_tests
224 for tests that require a servo to operate.
225
226 Recommended usage:
227 ~~~~~~~~
228 args_dict = utils.args_to_dict(args)
229 servo_args = hosts.SiteHost.get_servo_arguments(args_dict)
230 host = hosts.create_host(machine, servo_args=servo_args)
231 ~~~~~~~~
232
233 @param args_dict Dictionary from which to extract the servo
234 arguments.
235 """
J. Richard Barnette964fba02012-10-24 17:34:29 -0700236 servo_args = {}
237 for arg in ('servo_host', 'servo_port'):
J. Richard Barnette7214e0b2013-02-06 15:20:49 -0800238 if arg in args_dict:
239 servo_args[arg] = args_dict[arg]
J. Richard Barnette964fba02012-10-24 17:34:29 -0700240 return servo_args
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700241
J. Richard Barnette964fba02012-10-24 17:34:29 -0700242
243 def _initialize(self, hostname, servo_args=None, *args, **dargs):
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700244 """Initialize superclasses, and |self.servo|.
245
246 For creating the host servo object, there are three
247 possibilities: First, if the host is a lab system known to
248 have a servo board, we connect to that servo unconditionally.
249 Second, if we're called from a control file that requires
J. Richard Barnette55fb8062012-05-23 10:29:31 -0700250 servo features for testing, it will pass settings for
251 `servo_host`, `servo_port`, or both. If neither of these
252 cases apply, `self.servo` will be `None`.
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700253
254 """
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700255 super(SiteHost, self)._initialize(hostname=hostname,
256 *args, **dargs)
J. Richard Barnettef0859852012-08-20 14:55:50 -0700257 # self.env is a dictionary of environment variable settings
258 # to be exported for commands run on the host.
259 # LIBC_FATAL_STDERR_ can be useful for diagnosing certain
260 # errors that might happen.
261 self.env['LIBC_FATAL_STDERR_'] = '1'
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700262 self._xmlrpc_proxy_map = {}
J. Richard Barnettebe5ebcc2013-02-11 16:03:15 -0800263 self.servo = _get_lab_servo(hostname)
J. Richard Barnettead7da482012-10-30 16:46:52 -0700264 if not self.servo and servo_args is not None:
J. Richard Barnette964fba02012-10-24 17:34:29 -0700265 self.servo = servo.Servo(**servo_args)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700266
267
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500268 def get_repair_image_name(self):
269 """Generate a image_name from variables in the global config.
270
271 @returns a str of $board-version/$BUILD.
272
273 """
274 stable_version = global_config.global_config.get_config_value(
275 'CROS', 'stable_cros_version')
276 build_pattern = global_config.global_config.get_config_value(
277 'CROS', 'stable_build_pattern')
278 board = self._get_board_from_afe()
279 if board is None:
280 raise error.AutoservError('DUT has no board attribute, '
281 'cannot be repaired.')
282 return build_pattern % (board, stable_version)
283
284
Scott Zawalski62bacae2013-03-05 10:40:32 -0500285 def _host_in_AFE(self):
286 """Check if the host is an object the AFE knows.
287
288 @returns the host object.
289 """
290 return self._AFE.get_hosts(hostname=self.hostname)
291
292
Chris Sosab76e0ee2013-05-22 16:55:41 -0700293 def lookup_job_repo_url(self):
294 """Looks up the job_repo_url for the host.
295
296 @returns job_repo_url from AFE or None if not found.
297
298 @raises KeyError if the host does not have a job_repo_url
299 """
300 if not self._host_in_AFE():
301 return None
302
303 hosts = self._AFE.get_hosts(hostname=self.hostname)
beepsb5efc532013-06-04 11:29:34 -0700304 if hosts and ds_constants.JOB_REPO_URL in hosts[0].attributes:
305 return hosts[0].attributes[ds_constants.JOB_REPO_URL]
Chris Sosab76e0ee2013-05-22 16:55:41 -0700306
307
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500308 def clear_cros_version_labels_and_job_repo_url(self):
309 """Clear cros_version labels and host attribute job_repo_url."""
Scott Zawalski62bacae2013-03-05 10:40:32 -0500310 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400311 return
312
Scott Zawalski62bacae2013-03-05 10:40:32 -0500313 host_list = [self.hostname]
314 labels = self._AFE.get_labels(
315 name__startswith=ds_constants.VERSION_PREFIX,
316 host__hostname=self.hostname)
Dan Shi0f466e82013-02-22 15:44:58 -0800317
Scott Zawalski62bacae2013-03-05 10:40:32 -0500318 for label in labels:
319 label.remove_hosts(hosts=host_list)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500320
beepscb6f1e22013-06-28 19:14:10 -0700321 self.update_job_repo_url(None, None)
322
323
324 def update_job_repo_url(self, devserver_url, image_name):
325 """
326 Updates the job_repo_url host attribute and asserts it's value.
327
328 @param devserver_url: The devserver to use in the job_repo_url.
329 @param image_name: The name of the image to use in the job_repo_url.
330
331 @raises AutoservError: If we failed to update the job_repo_url.
332 """
333 repo_url = None
334 if devserver_url and image_name:
335 repo_url = tools.get_package_url(devserver_url, image_name)
336 self._AFE.set_host_attribute(ds_constants.JOB_REPO_URL, repo_url,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500337 hostname=self.hostname)
beepscb6f1e22013-06-28 19:14:10 -0700338 if self.lookup_job_repo_url() != repo_url:
339 raise error.AutoservError('Failed to update job_repo_url with %s, '
340 'host %s' % (repo_url, self.hostname))
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500341
342
Dan Shie9309262013-06-19 22:50:21 -0700343 def add_cros_version_labels_and_job_repo_url(self, image_name):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400344 """Add cros_version labels and host attribute job_repo_url.
345
346 @param image_name: The name of the image e.g.
347 lumpy-release/R27-3837.0.0
Dan Shi7458bf62013-06-10 12:50:16 -0700348
Scott Zawalskieadbf702013-03-14 09:23:06 -0400349 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500350 if not self._host_in_AFE():
Scott Zawalskieadbf702013-03-14 09:23:06 -0400351 return
Scott Zawalski62bacae2013-03-05 10:40:32 -0500352
Scott Zawalskieadbf702013-03-14 09:23:06 -0400353 cros_label = '%s%s' % (ds_constants.VERSION_PREFIX, image_name)
Dan Shie9309262013-06-19 22:50:21 -0700354 devserver_url = dev_server.ImageServer.resolve(image_name).url()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500355
356 labels = self._AFE.get_labels(name=cros_label)
357 if labels:
358 label = labels[0]
359 else:
360 label = self._AFE.create_label(name=cros_label)
361
362 label.add_hosts([self.hostname])
beepscb6f1e22013-06-28 19:14:10 -0700363 self.update_job_repo_url(devserver_url, image_name)
364
365
beepsdae65fd2013-07-26 16:24:41 -0700366 def verify_job_repo_url(self, tag=''):
beepscb6f1e22013-06-28 19:14:10 -0700367 """
368 Make sure job_repo_url of this host is valid.
369
joychen03eaad92013-06-26 09:55:21 -0700370 Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
beepscb6f1e22013-06-28 19:14:10 -0700371 lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
372 autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
373 download and extract it. If the devserver embedded in the url is
374 unresponsive, update the job_repo_url of the host after staging it on
375 another devserver.
376
377 @param job_repo_url: A url pointing to the devserver where the autotest
378 package for this build should be staged.
beepsdae65fd2013-07-26 16:24:41 -0700379 @param tag: The tag from the server job, in the format
380 <job_id>-<user>/<hostname>, or <hostless> for a server job.
beepscb6f1e22013-06-28 19:14:10 -0700381
382 @raises DevServerException: If we could not resolve a devserver.
383 @raises AutoservError: If we're unable to save the new job_repo_url as
384 a result of choosing a new devserver because the old one failed to
385 respond to a health check.
beeps0c865032013-07-30 11:37:06 -0700386 @raises urllib2.URLError: If the devserver embedded in job_repo_url
387 doesn't respond within the timeout.
beepscb6f1e22013-06-28 19:14:10 -0700388 """
389 job_repo_url = self.lookup_job_repo_url()
390 if not job_repo_url:
391 logging.warning('No job repo url set on host %s', self.hostname)
392 return
393
394 logging.info('Verifying job repo url %s', job_repo_url)
395 devserver_url, image_name = tools.get_devserver_build_from_package_url(
396 job_repo_url)
397
beeps0c865032013-07-30 11:37:06 -0700398 ds = dev_server.ImageServer(devserver_url)
beepscb6f1e22013-06-28 19:14:10 -0700399
400 logging.info('Staging autotest artifacts for %s on devserver %s',
401 image_name, ds.url())
beeps687243d2013-07-18 15:29:27 -0700402
403 start_time = time.time()
beepscb6f1e22013-06-28 19:14:10 -0700404 ds.stage_artifacts(image_name, ['autotest'])
beeps687243d2013-07-18 15:29:27 -0700405 stage_time = time.time() - start_time
406
407 # Record how much of the verification time comes from a devserver
408 # restage. If we're doing things right we should not see multiple
409 # devservers for a given board/build/branch path.
410 try:
411 board, build_type, branch = site_utils.ParseBuildName(
412 image_name)[:3]
413 except site_utils.ParseBuildNameException as e:
414 pass
415 else:
beeps0c865032013-07-30 11:37:06 -0700416 devserver = devserver_url[
417 devserver_url.find('/')+2:devserver_url.rfind(':')]
beeps687243d2013-07-18 15:29:27 -0700418 stats_key = {
419 'board': board,
420 'build_type': build_type,
421 'branch': branch,
beeps0c865032013-07-30 11:37:06 -0700422 'devserver': devserver.replace('.', '_'),
beeps687243d2013-07-18 15:29:27 -0700423 }
424 stats.Gauge('verify_job_repo_url').send(
425 '%(board)s.%(build_type)s.%(branch)s.%(devserver)s' % stats_key,
426 stage_time)
beepscb6f1e22013-06-28 19:14:10 -0700427
Scott Zawalskieadbf702013-03-14 09:23:06 -0400428
Dan Shi0f466e82013-02-22 15:44:58 -0800429 def _try_stateful_update(self, update_url, force_update, updater):
430 """Try to use stateful update to initialize DUT.
431
432 When DUT is already running the same version that machine_install
433 tries to install, stateful update is a much faster way to clean up
434 the DUT for testing, compared to a full reimage. It is implemeted
435 by calling autoupdater.run_update, but skipping updating root, as
436 updating the kernel is time consuming and not necessary.
437
438 @param update_url: url of the image.
439 @param force_update: Set to True to update the image even if the DUT
440 is running the same version.
441 @param updater: ChromiumOSUpdater instance used to update the DUT.
442 @returns: True if the DUT was updated with stateful update.
443
444 """
445 if not updater.check_version():
446 return False
447 if not force_update:
448 logging.info('Canceling stateful update because the new and '
449 'old versions are the same.')
450 return False
451 # Following folders should be rebuilt after stateful update.
452 # A test file is used to confirm each folder gets rebuilt after
453 # the stateful update.
454 folders_to_check = ['/var', '/home', '/mnt/stateful_partition']
455 test_file = '.test_file_to_be_deleted'
456 for folder in folders_to_check:
457 touch_path = os.path.join(folder, test_file)
458 self.run('touch %s' % touch_path)
459
460 if not updater.run_update(force_update=True, update_root=False):
461 return False
462
463 # Reboot to complete stateful update.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700464 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Dan Shi0f466e82013-02-22 15:44:58 -0800465 check_file_cmd = 'test -f %s; echo $?'
466 for folder in folders_to_check:
467 test_file_path = os.path.join(folder, test_file)
468 result = self.run(check_file_cmd % test_file_path,
469 ignore_status=True)
470 if result.exit_status == 1:
471 return False
472 return True
473
474
J. Richard Barnette7275b612013-06-04 18:13:11 -0700475 def _post_update_processing(self, updater, expected_kernel=None):
Dan Shi0f466e82013-02-22 15:44:58 -0800476 """After the DUT is updated, confirm machine_install succeeded.
477
478 @param updater: ChromiumOSUpdater instance used to update the DUT.
J. Richard Barnette7275b612013-06-04 18:13:11 -0700479 @param expected_kernel: kernel expected to be active after reboot,
480 or `None` to skip rollback checking.
Dan Shi0f466e82013-02-22 15:44:58 -0800481
482 """
J. Richard Barnette7275b612013-06-04 18:13:11 -0700483 # Touch the lab machine file to leave a marker that
484 # distinguishes this image from other test images.
485 # Afterwards, we must re-run the autoreboot script because
486 # it depends on the _LAB_MACHINE_FILE.
Dan Shi0f466e82013-02-22 15:44:58 -0800487 self.run('touch %s' % self._LAB_MACHINE_FILE)
Dan Shi0f466e82013-02-22 15:44:58 -0800488 self.run('start autoreboot')
489
J. Richard Barnette7275b612013-06-04 18:13:11 -0700490 # Figure out the newly active kernel.
491 active_kernel, _ = updater.get_kernel_state()
492
493 # Check for rollback due to a bad build.
494 if expected_kernel and active_kernel != expected_kernel:
495 # Print out some information to make it easier to debug
496 # the rollback.
Dan Shi0f466e82013-02-22 15:44:58 -0800497 logging.debug('Dumping partition table.')
Dan Shi346725f2013-03-20 15:22:38 -0700498 self.run('cgpt show $(rootdev -s -d)')
Dan Shi0f466e82013-02-22 15:44:58 -0800499 logging.debug('Dumping crossystem for firmware debugging.')
Dan Shi346725f2013-03-20 15:22:38 -0700500 self.run('crossystem --all')
Dan Shi0f466e82013-02-22 15:44:58 -0800501 raise autoupdater.ChromiumOSError(
J. Richard Barnette7275b612013-06-04 18:13:11 -0700502 'Build %s failed to boot on %s; system rolled back '
503 'to previous build' % (updater.update_version,
504 self.hostname))
Dan Shi0f466e82013-02-22 15:44:58 -0800505
J. Richard Barnette7275b612013-06-04 18:13:11 -0700506 # Check that we've got the build we meant to install.
507 if not updater.check_version_to_confirm_install():
508 raise autoupdater.ChromiumOSError(
509 'Failed to update %s to build %s; found build '
510 '%s instead' % (self.hostname,
511 updater.update_version,
512 updater.get_build_id()))
Scott Zawalski62bacae2013-03-05 10:40:32 -0500513
J. Richard Barnette7275b612013-06-04 18:13:11 -0700514 # Make sure chromeos-setgoodkernel runs.
515 try:
Dan Shi0f466e82013-02-22 15:44:58 -0800516 utils.poll_for_condition(
J. Richard Barnette7275b612013-06-04 18:13:11 -0700517 lambda: (updater.get_kernel_tries(active_kernel) == 0
518 and updater.get_kernel_success(active_kernel)),
519 exception=autoupdater.ChromiumOSError(),
Dan Shi0f466e82013-02-22 15:44:58 -0800520 timeout=self._KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
J. Richard Barnette7275b612013-06-04 18:13:11 -0700521 except autoupdater.ChromiumOSError as e:
522 services_status = self.run('status system-services').stdout
523 if services_status != 'system-services start/running\n':
524 event = ('Chrome failed to reach login screen')
525 else:
526 event = ('update-engine failed to call '
527 'chromeos-setgoodkernel')
528 raise autoupdater.ChromiumOSError(
529 'After update and reboot, %s '
530 'within %d seconds' % (event,
531 self._KERNEL_UPDATE_TIMEOUT))
Dan Shi0f466e82013-02-22 15:44:58 -0800532
533
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700534 def _stage_image_for_update(self, image_name=None):
Scott Zawalskieadbf702013-03-14 09:23:06 -0400535 """Stage a build on a devserver and return the update_url.
536
537 @param image_name: a name like lumpy-release/R27-3837.0.0
538 @returns an update URL like:
539 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
540 """
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700541 if not image_name:
542 image_name = self.get_repair_image_name()
543 logging.info('Staging build for AU: %s', image_name)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400544 devserver = dev_server.ImageServer.resolve(image_name)
545 devserver.trigger_download(image_name, synchronous=False)
546 return tools.image_url_pattern() % (devserver.url(), image_name)
547
548
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700549 def stage_image_for_servo(self, image_name=None):
550 """Stage a build on a devserver and return the update_url.
551
552 @param image_name: a name like lumpy-release/R27-3837.0.0
553 @returns an update URL like:
554 http://172.22.50.205:8082/update/lumpy-release/R27-3837.0.0
555 """
556 if not image_name:
557 image_name = self.get_repair_image_name()
558 logging.info('Staging build for servo install: %s', image_name)
559 devserver = dev_server.ImageServer.resolve(image_name)
560 devserver.stage_artifacts(image_name, ['test_image'])
561 return devserver.get_test_image_url(image_name)
562
563
Chris Sosaa3ac2152012-05-23 22:23:13 -0700564 def machine_install(self, update_url=None, force_update=False,
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500565 local_devserver=False, repair=False):
566 """Install the DUT.
567
Dan Shi0f466e82013-02-22 15:44:58 -0800568 Use stateful update if the DUT is already running the same build.
569 Stateful update does not update kernel and tends to run much faster
570 than a full reimage. If the DUT is running a different build, or it
571 failed to do a stateful update, full update, including kernel update,
572 will be applied to the DUT.
573
Scott Zawalskieadbf702013-03-14 09:23:06 -0400574 Once a host enters machine_install its cros_version label will be
575 removed as well as its host attribute job_repo_url (used for
576 package install).
577
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500578 @param update_url: The url to use for the update
579 pattern: http://$devserver:###/update/$build
580 If update_url is None and repair is True we will install the
581 stable image listed in global_config under
582 CROS.stable_cros_version.
583 @param force_update: Force an update even if the version installed
584 is the same. Default:False
585 @param local_devserver: Used by run_remote_test to allow people to
586 use their local devserver. Default: False
587 @param repair: Whether or not we are in repair mode. This adds special
588 cases for repairing a machine like starting update_engine.
589 Setting repair to True sets force_update to True as well.
590 default: False
591 @raises autoupdater.ChromiumOSError
592
593 """
Dan Shi7458bf62013-06-10 12:50:16 -0700594 if update_url:
595 logging.debug('update url is set to %s', update_url)
596 else:
597 logging.debug('update url is not set, resolving...')
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700598 if self._parser.options.image:
599 requested_build = self._parser.options.image
600 if requested_build.startswith('http://'):
601 update_url = requested_build
Dan Shi7458bf62013-06-10 12:50:16 -0700602 logging.debug('update url is retrieved from requested_build'
603 ': %s', update_url)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700604 else:
605 # Try to stage any build that does not start with
606 # http:// on the devservers defined in
607 # global_config.ini.
Dan Shi7458bf62013-06-10 12:50:16 -0700608 update_url = self._stage_image_for_update(requested_build)
609 logging.debug('Build staged, and update_url is set to: %s',
610 update_url)
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700611 elif repair:
612 update_url = self._stage_image_for_update()
Dan Shi7458bf62013-06-10 12:50:16 -0700613 logging.debug('Build staged, and update_url is set to: %s',
614 update_url)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400615 else:
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700616 raise autoupdater.ChromiumOSError(
617 'Update failed. No update URL provided.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500618
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500619 if repair:
Dan Shi0f466e82013-02-22 15:44:58 -0800620 # In case the system is in a bad state, we always reboot the machine
621 # before machine_install.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700622 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500623 self.run('stop update-engine; start update-engine')
624 force_update = True
Dan Shi0f466e82013-02-22 15:44:58 -0800625
Chris Sosaa3ac2152012-05-23 22:23:13 -0700626 updater = autoupdater.ChromiumOSUpdater(update_url, host=self,
Chris Sosa72312602013-04-16 15:01:56 -0700627 local_devserver=local_devserver)
Dan Shi0f466e82013-02-22 15:44:58 -0800628 updated = False
Scott Zawalskieadbf702013-03-14 09:23:06 -0400629 # Remove cros-version and job_repo_url host attribute from host.
630 self.clear_cros_version_labels_and_job_repo_url()
Dan Shi0f466e82013-02-22 15:44:58 -0800631 # If the DUT is already running the same build, try stateful update
632 # first. Stateful update does not update kernel and tends to run much
633 # faster than a full reimage.
634 try:
Chris Sosab76e0ee2013-05-22 16:55:41 -0700635 updated = self._try_stateful_update(
636 update_url, force_update, updater)
Dan Shi0f466e82013-02-22 15:44:58 -0800637 if updated:
638 logging.info('DUT is updated with stateful update.')
639 except Exception as e:
640 logging.exception(e)
641 logging.warn('Failed to stateful update DUT, force to update.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700642
Dan Shi0f466e82013-02-22 15:44:58 -0800643 inactive_kernel = None
644 # Do a full update if stateful update is not applicable or failed.
645 if not updated:
646 # In case the system is in a bad state, we always reboot the
647 # machine before machine_install.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700648 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
Chris Sosab7612bc2013-03-21 10:32:37 -0700649
650 # TODO(sosa): Remove temporary hack to get rid of bricked machines
651 # that can't update due to a corrupted policy.
652 self.run('rm -rf /var/lib/whitelist')
653 self.run('touch /var/lib/whitelist')
654 self.run('chmod -w /var/lib/whitelist')
Scott Zawalskib550d5a2013-03-22 09:23:59 -0400655 self.run('stop update-engine; start update-engine')
Chris Sosab7612bc2013-03-21 10:32:37 -0700656
Dan Shi0f466e82013-02-22 15:44:58 -0800657 if updater.run_update(force_update):
658 updated = True
659 # Figure out active and inactive kernel.
660 active_kernel, inactive_kernel = updater.get_kernel_state()
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700661
Dan Shi0f466e82013-02-22 15:44:58 -0800662 # Ensure inactive kernel has higher priority than active.
663 if (updater.get_kernel_priority(inactive_kernel)
664 < updater.get_kernel_priority(active_kernel)):
665 raise autoupdater.ChromiumOSError(
666 'Update failed. The priority of the inactive kernel'
667 ' partition is less than that of the active kernel'
668 ' partition.')
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700669
Dan Shi0f466e82013-02-22 15:44:58 -0800670 update_engine_log = '/var/log/update_engine.log'
671 logging.info('Dumping %s', update_engine_log)
672 self.run('cat %s' % update_engine_log)
673 # Updater has returned successfully; reboot the host.
Chris Sosab76e0ee2013-05-22 16:55:41 -0700674 self.reboot(timeout=self.REBOOT_TIMEOUT, wait=True)
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700675
Dan Shi0f466e82013-02-22 15:44:58 -0800676 if updated:
677 self._post_update_processing(updater, inactive_kernel)
Scott Zawalskieadbf702013-03-14 09:23:06 -0400678 image_name = autoupdater.url_to_image_name(update_url)
Dan Shie9309262013-06-19 22:50:21 -0700679 self.add_cros_version_labels_and_job_repo_url(image_name)
Simran Basi13fa1ba2013-03-04 10:56:47 -0800680
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700681 # Clean up any old autotest directories which may be lying around.
682 for path in global_config.global_config.get_config_value(
683 'AUTOSERV', 'client_autodir_paths', type=list):
684 self.run('rm -rf ' + path)
685
686
Richard Barnette82c35912012-11-20 10:09:10 -0800687 def _get_board_from_afe(self):
688 """Retrieve this host's board from its labels in the AFE.
689
690 Looks for a host label of the form "board:<board>", and
691 returns the "<board>" part of the label. `None` is returned
692 if there is not a single, unique label matching the pattern.
693
694 @returns board from label, or `None`.
695 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700696 return server_utils.get_board_from_afe(self.hostname, self._AFE)
Simran Basi833814b2013-01-29 13:13:43 -0800697
698
699 def get_build(self):
700 """Retrieve the current build for this Host from the AFE.
701
702 Looks through this host's labels in the AFE to determine its build.
703
704 @returns The current build or None if it could not find it or if there
705 were multiple build labels assigned to this host.
706 """
Dan Shia1ecd5c2013-06-06 11:21:31 -0700707 return server_utils.get_build_from_afe(self.hostname, self._AFE)
Richard Barnette82c35912012-11-20 10:09:10 -0800708
709
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500710 def _install_repair(self):
711 """Attempt to repair this host using upate-engine.
712
713 If the host is up, try installing the DUT with a stable
714 "repair" version of Chrome OS as defined in the global_config
715 under CROS.stable_cros_version.
716
Scott Zawalski62bacae2013-03-05 10:40:32 -0500717 @raises AutoservRepairMethodNA if the DUT is not reachable.
718 @raises ChromiumOSError if the install failed for some reason.
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500719
720 """
721 if not self.is_up():
Scott Zawalski62bacae2013-03-05 10:40:32 -0500722 raise error.AutoservRepairMethodNA('DUT unreachable for install.')
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500723
724 logging.info('Attempting to reimage machine to repair image.')
725 try:
726 self.machine_install(repair=True)
Fang Dengd0672f32013-03-18 17:18:09 -0700727 except autoupdater.ChromiumOSError as e:
728 logging.exception(e)
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500729 logging.info('Repair via install failed.')
Scott Zawalski62bacae2013-03-05 10:40:32 -0500730 raise
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500731
732
Scott Zawalski62bacae2013-03-05 10:40:32 -0500733 def servo_install(self, image_url=None):
734 """
735 Re-install the OS on the DUT by:
736 1) installing a test image on a USB storage device attached to the Servo
737 board,
Richard Barnette03a0c132012-11-05 12:40:35 -0800738 2) booting that image in recovery mode, and then
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700739 3) installing the image with chromeos-install.
740
Scott Zawalski62bacae2013-03-05 10:40:32 -0500741 @param image_url: If specified use as the url to install on the DUT.
742 otherwise boot the currently staged image on the USB stick.
Richard Barnette03a0c132012-11-05 12:40:35 -0800743
Scott Zawalski62bacae2013-03-05 10:40:32 -0500744 @raises AutoservError if the image fails to boot.
Richard Barnette03a0c132012-11-05 12:40:35 -0800745 """
J. Richard Barnette31b2e312013-04-04 16:05:22 -0700746 self.servo.install_recovery_image(image_url)
Richard Barnette03a0c132012-11-05 12:40:35 -0800747 if not self.wait_up(timeout=self.USB_BOOT_TIMEOUT):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500748 raise error.AutoservRepairFailure(
749 'DUT failed to boot from USB after %d seconds' %
750 self.USB_BOOT_TIMEOUT)
751
752 self.run('chromeos-install --yes', timeout=self._INSTALL_TIMEOUT)
Richard Barnette03a0c132012-11-05 12:40:35 -0800753 self.servo.power_long_press()
Fang Dengafb88142013-05-30 17:44:31 -0700754 self.servo.switch_usbkey('off')
J. Richard Barnettefbcc7122013-07-24 18:24:59 -0700755 # We *must* use power_on() here; on Parrot it's how we get
756 # out of recovery mode.
757 self.servo.get_power_state_controller().power_on()
Richard Barnette03a0c132012-11-05 12:40:35 -0800758 if not self.wait_up(timeout=self.BOOT_TIMEOUT):
759 raise error.AutoservError('DUT failed to reboot installed '
760 'test image after %d seconds' %
Scott Zawalski62bacae2013-03-05 10:40:32 -0500761 self.BOOT_TIMEOUT)
762
763
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700764 def _servo_repair_reinstall(self):
Scott Zawalski62bacae2013-03-05 10:40:32 -0500765 """Reinstall the DUT utilizing servo and a test image.
766
767 Re-install the OS on the DUT by:
768 1) installing a test image on a USB storage device attached to the Servo
769 board,
770 2) booting that image in recovery mode, and then
771 3) installing the image with chromeos-install.
772
Scott Zawalski62bacae2013-03-05 10:40:32 -0500773 @raises AutoservRepairMethodNA if the device does not have servo
774 support.
775
776 """
777 if not self.servo:
778 raise error.AutoservRepairMethodNA('Repair Reinstall NA: '
779 'DUT has no servo support.')
780
781 logging.info('Attempting to recovery servo enabled device with '
782 'servo_repair_reinstall')
783
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700784 image_url = self.stage_image_for_servo()
Scott Zawalski62bacae2013-03-05 10:40:32 -0500785 self.servo_install(image_url)
786
787
788 def _servo_repair_power(self):
789 """Attempt to repair DUT using an attached Servo.
790
791 Attempt to power on the DUT via power_long_press.
792
793 @raises AutoservRepairMethodNA if the device does not have servo
794 support.
795 @raises AutoservRepairFailure if the repair fails for any reason.
796 """
797 if not self.servo:
798 raise error.AutoservRepairMethodNA('Repair Power NA: '
799 'DUT has no servo support.')
800
801 logging.info('Attempting to recover servo enabled device by '
802 'powering it off and on.')
803 self.servo.get_power_state_controller().power_off()
804 self.servo.get_power_state_controller().power_on()
805 if self.wait_up(self.BOOT_TIMEOUT):
806 return
807
808 raise error.AutoservRepairFailure('DUT did not boot after long_press.')
Richard Barnette03a0c132012-11-05 12:40:35 -0800809
810
Richard Barnette82c35912012-11-20 10:09:10 -0800811 def _powercycle_to_repair(self):
812 """Utilize the RPM Infrastructure to bring the host back up.
813
814 If the host is not up/repaired after the first powercycle we utilize
815 auto fallback to the last good install by powercycling and rebooting the
816 host 6 times.
Scott Zawalski62bacae2013-03-05 10:40:32 -0500817
818 @raises AutoservRepairMethodNA if the device does not support remote
819 power.
820 @raises AutoservRepairFailure if the repair fails for any reason.
821
Richard Barnette82c35912012-11-20 10:09:10 -0800822 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500823 if not self.has_power():
824 raise error.AutoservRepairMethodNA('Device does not support power.')
825
Richard Barnette82c35912012-11-20 10:09:10 -0800826 logging.info('Attempting repair via RPM powercycle.')
827 failed_cycles = 0
828 self.power_cycle()
829 while not self.wait_up(timeout=self.BOOT_TIMEOUT):
830 failed_cycles += 1
831 if failed_cycles >= self._MAX_POWER_CYCLE_ATTEMPTS:
Scott Zawalski62bacae2013-03-05 10:40:32 -0500832 raise error.AutoservRepairFailure(
833 'Powercycled host %s %d times; device did not come back'
834 ' online.' % (self.hostname, failed_cycles))
Richard Barnette82c35912012-11-20 10:09:10 -0800835 self.power_cycle()
836 if failed_cycles == 0:
837 logging.info('Powercycling was successful first time.')
838 else:
839 logging.info('Powercycling was successful after %d failures.',
840 failed_cycles)
841
842
843 def repair_full(self):
844 """Repair a host for repair level NO_PROTECTION.
845
846 This overrides the base class function for repair; it does
847 not call back to the parent class, but instead offers a
848 simplified implementation based on the capabilities in the
849 Chrome OS test lab.
850
J. Richard Barnettefde55fc2013-03-15 17:47:01 -0700851 If `self.verify()` fails, the following procedures are
852 attempted:
853 1. Try to re-install to a known stable image using
854 auto-update.
Scott Zawalski62bacae2013-03-05 10:40:32 -0500855 2. If there's a servo for the DUT, try to power the DUT off and
856 on.
857 3. If there's a servo for the DUT, try to re-install via
J. Richard Barnettefde55fc2013-03-15 17:47:01 -0700858 the servo.
Scott Zawalski62bacae2013-03-05 10:40:32 -0500859 4. If the DUT can be power-cycled via RPM, try to repair
Richard Barnette82c35912012-11-20 10:09:10 -0800860 by power-cycling.
861
862 As with the parent method, the last operation performed on
863 the DUT must be to call `self.verify()`; if that call fails,
864 the exception it raises is passed back to the caller.
J. Richard Barnettefde55fc2013-03-15 17:47:01 -0700865
Scott Zawalski62bacae2013-03-05 10:40:32 -0500866 @raises AutoservRepairTotalFailure if the repair process fails to
867 fix the DUT.
Richard Barnette82c35912012-11-20 10:09:10 -0800868 """
Scott Zawalski62bacae2013-03-05 10:40:32 -0500869 # TODO(scottz): This should use something similar to label_decorator,
870 # but needs to be populated in order so DUTs are repaired with the
871 # least amount of effort.
872 repair_funcs = [self._install_repair, self._servo_repair_power,
J. Richard Barnettee4af8b92013-05-01 13:16:12 -0700873 self._servo_repair_reinstall,
Scott Zawalski62bacae2013-03-05 10:40:32 -0500874 self._powercycle_to_repair]
875 errors = []
876 for repair_func in repair_funcs:
877 try:
878 repair_func()
879 self.verify()
880 return
881 except Exception as e:
882 logging.warn('Failed to repair device: %s', e)
883 errors.append(str(e))
Scott Zawalski89c44dd2013-02-26 09:28:02 -0500884
Scott Zawalski62bacae2013-03-05 10:40:32 -0500885 raise error.AutoservRepairTotalFailure(
886 'All attempts at repairing the device failed:\n%s' %
887 '\n'.join(errors))
Richard Barnette82c35912012-11-20 10:09:10 -0800888
889
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700890 def close(self):
891 super(SiteHost, self).close()
892 self.xmlrpc_disconnect_all()
893
894
Simran Basi5e6339a2013-03-21 11:34:32 -0700895 def _cleanup_poweron(self):
896 """Special cleanup method to make sure hosts always get power back."""
897 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
898 hosts = afe.get_hosts(hostname=self.hostname)
899 if not hosts or not (self._RPM_OUTLET_CHANGED in
900 hosts[0].attributes):
901 return
902 logging.debug('This host has recently interacted with the RPM'
903 ' Infrastructure. Ensuring power is on.')
904 try:
905 self.power_on()
906 except rpm_client.RemotePowerException:
907 # If cleanup has completed but there was an issue with the RPM
908 # Infrastructure, log an error message rather than fail cleanup
909 logging.error('Failed to turn Power On for this host after '
910 'cleanup through the RPM Infrastructure.')
911 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, None,
912 hostname=self.hostname)
913
914
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700915 def cleanup(self):
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700916 client_at = autotest.Autotest(self)
Richard Barnette82c35912012-11-20 10:09:10 -0800917 self.run('rm -f %s' % constants.CLEANUP_LOGS_PAUSED_FILE)
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500918 try:
919 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
920 '_clear_login_prompt_state')
921 self.run('restart ui')
922 client_at.run_static_method('autotest_lib.client.cros.cros_ui',
923 '_wait_for_login_prompt')
Alex Millerf4517962013-02-25 15:03:02 -0800924 except (error.AutotestRunError, error.AutoservRunError):
Scott Zawalskiddbc31e2012-11-15 11:29:01 -0500925 logging.warn('Unable to restart ui, rebooting device.')
926 # Since restarting the UI fails fall back to normal Autotest
927 # cleanup routines, i.e. reboot the machine.
928 super(SiteHost, self).cleanup()
Simran Basi5e6339a2013-03-21 11:34:32 -0700929 # Check if the rpm outlet was manipulated.
Simran Basid5e5e272012-09-24 15:23:59 -0700930 if self.has_power():
Simran Basi5e6339a2013-03-21 11:34:32 -0700931 self._cleanup_poweron()
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700932
933
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700934 def reboot(self, **dargs):
935 """
936 This function reboots the site host. The more generic
937 RemoteHost.reboot() performs sync and sleeps for 5
938 seconds. This is not necessary for Chrome OS devices as the
939 sync should be finished in a short time during the reboot
940 command.
941 """
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800942 if 'reboot_cmd' not in dargs:
943 dargs['reboot_cmd'] = ('((reboot & sleep 10; reboot -f &)'
944 ' </dev/null >/dev/null 2>&1 &)')
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700945 # Enable fastsync to avoid running extra sync commands before reboot.
Tom Wai-Hong Tamf5cd1d42012-08-13 12:04:08 +0800946 if 'fastsync' not in dargs:
947 dargs['fastsync'] = True
Yu-Ju Honga2be94a2012-07-31 09:48:52 -0700948 super(SiteHost, self).reboot(**dargs)
949
950
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700951 def verify_software(self):
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800952 """Verify working software on a Chrome OS system.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700953
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800954 Tests for the following conditions:
955 1. All conditions tested by the parent version of this
956 function.
957 2. Sufficient space in /mnt/stateful_partition.
Fang Deng6b05f5b2013-03-20 13:42:11 -0700958 3. Sufficient space in /mnt/stateful_partition/encrypted.
959 4. update_engine answers a simple status request over DBus.
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700960
J. Richard Barnette45e93de2012-04-11 17:24:15 -0700961 """
962 super(SiteHost, self).verify_software()
963 self.check_diskspace(
964 '/mnt/stateful_partition',
965 global_config.global_config.get_config_value(
Fang Deng6b05f5b2013-03-20 13:42:11 -0700966 'SERVER', 'gb_diskspace_required', type=float,
967 default=20.0))
968 self.check_diskspace(
969 '/mnt/stateful_partition/encrypted',
970 global_config.global_config.get_config_value(
971 'SERVER', 'gb_encrypted_diskspace_required', type=float,
972 default=0.1))
Richard Barnetteb2bc13c2013-01-08 17:32:51 -0800973 self.run('update_engine_client --status')
Scott Zawalskifbca4a92013-03-04 15:56:42 -0500974 # Makes sure python is present, loads and can use built in functions.
975 # We have seen cases where importing cPickle fails with undefined
976 # symbols in cPickle.so.
977 self.run('python -c "import cPickle"')
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700978
979
Christopher Wileyd78249a2013-03-01 13:05:31 -0800980 def xmlrpc_connect(self, command, port, command_name=None,
981 ready_test_name=None, timeout_seconds=10):
J. Richard Barnette1d78b012012-05-15 13:56:30 -0700982 """Connect to an XMLRPC server on the host.
983
984 The `command` argument should be a simple shell command that
985 starts an XMLRPC server on the given `port`. The command
986 must not daemonize, and must terminate cleanly on SIGTERM.
987 The command is started in the background on the host, and a
988 local XMLRPC client for the server is created and returned
989 to the caller.
990
991 Note that the process of creating an XMLRPC client makes no
992 attempt to connect to the remote server; the caller is
993 responsible for determining whether the server is running
994 correctly, and is ready to serve requests.
995
Christopher Wileyd78249a2013-03-01 13:05:31 -0800996 Optionally, the caller can pass ready_test_name, a string
997 containing the name of a method to call on the proxy. This
998 method should take no parameters and return successfully only
999 when the server is ready to process client requests. When
1000 ready_test_name is set, xmlrpc_connect will block until the
1001 proxy is ready, and throw a TestError if the server isn't
1002 ready by timeout_seconds.
1003
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001004 @param command Shell command to start the server.
1005 @param port Port number on which the server is expected to
1006 be serving.
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001007 @param command_name String to use as input to `pkill` to
1008 terminate the XMLRPC server on the host.
Christopher Wileyd78249a2013-03-01 13:05:31 -08001009 @param ready_test_name String containing the name of a
1010 method defined on the XMLRPC server.
1011 @param timeout_seconds Number of seconds to wait
1012 for the server to become 'ready.' Will throw a
1013 TestFail error if server is not ready in time.
1014
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001015 """
1016 self.xmlrpc_disconnect(port)
1017
1018 # Chrome OS on the target closes down most external ports
1019 # for security. We could open the port, but doing that
1020 # would conflict with security tests that check that only
1021 # expected ports are open. So, to get to the port on the
1022 # target we use an ssh tunnel.
1023 local_port = utils.get_unused_port()
1024 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
1025 ssh_cmd = make_ssh_command(opts=tunnel_options)
1026 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
1027 logging.debug('Full tunnel command: %s', tunnel_cmd)
1028 tunnel_proc = subprocess.Popen(tunnel_cmd, shell=True, close_fds=True)
1029 logging.debug('Started XMLRPC tunnel, local = %d'
1030 ' remote = %d, pid = %d',
1031 local_port, port, tunnel_proc.pid)
1032
1033 # Start the server on the host. Redirection in the command
1034 # below is necessary, because 'ssh' won't terminate until
1035 # background child processes close stdin, stdout, and
1036 # stderr.
1037 remote_cmd = '( %s ) </dev/null >/dev/null 2>&1 & echo $!' % command
1038 remote_pid = self.run(remote_cmd).stdout.rstrip('\n')
1039 logging.debug('Started XMLRPC server on host %s, pid = %s',
1040 self.hostname, remote_pid)
1041
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001042 self._xmlrpc_proxy_map[port] = (command_name, tunnel_proc)
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001043 rpc_url = 'http://localhost:%d' % local_port
Christopher Wileyd78249a2013-03-01 13:05:31 -08001044 proxy = xmlrpclib.ServerProxy(rpc_url, allow_none=True)
1045 if ready_test_name is not None:
J. Richard Barnette13eb7c02013-03-07 12:06:29 -08001046 # retry.retry logs each attempt; calculate delay_sec to
1047 # keep log spam to a dull roar.
Christopher Wiley0ed712b2013-04-09 15:25:12 -07001048 @retry.retry((socket.error,
1049 xmlrpclib.ProtocolError,
1050 httplib.BadStatusLine),
Christopher Wileyd78249a2013-03-01 13:05:31 -08001051 timeout_min=timeout_seconds/60.0,
J. Richard Barnette13eb7c02013-03-07 12:06:29 -08001052 delay_sec=min(max(timeout_seconds/20.0, 0.1), 1))
Christopher Wileyd78249a2013-03-01 13:05:31 -08001053 def ready_test():
1054 """ Call proxy.ready_test_name(). """
1055 getattr(proxy, ready_test_name)()
1056 successful = False
1057 try:
1058 logging.info('Waiting %d seconds for XMLRPC server '
1059 'to start.', timeout_seconds)
1060 ready_test()
1061 successful = True
Christopher Wileyd78249a2013-03-01 13:05:31 -08001062 finally:
1063 if not successful:
1064 logging.error('Failed to start XMLRPC server.')
1065 self.xmlrpc_disconnect(port)
1066 logging.info('XMLRPC server started successfully.')
1067 return proxy
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001068
1069 def xmlrpc_disconnect(self, port):
1070 """Disconnect from an XMLRPC server on the host.
1071
1072 Terminates the remote XMLRPC server previously started for
1073 the given `port`. Also closes the local ssh tunnel created
1074 for the connection to the host. This function does not
1075 directly alter the state of a previously returned XMLRPC
1076 client object; however disconnection will cause all
1077 subsequent calls to methods on the object to fail.
1078
1079 This function does nothing if requested to disconnect a port
1080 that was not previously connected via `self.xmlrpc_connect()`
1081
1082 @param port Port number passed to a previous call to
1083 `xmlrpc_connect()`
1084 """
1085 if port not in self._xmlrpc_proxy_map:
1086 return
1087 entry = self._xmlrpc_proxy_map[port]
1088 remote_name = entry[0]
1089 tunnel_proc = entry[1]
1090 if remote_name:
1091 # We use 'pkill' to find our target process rather than
1092 # a PID, because the host may have rebooted since
1093 # connecting, and we don't want to kill an innocent
1094 # process with the same PID.
1095 #
1096 # 'pkill' helpfully exits with status 1 if no target
1097 # process is found, for which run() will throw an
Simran Basid5e5e272012-09-24 15:23:59 -07001098 # exception. We don't want that, so we the ignore
J. Richard Barnette1d78b012012-05-15 13:56:30 -07001099 # status.
1100 self.run("pkill -f '%s'" % remote_name, ignore_status=True)
1101
1102 if tunnel_proc.poll() is None:
1103 tunnel_proc.terminate()
1104 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
1105 else:
1106 logging.debug('Tunnel pid %d terminated early, status %d',
1107 tunnel_proc.pid, tunnel_proc.returncode)
1108 del self._xmlrpc_proxy_map[port]
1109
1110
1111 def xmlrpc_disconnect_all(self):
1112 """Disconnect all known XMLRPC proxy ports."""
1113 for port in self._xmlrpc_proxy_map.keys():
1114 self.xmlrpc_disconnect(port)
1115
1116
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001117 def _ping_check_status(self, status):
1118 """Ping the host once, and return whether it has a given status.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001119
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001120 @param status Check the ping status against this value.
1121 @return True iff `status` and the result of ping are the same
1122 (i.e. both True or both False).
1123
1124 """
1125 ping_val = utils.ping(self.hostname, tries=1, deadline=1)
1126 return not (status ^ (ping_val == 0))
1127
1128 def _ping_wait_for_status(self, status, timeout):
1129 """Wait for the host to have a given status (UP or DOWN).
1130
1131 Status is checked by polling. Polling will not last longer
1132 than the number of seconds in `timeout`. The polling
1133 interval will be long enough that only approximately
1134 _PING_WAIT_COUNT polling cycles will be executed, subject
1135 to a maximum interval of about one minute.
1136
1137 @param status Waiting will stop immediately if `ping` of the
1138 host returns this status.
1139 @param timeout Poll for at most this many seconds.
1140 @return True iff the host status from `ping` matched the
1141 requested status at the time of return.
1142
1143 """
1144 # _ping_check_status() takes about 1 second, hence the
1145 # "- 1" in the formula below.
1146 poll_interval = min(int(timeout / self._PING_WAIT_COUNT), 60) - 1
1147 end_time = time.time() + timeout
1148 while time.time() <= end_time:
1149 if self._ping_check_status(status):
1150 return True
1151 if poll_interval > 0:
1152 time.sleep(poll_interval)
1153
1154 # The last thing we did was sleep(poll_interval), so it may
1155 # have been too long since the last `ping`. Check one more
1156 # time, just to be sure.
1157 return self._ping_check_status(status)
1158
1159 def ping_wait_up(self, timeout):
1160 """Wait for the host to respond to `ping`.
1161
1162 N.B. This method is not a reliable substitute for
1163 `wait_up()`, because a host that responds to ping will not
1164 necessarily respond to ssh. This method should only be used
1165 if the target DUT can be considered functional even if it
1166 can't be reached via ssh.
1167
1168 @param timeout Minimum time to allow before declaring the
1169 host to be non-responsive.
1170 @return True iff the host answered to ping before the timeout.
1171
1172 """
1173 return self._ping_wait_for_status(self._PING_STATUS_UP, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001174
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001175 def ping_wait_down(self, timeout):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001176 """Wait until the host no longer responds to `ping`.
1177
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001178 This function can be used as a slightly faster version of
1179 `wait_down()`, by avoiding potentially long ssh timeouts.
1180
1181 @param timeout Minimum time to allow for the host to become
1182 non-responsive.
1183 @return True iff the host quit answering ping before the
1184 timeout.
1185
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001186 """
J. Richard Barnetteb6de7e32013-02-14 13:28:04 -08001187 return self._ping_wait_for_status(self._PING_STATUS_DOWN, timeout)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001188
1189 def test_wait_for_sleep(self):
1190 """Wait for the client to enter low-power sleep mode.
1191
1192 The test for "is asleep" can't distinguish a system that is
1193 powered off; to confirm that the unit was asleep, it is
1194 necessary to force resume, and then call
1195 `test_wait_for_resume()`.
1196
1197 This function is expected to be called from a test as part
1198 of a sequence like the following:
1199
1200 ~~~~~~~~
1201 boot_id = host.get_boot_id()
1202 # trigger sleep on the host
1203 host.test_wait_for_sleep()
1204 # trigger resume on the host
1205 host.test_wait_for_resume(boot_id)
1206 ~~~~~~~~
1207
1208 @exception TestFail The host did not go to sleep within
1209 the allowed time.
1210 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001211 if not self.ping_wait_down(timeout=self.SLEEP_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001212 raise error.TestFail(
1213 'client failed to sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001214 self.SLEEP_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001215
1216
1217 def test_wait_for_resume(self, old_boot_id):
1218 """Wait for the client to resume from low-power sleep mode.
1219
1220 The `old_boot_id` parameter should be the value from
1221 `get_boot_id()` obtained prior to entering sleep mode. A
1222 `TestFail` exception is raised if the boot id changes.
1223
1224 See @ref test_wait_for_sleep for more on this function's
1225 usage.
1226
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001227 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001228 target host went to sleep.
1229
1230 @exception TestFail The host did not respond within the
1231 allowed time.
1232 @exception TestFail The host responded, but the boot id test
1233 indicated a reboot rather than a sleep
1234 cycle.
1235 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001236 if not self.wait_up(timeout=self.RESUME_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001237 raise error.TestFail(
1238 'client failed to resume from sleep after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001239 self.RESUME_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001240 else:
1241 new_boot_id = self.get_boot_id()
1242 if new_boot_id != old_boot_id:
1243 raise error.TestFail(
1244 'client rebooted, but sleep was expected'
1245 ' (old boot %s, new boot %s)'
1246 % (old_boot_id, new_boot_id))
1247
1248
1249 def test_wait_for_shutdown(self):
1250 """Wait for the client to shut down.
1251
1252 The test for "has shut down" can't distinguish a system that
1253 is merely asleep; to confirm that the unit was down, it is
1254 necessary to force boot, and then call test_wait_for_boot().
1255
1256 This function is expected to be called from a test as part
1257 of a sequence like the following:
1258
1259 ~~~~~~~~
1260 boot_id = host.get_boot_id()
1261 # trigger shutdown on the host
1262 host.test_wait_for_shutdown()
1263 # trigger boot on the host
1264 host.test_wait_for_boot(boot_id)
1265 ~~~~~~~~
1266
1267 @exception TestFail The host did not shut down within the
1268 allowed time.
1269 """
Andrew Bresticker678c0c72013-01-22 10:44:09 -08001270 if not self.ping_wait_down(timeout=self.SHUTDOWN_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001271 raise error.TestFail(
1272 'client failed to shut down after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001273 self.SHUTDOWN_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001274
1275
1276 def test_wait_for_boot(self, old_boot_id=None):
1277 """Wait for the client to boot from cold power.
1278
1279 The `old_boot_id` parameter should be the value from
1280 `get_boot_id()` obtained prior to shutting down. A
1281 `TestFail` exception is raised if the boot id does not
1282 change. The boot id test is omitted if `old_boot_id` is not
1283 specified.
1284
1285 See @ref test_wait_for_shutdown for more on this function's
1286 usage.
1287
J. Richard Barnette7214e0b2013-02-06 15:20:49 -08001288 @param old_boot_id A boot id value obtained before the
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001289 shut down.
1290
1291 @exception TestFail The host did not respond within the
1292 allowed time.
1293 @exception TestFail The host responded, but the boot id test
1294 indicated that there was no reboot.
1295 """
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001296 if not self.wait_up(timeout=self.REBOOT_TIMEOUT):
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001297 raise error.TestFail(
1298 'client failed to reboot after %d seconds' %
J. Richard Barnetteeb69d722012-06-18 17:29:44 -07001299 self.REBOOT_TIMEOUT)
J. Richard Barnette134ec2c2012-04-25 12:59:37 -07001300 elif old_boot_id:
1301 if self.get_boot_id() == old_boot_id:
1302 raise error.TestFail(
1303 'client is back up, but did not reboot'
1304 ' (boot %s)' % old_boot_id)
Simran Basid5e5e272012-09-24 15:23:59 -07001305
1306
1307 @staticmethod
1308 def check_for_rpm_support(hostname):
1309 """For a given hostname, return whether or not it is powered by an RPM.
1310
1311 @return None if this host does not follows the defined naming format
1312 for RPM powered DUT's in the lab. If it does follow the format,
1313 it returns a regular expression MatchObject instead.
1314 """
Richard Barnette82c35912012-11-20 10:09:10 -08001315 return re.match(SiteHost._RPM_HOSTNAME_REGEX, hostname)
Simran Basid5e5e272012-09-24 15:23:59 -07001316
1317
1318 def has_power(self):
1319 """For this host, return whether or not it is powered by an RPM.
1320
1321 @return True if this host is in the CROS lab and follows the defined
1322 naming format.
1323 """
1324 return SiteHost.check_for_rpm_support(self.hostname)
1325
1326
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001327 def _set_power(self, state, power_method):
1328 """Sets the power to the host via RPM, Servo or manual.
1329
1330 @param state Specifies which power state to set to DUT
1331 @param power_method Specifies which method of power control to
1332 use. By default "RPM" will be used. Valid values
1333 are the strings "RPM", "manual", "servoj10".
1334
1335 """
1336 ACCEPTABLE_STATES = ['ON', 'OFF']
1337
1338 if state.upper() not in ACCEPTABLE_STATES:
1339 raise error.TestError('State must be one of: %s.'
1340 % (ACCEPTABLE_STATES,))
1341
1342 if power_method == self.POWER_CONTROL_SERVO:
1343 logging.info('Setting servo port J10 to %s', state)
1344 self.servo.set('prtctl3_pwren', state.lower())
1345 time.sleep(self._USB_POWER_TIMEOUT)
1346 elif power_method == self.POWER_CONTROL_MANUAL:
1347 logging.info('You have %d seconds to set the AC power to %s.',
1348 self._POWER_CYCLE_TIMEOUT, state)
1349 time.sleep(self._POWER_CYCLE_TIMEOUT)
1350 else:
1351 if not self.has_power():
1352 raise error.TestFail('DUT does not have RPM connected.')
Simran Basi5e6339a2013-03-21 11:34:32 -07001353 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1354 afe.set_host_attribute(self._RPM_OUTLET_CHANGED, True,
1355 hostname=self.hostname)
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001356 rpm_client.set_power(self.hostname, state.upper())
Simran Basid5e5e272012-09-24 15:23:59 -07001357
1358
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001359 def power_off(self, power_method=POWER_CONTROL_RPM):
1360 """Turn off power to this host via RPM, Servo or manual.
1361
1362 @param power_method Specifies which method of power control to
1363 use. By default "RPM" will be used. Valid values
1364 are the strings "RPM", "manual", "servoj10".
1365
1366 """
1367 self._set_power('OFF', power_method)
Simran Basid5e5e272012-09-24 15:23:59 -07001368
1369
Ismail Noorbasha07fdb612013-02-14 14:13:31 -08001370 def power_on(self, power_method=POWER_CONTROL_RPM):
1371 """Turn on power to this host via RPM, Servo or manual.
1372
1373 @param power_method Specifies which method of power control to
1374 use. By default "RPM" will be used. Valid values
1375 are the strings "RPM", "manual", "servoj10".
1376
1377 """
1378 self._set_power('ON', power_method)
1379
1380
1381 def power_cycle(self, power_method=POWER_CONTROL_RPM):
1382 """Cycle power to this host by turning it OFF, then ON.
1383
1384 @param power_method Specifies which method of power control to
1385 use. By default "RPM" will be used. Valid values
1386 are the strings "RPM", "manual", "servoj10".
1387
1388 """
1389 if power_method in (self.POWER_CONTROL_SERVO,
1390 self.POWER_CONTROL_MANUAL):
1391 self.power_off(power_method=power_method)
1392 time.sleep(self._POWER_CYCLE_TIMEOUT)
1393 self.power_on(power_method=power_method)
1394 else:
1395 rpm_client.set_power(self.hostname, 'CYCLE')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001396
1397
1398 def get_platform(self):
1399 """Determine the correct platform label for this host.
1400
1401 @returns a string representing this host's platform.
1402 """
1403 crossystem = utils.Crossystem(self)
1404 crossystem.init()
1405 # Extract fwid value and use the leading part as the platform id.
1406 # fwid generally follow the format of {platform}.{firmware version}
1407 # Example: Alex.X.YYY.Z or Google_Alex.X.YYY.Z
1408 platform = crossystem.fwid().split('.')[0].lower()
1409 # Newer platforms start with 'Google_' while the older ones do not.
1410 return platform.replace('google_', '')
1411
1412
Aviv Keshet74c89a92013-02-04 15:18:30 -08001413 @label_decorator()
Simran Basic6f1f7a2012-10-16 10:47:46 -07001414 def get_board(self):
1415 """Determine the correct board label for this host.
1416
1417 @returns a string representing this host's board.
1418 """
1419 release_info = utils.parse_cmd_output('cat /etc/lsb-release',
1420 run_method=self.run)
1421 board = release_info['CHROMEOS_RELEASE_BOARD']
1422 # Devices in the lab generally have the correct board name but our own
1423 # development devices have {board_name}-signed-{key_type}. The board
1424 # name may also begin with 'x86-' which we need to keep.
Simran Basi833814b2013-01-29 13:13:43 -08001425 board_format_string = ds_constants.BOARD_PREFIX + '%s'
Simran Basic6f1f7a2012-10-16 10:47:46 -07001426 if 'x86' not in board:
Simran Basi833814b2013-01-29 13:13:43 -08001427 return board_format_string % board.split('-')[0]
1428 return board_format_string % '-'.join(board.split('-')[0:2])
Simran Basic6f1f7a2012-10-16 10:47:46 -07001429
1430
Aviv Keshet74c89a92013-02-04 15:18:30 -08001431 @label_decorator('lightsensor')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001432 def has_lightsensor(self):
1433 """Determine the correct board label for this host.
1434
1435 @returns the string 'lightsensor' if this host has a lightsensor or
1436 None if it does not.
1437 """
1438 search_cmd = "find -L %s -maxdepth 4 | egrep '%s'" % (
Richard Barnette82c35912012-11-20 10:09:10 -08001439 self._LIGHTSENSOR_SEARCH_DIR, '|'.join(self._LIGHTSENSOR_FILES))
Simran Basic6f1f7a2012-10-16 10:47:46 -07001440 try:
1441 # Run the search cmd following the symlinks. Stderr_tee is set to
1442 # None as there can be a symlink loop, but the command will still
1443 # execute correctly with a few messages printed to stderr.
1444 self.run(search_cmd, stdout_tee=None, stderr_tee=None)
1445 return 'lightsensor'
1446 except error.AutoservRunError:
1447 # egrep exited with a return code of 1 meaning none of the possible
1448 # lightsensor files existed.
1449 return None
1450
1451
Aviv Keshet74c89a92013-02-04 15:18:30 -08001452 @label_decorator('bluetooth')
Simran Basic6f1f7a2012-10-16 10:47:46 -07001453 def has_bluetooth(self):
1454 """Determine the correct board label for this host.
1455
1456 @returns the string 'bluetooth' if this host has bluetooth or
1457 None if it does not.
1458 """
1459 try:
1460 self.run('test -d /sys/class/bluetooth/hci0')
1461 # test exited with a return code of 0.
1462 return 'bluetooth'
1463 except error.AutoservRunError:
1464 # test exited with a return code 1 meaning the directory did not
1465 # exist.
1466 return None
1467
1468
1469 def get_labels(self):
1470 """Return a list of labels for this given host.
1471
1472 This is the main way to retrieve all the automatic labels for a host
1473 as it will run through all the currently implemented label functions.
1474 """
1475 labels = []
Richard Barnette82c35912012-11-20 10:09:10 -08001476 for label_function in self._LABEL_FUNCTIONS:
Simran Basic6f1f7a2012-10-16 10:47:46 -07001477 label = label_function(self)
1478 if label:
1479 labels.append(label)
1480 return labels