blob: 7e40466b681325bb46e45202f9eae4ab2655ea95 [file] [log] [blame]
Alex Miller0516e4c2013-06-03 18:07:48 -07001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5
Alex Miller1968edf2014-02-27 18:11:36 -08006import abc
Alex Miller0516e4c2013-06-03 18:07:48 -07007
8import common
Fang Dengaf30e7c2014-11-15 13:57:03 -08009from autotest_lib.server.cros import provision_actionables as actionables
Alex Miller0516e4c2013-06-03 18:07:48 -070010
11
12### Constants for label prefixes
13CROS_VERSION_PREFIX = 'cros-version'
Dan Shi0723bf52015-06-24 10:52:38 -070014FW_RW_VERSION_PREFIX = 'fwrw-version'
Dan Shi36cfd832014-10-10 13:38:51 -070015FW_RO_VERSION_PREFIX = 'fwro-version'
Alex Miller0516e4c2013-06-03 18:07:48 -070016
Chris Sosae92399e2015-04-24 11:32:59 -070017# Default number of provisions attempts to try if we believe the devserver is
18# flaky.
19FLAKY_DEVSERVER_ATTEMPTS = 2
20
Alex Miller0516e4c2013-06-03 18:07:48 -070021
22### Helpers to convert value to label
23def cros_version_to_label(image):
24 """
25 Returns the proper label name for a ChromeOS build of |image|.
26
27 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
28 @returns: A string that is the appropriate label name.
29
30 """
31 return CROS_VERSION_PREFIX + ':' + image
32
33
Dan Shi9cb0eec2014-06-03 09:04:50 -070034def fw_version_to_label(image):
35 """
36 Returns the proper label name for a firmware build of |image|.
37
38 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
39 @returns: A string that is the appropriate label name.
40
41 """
Dan Shi0723bf52015-06-24 10:52:38 -070042 return FW_RW_VERSION_PREFIX + ':' + image
Dan Shi9cb0eec2014-06-03 09:04:50 -070043
44
Alex Miller1968edf2014-02-27 18:11:36 -080045class _SpecialTaskAction(object):
Alex Miller0516e4c2013-06-03 18:07:48 -070046 """
Alex Miller1968edf2014-02-27 18:11:36 -080047 Base class to give a template for mapping labels to tests.
Alex Miller0516e4c2013-06-03 18:07:48 -070048 """
Alex Miller1968edf2014-02-27 18:11:36 -080049
50 __metaclass__ = abc.ABCMeta
Alex Miller0516e4c2013-06-03 18:07:48 -070051
52
Alex Miller1968edf2014-02-27 18:11:36 -080053 # One cannot do
54 # @abc.abstractproperty
55 # _actions = {}
56 # so this is the next best thing
57 @abc.abstractproperty
58 def _actions(self):
59 """A dictionary mapping labels to test names."""
60 pass
61
62
63 @abc.abstractproperty
64 def name(self):
65 """The name of this special task to be used in output."""
66 pass
67
68
69 @classmethod
70 def acts_on(cls, label):
71 """
72 Returns True if the label is a label that we recognize as something we
73 know how to act on, given our _actions.
74
75 @param label: The label as a string.
76 @returns: True if there exists a test to run for this label.
77
78 """
79 return label.split(':')[0] in cls._actions
80
81
82 @classmethod
83 def test_for(cls, label):
84 """
85 Returns the test associated with the given (string) label name.
86
87 @param label: The label for which the action is being requested.
88 @returns: The string name of the test that should be run.
89 @raises KeyError: If the name was not recognized as one we care about.
90
91 """
92 return cls._actions[label]
93
94
Alex Milleraa772002014-04-10 17:51:21 -070095 @classmethod
96 def partition(cls, labels):
97 """
98 Filter a list of labels into two sets: those labels that we know how to
99 act on and those that we don't know how to act on.
100
101 @param labels: A list of strings of labels.
102 @returns: A tuple where the first element is a set of unactionable
103 labels, and the second element is a set of the actionable
104 labels.
105 """
106 capabilities = set()
107 configurations = set()
108
109 for label in labels:
110 if cls.acts_on(label):
111 configurations.add(label)
112 else:
113 capabilities.add(label)
114
115 return capabilities, configurations
116
117
Alex Miller1968edf2014-02-27 18:11:36 -0800118class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -0700119 """
Alex Miller1968edf2014-02-27 18:11:36 -0800120 Tests to verify that the DUT is in a sane, known good state that we can run
121 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -0700122 """
Alex Miller1968edf2014-02-27 18:11:36 -0800123
124 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800125 'modem_repair': actionables.TestActionable('cellular_StaleModemReboot'),
Dan Shi22220c02015-07-10 04:21:36 +0000126 'rpm': actionables.TestActionable('power_RPMTest'),
Alex Miller1968edf2014-02-27 18:11:36 -0800127 }
128
129 name = 'verify'
130
131
132class Provision(_SpecialTaskAction):
133 """
134 Provisioning runs to change the configuration of the DUT from one state to
135 another. It will only be run on verified DUTs.
136 """
137
138 # TODO(milleral): http://crbug.com/249555
139 # Create some way to discover and register provisioning tests so that we
140 # don't need to hand-maintain a list of all of them.
141 _actions = {
Fang Deng9d548742015-02-03 11:35:02 -0800142 CROS_VERSION_PREFIX: actionables.TestActionable(
143 'provision_AutoUpdate',
144 extra_kwargs={'disable_sysinfo': False,
145 'disable_before_test_sysinfo': False,
146 'disable_before_iteration_sysinfo': True,
147 'disable_after_test_sysinfo': True,
148 'disable_after_iteration_sysinfo': True}),
Dan Shi0723bf52015-06-24 10:52:38 -0700149 FW_RW_VERSION_PREFIX: actionables.TestActionable(
Fang Dengaf30e7c2014-11-15 13:57:03 -0800150 'provision_FirmwareUpdate'),
Alex Miller1968edf2014-02-27 18:11:36 -0800151 }
152
153 name = 'provision'
154
155
156class Cleanup(_SpecialTaskAction):
157 """
158 Cleanup runs after a test fails to try and remove artifacts of tests and
159 ensure the DUT will be in a sane state for the next test run.
160 """
161
162 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800163 'cleanup-reboot': actionables.RebootActionable(),
Alex Miller1968edf2014-02-27 18:11:36 -0800164 }
165
166 name = 'cleanup'
167
168
169class Repair(_SpecialTaskAction):
170 """
171 Repair runs when one of the other special tasks fails. It should be able
172 to take a component of the DUT that's in an unknown state and restore it to
173 a good state.
174 """
175
176 _actions = {
177 }
178
179 name = 'repair'
180
181
Alex Miller1968edf2014-02-27 18:11:36 -0800182
Alex Milleraa772002014-04-10 17:51:21 -0700183# TODO(milleral): crbug.com/364273
184# Label doesn't really mean label in this context. We're putting things into
185# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
186# doing that.
187def is_for_special_action(label):
188 """
189 If any special task handles the label specially, then we're using the label
190 to communicate that we want an action, and not as an actual dependency that
191 the test has.
192
193 @param label: A string label name.
194 @return True if any special task handles this label specially,
195 False if no special task handles this label.
196 """
197 return (Verify.acts_on(label) or
198 Provision.acts_on(label) or
199 Cleanup.acts_on(label) or
200 Repair.acts_on(label))
Alex Miller0516e4c2013-06-03 18:07:48 -0700201
202
203def filter_labels(labels):
204 """
205 Filter a list of labels into two sets: those labels that we know how to
206 change and those that we don't. For the ones we know how to change, split
207 them apart into the name of configuration type and its value.
208
209 @param labels: A list of strings of labels.
210 @returns: A tuple where the first element is a set of unprovisionable
211 labels, and the second element is a set of the provisionable
212 labels.
213
214 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
215 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
216
217 """
Alex Milleraa772002014-04-10 17:51:21 -0700218 return Provision.partition(labels)
Alex Miller0516e4c2013-06-03 18:07:48 -0700219
220
221def split_labels(labels):
222 """
223 Split a list of labels into a dict mapping name to value. All labels must
224 be provisionable labels, or else a ValueError
225
226 @param labels: list of strings of label names
227 @returns: A dict of where the key is the configuration name, and the value
228 is the configuration value.
229 @raises: ValueError if a label is not a provisionable label.
230
231 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
232 {'cros-version': 'lumpy-release/R28-3993.0.0'}
233 >>> split_labels(['bluetooth'])
234 Traceback (most recent call last):
235 ...
236 ValueError: Unprovisionable label bluetooth
237
238 """
239 configurations = dict()
240
241 for label in labels:
Alex Milleraa772002014-04-10 17:51:21 -0700242 if Provision.acts_on(label):
Alex Miller0516e4c2013-06-03 18:07:48 -0700243 name, value = label.split(':', 1)
244 configurations[name] = value
245 else:
246 raise ValueError('Unprovisionable label %s' % label)
247
248 return configurations
249
250
Alex Miller2229c9c2013-08-27 15:20:39 -0700251def join(provision_type, provision_value):
252 """
253 Combine the provision type and value into the label name.
254
255 @param provision_type: One of the constants that are the label prefixes.
256 @param provision_value: A string of the value for this provision type.
257 @returns: A string that is the label name for this (type, value) pair.
258
259 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
260 'cros-version:lumpy-release/R27-3773.0.0'
261
262 """
263 return '%s:%s' % (provision_type, provision_value)
264
265
Alex Miller667b5f22014-02-28 15:33:39 -0800266class SpecialTaskActionException(Exception):
267 """
268 Exception raised when a special task fails to successfully run a test that
269 is required.
270
271 This is also a literally meaningless exception. It's always just discarded.
272 """
273
274
275def run_special_task_actions(job, host, labels, task):
276 """
277 Iterate through all `label`s and run any tests on `host` that `task` has
278 corresponding to the passed in labels.
279
280 Emits status lines for each run test, and INFO lines for each skipped label.
281
282 @param job: A job object from a control file.
283 @param host: The host to run actions on.
284 @param labels: The list of job labels to work on.
285 @param task: An instance of _SpecialTaskAction.
286 @returns: None
287 @raises: SpecialTaskActionException if a test fails.
288
289 """
290 capabilities, configuration = filter_labels(labels)
291
292 for label in capabilities:
293 if task.acts_on(label):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800294 action_item = task.test_for(label)
295 success = action_item.execute(job=job, host=host)
Alex Miller667b5f22014-02-28 15:33:39 -0800296 if not success:
297 raise SpecialTaskActionException()
298 else:
299 job.record('INFO', None, task.name,
300 "Can't %s label '%s'." % (task.name, label))
301
302 for name, value in split_labels(configuration).items():
303 if task.acts_on(name):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800304 action_item = task.test_for(name)
305 success = action_item.execute(job=job, host=host, value=value)
Alex Miller667b5f22014-02-28 15:33:39 -0800306 if not success:
307 raise SpecialTaskActionException()
308 else:
309 job.record('INFO', None, task.name,
310 "Can't %s label '%s:%s'." % (task.name, name, value))