blob: 5d9e783264335e57750c8ae00a16ea9212f23a4d [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',
Dan Shid2b82f32014-10-24 13:39:19 -0700123 # TODO(crbug.com/404421): set rpm action to power_RPMTest after the RPM
124 # is stable in lab (destiny). The power_RPMTest failure led to reset job
125 # failure and that left dut in Repair Failed. Since the test will fail
126 # anyway due to the destiny lab issue, and test retry will retry the
127 # test in another DUT.
128 # This change temporarily disable the RPM check in reset job.
129 # Another way to do this is to remove rpm dependency from tests' control
130 # file. That will involve changes on multiple control files. This one
131 # line change here is a simple temporary fix.
132 'rpm': 'dummy_PassServer',
Alex Miller1968edf2014-02-27 18:11:36 -0800133 }
134
135 name = 'verify'
136
137
138class Provision(_SpecialTaskAction):
139 """
140 Provisioning runs to change the configuration of the DUT from one state to
141 another. It will only be run on verified DUTs.
142 """
143
144 # TODO(milleral): http://crbug.com/249555
145 # Create some way to discover and register provisioning tests so that we
146 # don't need to hand-maintain a list of all of them.
147 _actions = {
148 CROS_VERSION_PREFIX: 'provision_AutoUpdate',
149 FW_VERSION_PREFIX: 'provision_FirmwareUpdate',
150 }
151
152 name = 'provision'
153
154
155class Cleanup(_SpecialTaskAction):
156 """
157 Cleanup runs after a test fails to try and remove artifacts of tests and
158 ensure the DUT will be in a sane state for the next test run.
159 """
160
161 _actions = {
Alex Millerf47db9c2014-04-18 18:36:36 -0700162 'cleanup-reboot': 'generic_RebootTest',
Alex Miller1968edf2014-02-27 18:11:36 -0800163 }
164
165 name = 'cleanup'
166
167
168class Repair(_SpecialTaskAction):
169 """
170 Repair runs when one of the other special tasks fails. It should be able
171 to take a component of the DUT that's in an unknown state and restore it to
172 a good state.
173 """
174
175 _actions = {
176 }
177
178 name = 'repair'
179
180
Alex Miller1968edf2014-02-27 18:11:36 -0800181
Alex Milleraa772002014-04-10 17:51:21 -0700182# TODO(milleral): crbug.com/364273
183# Label doesn't really mean label in this context. We're putting things into
184# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
185# doing that.
186def is_for_special_action(label):
187 """
188 If any special task handles the label specially, then we're using the label
189 to communicate that we want an action, and not as an actual dependency that
190 the test has.
191
192 @param label: A string label name.
193 @return True if any special task handles this label specially,
194 False if no special task handles this label.
195 """
196 return (Verify.acts_on(label) or
197 Provision.acts_on(label) or
198 Cleanup.acts_on(label) or
199 Repair.acts_on(label))
Alex Miller0516e4c2013-06-03 18:07:48 -0700200
201
202def filter_labels(labels):
203 """
204 Filter a list of labels into two sets: those labels that we know how to
205 change and those that we don't. For the ones we know how to change, split
206 them apart into the name of configuration type and its value.
207
208 @param labels: A list of strings of labels.
209 @returns: A tuple where the first element is a set of unprovisionable
210 labels, and the second element is a set of the provisionable
211 labels.
212
213 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
214 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
215
216 """
Alex Milleraa772002014-04-10 17:51:21 -0700217 return Provision.partition(labels)
Alex Miller0516e4c2013-06-03 18:07:48 -0700218
219
220def split_labels(labels):
221 """
222 Split a list of labels into a dict mapping name to value. All labels must
223 be provisionable labels, or else a ValueError
224
225 @param labels: list of strings of label names
226 @returns: A dict of where the key is the configuration name, and the value
227 is the configuration value.
228 @raises: ValueError if a label is not a provisionable label.
229
230 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
231 {'cros-version': 'lumpy-release/R28-3993.0.0'}
232 >>> split_labels(['bluetooth'])
233 Traceback (most recent call last):
234 ...
235 ValueError: Unprovisionable label bluetooth
236
237 """
238 configurations = dict()
239
240 for label in labels:
Alex Milleraa772002014-04-10 17:51:21 -0700241 if Provision.acts_on(label):
Alex Miller0516e4c2013-06-03 18:07:48 -0700242 name, value = label.split(':', 1)
243 configurations[name] = value
244 else:
245 raise ValueError('Unprovisionable label %s' % label)
246
247 return configurations
248
249
Alex Miller2229c9c2013-08-27 15:20:39 -0700250def join(provision_type, provision_value):
251 """
252 Combine the provision type and value into the label name.
253
254 @param provision_type: One of the constants that are the label prefixes.
255 @param provision_value: A string of the value for this provision type.
256 @returns: A string that is the label name for this (type, value) pair.
257
258 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
259 'cros-version:lumpy-release/R27-3773.0.0'
260
261 """
262 return '%s:%s' % (provision_type, provision_value)
263
264
Alex Miller667b5f22014-02-28 15:33:39 -0800265class SpecialTaskActionException(Exception):
266 """
267 Exception raised when a special task fails to successfully run a test that
268 is required.
269
270 This is also a literally meaningless exception. It's always just discarded.
271 """
272
273
274def run_special_task_actions(job, host, labels, task):
275 """
276 Iterate through all `label`s and run any tests on `host` that `task` has
277 corresponding to the passed in labels.
278
279 Emits status lines for each run test, and INFO lines for each skipped label.
280
281 @param job: A job object from a control file.
282 @param host: The host to run actions on.
283 @param labels: The list of job labels to work on.
284 @param task: An instance of _SpecialTaskAction.
285 @returns: None
286 @raises: SpecialTaskActionException if a test fails.
287
288 """
289 capabilities, configuration = filter_labels(labels)
290
291 for label in capabilities:
292 if task.acts_on(label):
293 test = task.test_for(label)
294 success = job.run_test(test, host=host)
295 if not success:
296 raise SpecialTaskActionException()
297 else:
298 job.record('INFO', None, task.name,
299 "Can't %s label '%s'." % (task.name, label))
300
301 for name, value in split_labels(configuration).items():
302 if task.acts_on(name):
303 test = task.test_for(name)
304 success = job.run_test(test, host=host, value=value)
305 if not success:
306 raise SpecialTaskActionException()
307 else:
308 job.record('INFO', None, task.name,
309 "Can't %s label '%s:%s'." % (task.name, name, value))
310
311
Alex Miller2229c9c2013-08-27 15:20:39 -0700312# This has been copied out of dynamic_suite's reimager.py, which no longer
313# exists. I'd prefer if this would go away by doing http://crbug.com/249424,
314# so that labels are just automatically made when we try to add them to a host.
Alex Miller0516e4c2013-06-03 18:07:48 -0700315def ensure_label_exists(name):
316 """
317 Ensure that a label called |name| exists in the autotest DB.
318
319 @param name: the label to check for/create.
320 @raises ValidationError: There was an error in the response that was
321 not because the label already existed.
322
323 """
324 afe = frontend.AFE()
325 try:
326 afe.create_label(name=name)
327 except proxy.ValidationError as ve:
328 if ('name' in ve.problem_keys and
329 'This value must be unique' in ve.problem_keys['name']):
330 logging.debug('Version label %s already exists', name)
331 else:
332 raise ve