blob: 44f2b1dd5b8b8ec057e1ac60b6ff057db59ac323 [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
81class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -070082 """
Alex Miller1968edf2014-02-27 18:11:36 -080083 Tests to verify that the DUT is in a sane, known good state that we can run
84 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -070085 """
Alex Miller1968edf2014-02-27 18:11:36 -080086
87 _actions = {
harpreetafdf4d92014-04-04 12:03:50 -070088 'modem_repair': 'cellular_StaleModemReboot'
Alex Miller1968edf2014-02-27 18:11:36 -080089 }
90
91 name = 'verify'
92
93
94class Provision(_SpecialTaskAction):
95 """
96 Provisioning runs to change the configuration of the DUT from one state to
97 another. It will only be run on verified DUTs.
98 """
99
100 # TODO(milleral): http://crbug.com/249555
101 # Create some way to discover and register provisioning tests so that we
102 # don't need to hand-maintain a list of all of them.
103 _actions = {
104 CROS_VERSION_PREFIX: 'provision_AutoUpdate',
105 FW_VERSION_PREFIX: 'provision_FirmwareUpdate',
106 }
107
108 name = 'provision'
109
110
111class Cleanup(_SpecialTaskAction):
112 """
113 Cleanup runs after a test fails to try and remove artifacts of tests and
114 ensure the DUT will be in a sane state for the next test run.
115 """
116
117 _actions = {
118 }
119
120 name = 'cleanup'
121
122
123class Repair(_SpecialTaskAction):
124 """
125 Repair runs when one of the other special tasks fails. It should be able
126 to take a component of the DUT that's in an unknown state and restore it to
127 a good state.
128 """
129
130 _actions = {
131 }
132
133 name = 'repair'
134
135
136# For backwards compatibility with old control files, we still need the
137# following:
138
139can_provision = Provision.acts_on
140provisioner_for = Provision.test_for
Alex Miller0516e4c2013-06-03 18:07:48 -0700141
142
143def filter_labels(labels):
144 """
145 Filter a list of labels into two sets: those labels that we know how to
146 change and those that we don't. For the ones we know how to change, split
147 them apart into the name of configuration type and its value.
148
149 @param labels: A list of strings of labels.
150 @returns: A tuple where the first element is a set of unprovisionable
151 labels, and the second element is a set of the provisionable
152 labels.
153
154 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
155 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
156
157 """
158 capabilities = set()
159 configurations = set()
160
161 for label in labels:
162 if can_provision(label):
163 configurations.add(label)
164 else:
165 capabilities.add(label)
166
167 return capabilities, configurations
168
169
170def split_labels(labels):
171 """
172 Split a list of labels into a dict mapping name to value. All labels must
173 be provisionable labels, or else a ValueError
174
175 @param labels: list of strings of label names
176 @returns: A dict of where the key is the configuration name, and the value
177 is the configuration value.
178 @raises: ValueError if a label is not a provisionable label.
179
180 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
181 {'cros-version': 'lumpy-release/R28-3993.0.0'}
182 >>> split_labels(['bluetooth'])
183 Traceback (most recent call last):
184 ...
185 ValueError: Unprovisionable label bluetooth
186
187 """
188 configurations = dict()
189
190 for label in labels:
191 if can_provision(label):
192 name, value = label.split(':', 1)
193 configurations[name] = value
194 else:
195 raise ValueError('Unprovisionable label %s' % label)
196
197 return configurations
198
199
Alex Miller2229c9c2013-08-27 15:20:39 -0700200def join(provision_type, provision_value):
201 """
202 Combine the provision type and value into the label name.
203
204 @param provision_type: One of the constants that are the label prefixes.
205 @param provision_value: A string of the value for this provision type.
206 @returns: A string that is the label name for this (type, value) pair.
207
208 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
209 'cros-version:lumpy-release/R27-3773.0.0'
210
211 """
212 return '%s:%s' % (provision_type, provision_value)
213
214
Alex Miller667b5f22014-02-28 15:33:39 -0800215class SpecialTaskActionException(Exception):
216 """
217 Exception raised when a special task fails to successfully run a test that
218 is required.
219
220 This is also a literally meaningless exception. It's always just discarded.
221 """
222
223
224def run_special_task_actions(job, host, labels, task):
225 """
226 Iterate through all `label`s and run any tests on `host` that `task` has
227 corresponding to the passed in labels.
228
229 Emits status lines for each run test, and INFO lines for each skipped label.
230
231 @param job: A job object from a control file.
232 @param host: The host to run actions on.
233 @param labels: The list of job labels to work on.
234 @param task: An instance of _SpecialTaskAction.
235 @returns: None
236 @raises: SpecialTaskActionException if a test fails.
237
238 """
239 capabilities, configuration = filter_labels(labels)
240
241 for label in capabilities:
242 if task.acts_on(label):
243 test = task.test_for(label)
244 success = job.run_test(test, host=host)
245 if not success:
246 raise SpecialTaskActionException()
247 else:
248 job.record('INFO', None, task.name,
249 "Can't %s label '%s'." % (task.name, label))
250
251 for name, value in split_labels(configuration).items():
252 if task.acts_on(name):
253 test = task.test_for(name)
254 success = job.run_test(test, host=host, value=value)
255 if not success:
256 raise SpecialTaskActionException()
257 else:
258 job.record('INFO', None, task.name,
259 "Can't %s label '%s:%s'." % (task.name, name, value))
260
261
Alex Miller2229c9c2013-08-27 15:20:39 -0700262# This has been copied out of dynamic_suite's reimager.py, which no longer
263# exists. I'd prefer if this would go away by doing http://crbug.com/249424,
264# so that labels are just automatically made when we try to add them to a host.
Alex Miller0516e4c2013-06-03 18:07:48 -0700265def ensure_label_exists(name):
266 """
267 Ensure that a label called |name| exists in the autotest DB.
268
269 @param name: the label to check for/create.
270 @raises ValidationError: There was an error in the response that was
271 not because the label already existed.
272
273 """
274 afe = frontend.AFE()
275 try:
276 afe.create_label(name=name)
277 except proxy.ValidationError as ve:
278 if ('name' in ve.problem_keys and
279 'This value must be unique' in ve.problem_keys['name']):
280 logging.debug('Version label %s already exists', name)
281 else:
282 raise ve