blob: f807717c68b58699a926e1db989918ff4808227c [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
Alex Miller1968edf2014-02-27 18:11:36 -080031class _SpecialTaskAction(object):
Alex Miller0516e4c2013-06-03 18:07:48 -070032 """
Alex Miller1968edf2014-02-27 18:11:36 -080033 Base class to give a template for mapping labels to tests.
Alex Miller0516e4c2013-06-03 18:07:48 -070034 """
Alex Miller1968edf2014-02-27 18:11:36 -080035
36 __metaclass__ = abc.ABCMeta
Alex Miller0516e4c2013-06-03 18:07:48 -070037
38
Alex Miller1968edf2014-02-27 18:11:36 -080039 # One cannot do
40 # @abc.abstractproperty
41 # _actions = {}
42 # so this is the next best thing
43 @abc.abstractproperty
44 def _actions(self):
45 """A dictionary mapping labels to test names."""
46 pass
47
48
49 @abc.abstractproperty
50 def name(self):
51 """The name of this special task to be used in output."""
52 pass
53
54
55 @classmethod
56 def acts_on(cls, label):
57 """
58 Returns True if the label is a label that we recognize as something we
59 know how to act on, given our _actions.
60
61 @param label: The label as a string.
62 @returns: True if there exists a test to run for this label.
63
64 """
65 return label.split(':')[0] in cls._actions
66
67
68 @classmethod
69 def test_for(cls, label):
70 """
71 Returns the test associated with the given (string) label name.
72
73 @param label: The label for which the action is being requested.
74 @returns: The string name of the test that should be run.
75 @raises KeyError: If the name was not recognized as one we care about.
76
77 """
78 return cls._actions[label]
79
80
Alex Milleraa772002014-04-10 17:51:21 -070081 @classmethod
82 def partition(cls, labels):
83 """
84 Filter a list of labels into two sets: those labels that we know how to
85 act on and those that we don't know how to act on.
86
87 @param labels: A list of strings of labels.
88 @returns: A tuple where the first element is a set of unactionable
89 labels, and the second element is a set of the actionable
90 labels.
91 """
92 capabilities = set()
93 configurations = set()
94
95 for label in labels:
96 if cls.acts_on(label):
97 configurations.add(label)
98 else:
99 capabilities.add(label)
100
101 return capabilities, configurations
102
103
Alex Miller1968edf2014-02-27 18:11:36 -0800104class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -0700105 """
Alex Miller1968edf2014-02-27 18:11:36 -0800106 Tests to verify that the DUT is in a sane, known good state that we can run
107 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -0700108 """
Alex Miller1968edf2014-02-27 18:11:36 -0800109
110 _actions = {
Derek Basehore9a1747a2014-05-06 16:50:12 -0700111 'modem_repair': 'cellular_StaleModemReboot',
112 'rpm': 'power_RPMTest',
Alex Miller1968edf2014-02-27 18:11:36 -0800113 }
114
115 name = 'verify'
116
117
118class Provision(_SpecialTaskAction):
119 """
120 Provisioning runs to change the configuration of the DUT from one state to
121 another. It will only be run on verified DUTs.
122 """
123
124 # TODO(milleral): http://crbug.com/249555
125 # Create some way to discover and register provisioning tests so that we
126 # don't need to hand-maintain a list of all of them.
127 _actions = {
128 CROS_VERSION_PREFIX: 'provision_AutoUpdate',
129 FW_VERSION_PREFIX: 'provision_FirmwareUpdate',
130 }
131
132 name = 'provision'
133
134
135class Cleanup(_SpecialTaskAction):
136 """
137 Cleanup runs after a test fails to try and remove artifacts of tests and
138 ensure the DUT will be in a sane state for the next test run.
139 """
140
141 _actions = {
Alex Millerf47db9c2014-04-18 18:36:36 -0700142 'cleanup-reboot': 'generic_RebootTest',
Alex Miller1968edf2014-02-27 18:11:36 -0800143 }
144
145 name = 'cleanup'
146
147
148class Repair(_SpecialTaskAction):
149 """
150 Repair runs when one of the other special tasks fails. It should be able
151 to take a component of the DUT that's in an unknown state and restore it to
152 a good state.
153 """
154
155 _actions = {
156 }
157
158 name = 'repair'
159
160
Alex Miller1968edf2014-02-27 18:11:36 -0800161
Alex Milleraa772002014-04-10 17:51:21 -0700162# TODO(milleral): crbug.com/364273
163# Label doesn't really mean label in this context. We're putting things into
164# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
165# doing that.
166def is_for_special_action(label):
167 """
168 If any special task handles the label specially, then we're using the label
169 to communicate that we want an action, and not as an actual dependency that
170 the test has.
171
172 @param label: A string label name.
173 @return True if any special task handles this label specially,
174 False if no special task handles this label.
175 """
176 return (Verify.acts_on(label) or
177 Provision.acts_on(label) or
178 Cleanup.acts_on(label) or
179 Repair.acts_on(label))
Alex Miller0516e4c2013-06-03 18:07:48 -0700180
181
182def filter_labels(labels):
183 """
184 Filter a list of labels into two sets: those labels that we know how to
185 change and those that we don't. For the ones we know how to change, split
186 them apart into the name of configuration type and its value.
187
188 @param labels: A list of strings of labels.
189 @returns: A tuple where the first element is a set of unprovisionable
190 labels, and the second element is a set of the provisionable
191 labels.
192
193 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
194 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
195
196 """
Alex Milleraa772002014-04-10 17:51:21 -0700197 return Provision.partition(labels)
Alex Miller0516e4c2013-06-03 18:07:48 -0700198
199
200def split_labels(labels):
201 """
202 Split a list of labels into a dict mapping name to value. All labels must
203 be provisionable labels, or else a ValueError
204
205 @param labels: list of strings of label names
206 @returns: A dict of where the key is the configuration name, and the value
207 is the configuration value.
208 @raises: ValueError if a label is not a provisionable label.
209
210 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
211 {'cros-version': 'lumpy-release/R28-3993.0.0'}
212 >>> split_labels(['bluetooth'])
213 Traceback (most recent call last):
214 ...
215 ValueError: Unprovisionable label bluetooth
216
217 """
218 configurations = dict()
219
220 for label in labels:
Alex Milleraa772002014-04-10 17:51:21 -0700221 if Provision.acts_on(label):
Alex Miller0516e4c2013-06-03 18:07:48 -0700222 name, value = label.split(':', 1)
223 configurations[name] = value
224 else:
225 raise ValueError('Unprovisionable label %s' % label)
226
227 return configurations
228
229
Alex Miller2229c9c2013-08-27 15:20:39 -0700230def join(provision_type, provision_value):
231 """
232 Combine the provision type and value into the label name.
233
234 @param provision_type: One of the constants that are the label prefixes.
235 @param provision_value: A string of the value for this provision type.
236 @returns: A string that is the label name for this (type, value) pair.
237
238 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
239 'cros-version:lumpy-release/R27-3773.0.0'
240
241 """
242 return '%s:%s' % (provision_type, provision_value)
243
244
Alex Miller667b5f22014-02-28 15:33:39 -0800245class SpecialTaskActionException(Exception):
246 """
247 Exception raised when a special task fails to successfully run a test that
248 is required.
249
250 This is also a literally meaningless exception. It's always just discarded.
251 """
252
253
254def run_special_task_actions(job, host, labels, task):
255 """
256 Iterate through all `label`s and run any tests on `host` that `task` has
257 corresponding to the passed in labels.
258
259 Emits status lines for each run test, and INFO lines for each skipped label.
260
261 @param job: A job object from a control file.
262 @param host: The host to run actions on.
263 @param labels: The list of job labels to work on.
264 @param task: An instance of _SpecialTaskAction.
265 @returns: None
266 @raises: SpecialTaskActionException if a test fails.
267
268 """
269 capabilities, configuration = filter_labels(labels)
270
271 for label in capabilities:
272 if task.acts_on(label):
273 test = task.test_for(label)
274 success = job.run_test(test, host=host)
275 if not success:
276 raise SpecialTaskActionException()
277 else:
278 job.record('INFO', None, task.name,
279 "Can't %s label '%s'." % (task.name, label))
280
281 for name, value in split_labels(configuration).items():
282 if task.acts_on(name):
283 test = task.test_for(name)
284 success = job.run_test(test, host=host, value=value)
285 if not success:
286 raise SpecialTaskActionException()
287 else:
288 job.record('INFO', None, task.name,
289 "Can't %s label '%s:%s'." % (task.name, name, value))
290
291
Alex Miller2229c9c2013-08-27 15:20:39 -0700292# This has been copied out of dynamic_suite's reimager.py, which no longer
293# exists. I'd prefer if this would go away by doing http://crbug.com/249424,
294# so that labels are just automatically made when we try to add them to a host.
Alex Miller0516e4c2013-06-03 18:07:48 -0700295def ensure_label_exists(name):
296 """
297 Ensure that a label called |name| exists in the autotest DB.
298
299 @param name: the label to check for/create.
300 @raises ValidationError: There was an error in the response that was
301 not because the label already existed.
302
303 """
304 afe = frontend.AFE()
305 try:
306 afe.create_label(name=name)
307 except proxy.ValidationError as ve:
308 if ('name' in ve.problem_keys and
309 'This value must be unique' in ve.problem_keys['name']):
310 logging.debug('Version label %s already exists', name)
311 else:
312 raise ve