blob: a60fa41055626cffeb18b514f03027aa0cb75fa9 [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 -07007import logging
8
9import common
10from autotest_lib.frontend.afe.json_rpc import proxy
11from autotest_lib.server import frontend
12
13
14### Constants for label prefixes
15CROS_VERSION_PREFIX = 'cros-version'
Fang Dengdbc86322013-08-09 16:17:30 -070016FW_VERSION_PREFIX = 'fw-version'
Alex Miller0516e4c2013-06-03 18:07:48 -070017
18
19### Helpers to convert value to label
20def cros_version_to_label(image):
21 """
22 Returns the proper label name for a ChromeOS build of |image|.
23
24 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
25 @returns: A string that is the appropriate label name.
26
27 """
28 return CROS_VERSION_PREFIX + ':' + image
29
30
Dan Shi9cb0eec2014-06-03 09:04:50 -070031def fw_version_to_label(image):
32 """
33 Returns the proper label name for a firmware build of |image|.
34
35 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
36 @returns: A string that is the appropriate label name.
37
38 """
39 return FW_VERSION_PREFIX + ':' + image
40
41
Alex Miller1968edf2014-02-27 18:11:36 -080042class _SpecialTaskAction(object):
Alex Miller0516e4c2013-06-03 18:07:48 -070043 """
Alex Miller1968edf2014-02-27 18:11:36 -080044 Base class to give a template for mapping labels to tests.
Alex Miller0516e4c2013-06-03 18:07:48 -070045 """
Alex Miller1968edf2014-02-27 18:11:36 -080046
47 __metaclass__ = abc.ABCMeta
Alex Miller0516e4c2013-06-03 18:07:48 -070048
49
Alex Miller1968edf2014-02-27 18:11:36 -080050 # One cannot do
51 # @abc.abstractproperty
52 # _actions = {}
53 # so this is the next best thing
54 @abc.abstractproperty
55 def _actions(self):
56 """A dictionary mapping labels to test names."""
57 pass
58
59
60 @abc.abstractproperty
61 def name(self):
62 """The name of this special task to be used in output."""
63 pass
64
65
66 @classmethod
67 def acts_on(cls, label):
68 """
69 Returns True if the label is a label that we recognize as something we
70 know how to act on, given our _actions.
71
72 @param label: The label as a string.
73 @returns: True if there exists a test to run for this label.
74
75 """
76 return label.split(':')[0] in cls._actions
77
78
79 @classmethod
80 def test_for(cls, label):
81 """
82 Returns the test associated with the given (string) label name.
83
84 @param label: The label for which the action is being requested.
85 @returns: The string name of the test that should be run.
86 @raises KeyError: If the name was not recognized as one we care about.
87
88 """
89 return cls._actions[label]
90
91
Alex Milleraa772002014-04-10 17:51:21 -070092 @classmethod
93 def partition(cls, labels):
94 """
95 Filter a list of labels into two sets: those labels that we know how to
96 act on and those that we don't know how to act on.
97
98 @param labels: A list of strings of labels.
99 @returns: A tuple where the first element is a set of unactionable
100 labels, and the second element is a set of the actionable
101 labels.
102 """
103 capabilities = set()
104 configurations = set()
105
106 for label in labels:
107 if cls.acts_on(label):
108 configurations.add(label)
109 else:
110 capabilities.add(label)
111
112 return capabilities, configurations
113
114
Alex Miller1968edf2014-02-27 18:11:36 -0800115class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -0700116 """
Alex Miller1968edf2014-02-27 18:11:36 -0800117 Tests to verify that the DUT is in a sane, known good state that we can run
118 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -0700119 """
Alex Miller1968edf2014-02-27 18:11:36 -0800120
121 _actions = {
Derek Basehore9a1747a2014-05-06 16:50:12 -0700122 'modem_repair': 'cellular_StaleModemReboot',
123 'rpm': 'power_RPMTest',
Alex Miller1968edf2014-02-27 18:11:36 -0800124 }
125
126 name = 'verify'
127
128
129class Provision(_SpecialTaskAction):
130 """
131 Provisioning runs to change the configuration of the DUT from one state to
132 another. It will only be run on verified DUTs.
133 """
134
135 # TODO(milleral): http://crbug.com/249555
136 # Create some way to discover and register provisioning tests so that we
137 # don't need to hand-maintain a list of all of them.
138 _actions = {
139 CROS_VERSION_PREFIX: 'provision_AutoUpdate',
140 FW_VERSION_PREFIX: 'provision_FirmwareUpdate',
141 }
142
143 name = 'provision'
144
145
146class Cleanup(_SpecialTaskAction):
147 """
148 Cleanup runs after a test fails to try and remove artifacts of tests and
149 ensure the DUT will be in a sane state for the next test run.
150 """
151
152 _actions = {
Alex Millerf47db9c2014-04-18 18:36:36 -0700153 'cleanup-reboot': 'generic_RebootTest',
Alex Miller1968edf2014-02-27 18:11:36 -0800154 }
155
156 name = 'cleanup'
157
158
159class Repair(_SpecialTaskAction):
160 """
161 Repair runs when one of the other special tasks fails. It should be able
162 to take a component of the DUT that's in an unknown state and restore it to
163 a good state.
164 """
165
166 _actions = {
167 }
168
169 name = 'repair'
170
171
Alex Miller1968edf2014-02-27 18:11:36 -0800172
Alex Milleraa772002014-04-10 17:51:21 -0700173# TODO(milleral): crbug.com/364273
174# Label doesn't really mean label in this context. We're putting things into
175# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
176# doing that.
177def is_for_special_action(label):
178 """
179 If any special task handles the label specially, then we're using the label
180 to communicate that we want an action, and not as an actual dependency that
181 the test has.
182
183 @param label: A string label name.
184 @return True if any special task handles this label specially,
185 False if no special task handles this label.
186 """
187 return (Verify.acts_on(label) or
188 Provision.acts_on(label) or
189 Cleanup.acts_on(label) or
190 Repair.acts_on(label))
Alex Miller0516e4c2013-06-03 18:07:48 -0700191
192
193def filter_labels(labels):
194 """
195 Filter a list of labels into two sets: those labels that we know how to
196 change and those that we don't. For the ones we know how to change, split
197 them apart into the name of configuration type and its value.
198
199 @param labels: A list of strings of labels.
200 @returns: A tuple where the first element is a set of unprovisionable
201 labels, and the second element is a set of the provisionable
202 labels.
203
204 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
205 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
206
207 """
Alex Milleraa772002014-04-10 17:51:21 -0700208 return Provision.partition(labels)
Alex Miller0516e4c2013-06-03 18:07:48 -0700209
210
211def split_labels(labels):
212 """
213 Split a list of labels into a dict mapping name to value. All labels must
214 be provisionable labels, or else a ValueError
215
216 @param labels: list of strings of label names
217 @returns: A dict of where the key is the configuration name, and the value
218 is the configuration value.
219 @raises: ValueError if a label is not a provisionable label.
220
221 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
222 {'cros-version': 'lumpy-release/R28-3993.0.0'}
223 >>> split_labels(['bluetooth'])
224 Traceback (most recent call last):
225 ...
226 ValueError: Unprovisionable label bluetooth
227
228 """
229 configurations = dict()
230
231 for label in labels:
Alex Milleraa772002014-04-10 17:51:21 -0700232 if Provision.acts_on(label):
Alex Miller0516e4c2013-06-03 18:07:48 -0700233 name, value = label.split(':', 1)
234 configurations[name] = value
235 else:
236 raise ValueError('Unprovisionable label %s' % label)
237
238 return configurations
239
240
Alex Miller2229c9c2013-08-27 15:20:39 -0700241def join(provision_type, provision_value):
242 """
243 Combine the provision type and value into the label name.
244
245 @param provision_type: One of the constants that are the label prefixes.
246 @param provision_value: A string of the value for this provision type.
247 @returns: A string that is the label name for this (type, value) pair.
248
249 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
250 'cros-version:lumpy-release/R27-3773.0.0'
251
252 """
253 return '%s:%s' % (provision_type, provision_value)
254
255
Alex Miller667b5f22014-02-28 15:33:39 -0800256class SpecialTaskActionException(Exception):
257 """
258 Exception raised when a special task fails to successfully run a test that
259 is required.
260
261 This is also a literally meaningless exception. It's always just discarded.
262 """
263
264
265def run_special_task_actions(job, host, labels, task):
266 """
267 Iterate through all `label`s and run any tests on `host` that `task` has
268 corresponding to the passed in labels.
269
270 Emits status lines for each run test, and INFO lines for each skipped label.
271
272 @param job: A job object from a control file.
273 @param host: The host to run actions on.
274 @param labels: The list of job labels to work on.
275 @param task: An instance of _SpecialTaskAction.
276 @returns: None
277 @raises: SpecialTaskActionException if a test fails.
278
279 """
280 capabilities, configuration = filter_labels(labels)
281
282 for label in capabilities:
283 if task.acts_on(label):
284 test = task.test_for(label)
285 success = job.run_test(test, host=host)
286 if not success:
287 raise SpecialTaskActionException()
288 else:
289 job.record('INFO', None, task.name,
290 "Can't %s label '%s'." % (task.name, label))
291
292 for name, value in split_labels(configuration).items():
293 if task.acts_on(name):
294 test = task.test_for(name)
295 success = job.run_test(test, host=host, value=value)
296 if not success:
297 raise SpecialTaskActionException()
298 else:
299 job.record('INFO', None, task.name,
300 "Can't %s label '%s:%s'." % (task.name, name, value))
301
302
Alex Miller2229c9c2013-08-27 15:20:39 -0700303# This has been copied out of dynamic_suite's reimager.py, which no longer
304# exists. I'd prefer if this would go away by doing http://crbug.com/249424,
305# so that labels are just automatically made when we try to add them to a host.
Alex Miller0516e4c2013-06-03 18:07:48 -0700306def ensure_label_exists(name):
307 """
308 Ensure that a label called |name| exists in the autotest DB.
309
310 @param name: the label to check for/create.
311 @raises ValidationError: There was an error in the response that was
312 not because the label already existed.
313
314 """
315 afe = frontend.AFE()
316 try:
317 afe.create_label(name=name)
318 except proxy.ValidationError as ve:
319 if ('name' in ve.problem_keys and
320 'This value must be unique' in ve.problem_keys['name']):
321 logging.debug('Version label %s already exists', name)
322 else:
323 raise ve