blob: b8dd8ede64d11757e691e47008bed1cecf2c8745 [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
Fang Dengaf30e7c2014-11-15 13:57:03 -080012from autotest_lib.server.cros import provision_actionables as actionables
Alex Miller0516e4c2013-06-03 18:07:48 -070013
14
15### Constants for label prefixes
16CROS_VERSION_PREFIX = 'cros-version'
Fang Dengdbc86322013-08-09 16:17:30 -070017FW_VERSION_PREFIX = 'fw-version'
Alex Miller0516e4c2013-06-03 18:07:48 -070018
19
20### Helpers to convert value to label
21def cros_version_to_label(image):
22 """
23 Returns the proper label name for a ChromeOS build of |image|.
24
25 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
26 @returns: A string that is the appropriate label name.
27
28 """
29 return CROS_VERSION_PREFIX + ':' + image
30
31
Dan Shi9cb0eec2014-06-03 09:04:50 -070032def fw_version_to_label(image):
33 """
34 Returns the proper label name for a firmware build of |image|.
35
36 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
37 @returns: A string that is the appropriate label name.
38
39 """
40 return FW_VERSION_PREFIX + ':' + image
41
42
Alex Miller1968edf2014-02-27 18:11:36 -080043class _SpecialTaskAction(object):
Alex Miller0516e4c2013-06-03 18:07:48 -070044 """
Alex Miller1968edf2014-02-27 18:11:36 -080045 Base class to give a template for mapping labels to tests.
Alex Miller0516e4c2013-06-03 18:07:48 -070046 """
Alex Miller1968edf2014-02-27 18:11:36 -080047
48 __metaclass__ = abc.ABCMeta
Alex Miller0516e4c2013-06-03 18:07:48 -070049
50
Alex Miller1968edf2014-02-27 18:11:36 -080051 # One cannot do
52 # @abc.abstractproperty
53 # _actions = {}
54 # so this is the next best thing
55 @abc.abstractproperty
56 def _actions(self):
57 """A dictionary mapping labels to test names."""
58 pass
59
60
61 @abc.abstractproperty
62 def name(self):
63 """The name of this special task to be used in output."""
64 pass
65
66
67 @classmethod
68 def acts_on(cls, label):
69 """
70 Returns True if the label is a label that we recognize as something we
71 know how to act on, given our _actions.
72
73 @param label: The label as a string.
74 @returns: True if there exists a test to run for this label.
75
76 """
77 return label.split(':')[0] in cls._actions
78
79
80 @classmethod
81 def test_for(cls, label):
82 """
83 Returns the test associated with the given (string) label name.
84
85 @param label: The label for which the action is being requested.
86 @returns: The string name of the test that should be run.
87 @raises KeyError: If the name was not recognized as one we care about.
88
89 """
90 return cls._actions[label]
91
92
Alex Milleraa772002014-04-10 17:51:21 -070093 @classmethod
94 def partition(cls, labels):
95 """
96 Filter a list of labels into two sets: those labels that we know how to
97 act on and those that we don't know how to act on.
98
99 @param labels: A list of strings of labels.
100 @returns: A tuple where the first element is a set of unactionable
101 labels, and the second element is a set of the actionable
102 labels.
103 """
104 capabilities = set()
105 configurations = set()
106
107 for label in labels:
108 if cls.acts_on(label):
109 configurations.add(label)
110 else:
111 capabilities.add(label)
112
113 return capabilities, configurations
114
115
Alex Miller1968edf2014-02-27 18:11:36 -0800116class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -0700117 """
Alex Miller1968edf2014-02-27 18:11:36 -0800118 Tests to verify that the DUT is in a sane, known good state that we can run
119 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -0700120 """
Alex Miller1968edf2014-02-27 18:11:36 -0800121
122 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800123 'modem_repair': actionables.TestActionable('cellular_StaleModemReboot'),
Dan Shid2b82f32014-10-24 13:39:19 -0700124 # TODO(crbug.com/404421): set rpm action to power_RPMTest after the RPM
125 # is stable in lab (destiny). The power_RPMTest failure led to reset job
126 # failure and that left dut in Repair Failed. Since the test will fail
127 # anyway due to the destiny lab issue, and test retry will retry the
128 # test in another DUT.
129 # This change temporarily disable the RPM check in reset job.
130 # Another way to do this is to remove rpm dependency from tests' control
131 # file. That will involve changes on multiple control files. This one
132 # line change here is a simple temporary fix.
Fang Dengaf30e7c2014-11-15 13:57:03 -0800133 'rpm': actionables.TestActionable('dummy_PassServer'),
Alex Miller1968edf2014-02-27 18:11:36 -0800134 }
135
136 name = 'verify'
137
138
139class Provision(_SpecialTaskAction):
140 """
141 Provisioning runs to change the configuration of the DUT from one state to
142 another. It will only be run on verified DUTs.
143 """
144
145 # TODO(milleral): http://crbug.com/249555
146 # Create some way to discover and register provisioning tests so that we
147 # don't need to hand-maintain a list of all of them.
148 _actions = {
Fang Deng9d548742015-02-03 11:35:02 -0800149 CROS_VERSION_PREFIX: actionables.TestActionable(
150 'provision_AutoUpdate',
151 extra_kwargs={'disable_sysinfo': False,
152 'disable_before_test_sysinfo': False,
153 'disable_before_iteration_sysinfo': True,
154 'disable_after_test_sysinfo': True,
155 'disable_after_iteration_sysinfo': True}),
Fang Dengaf30e7c2014-11-15 13:57:03 -0800156 FW_VERSION_PREFIX: actionables.TestActionable(
157 'provision_FirmwareUpdate'),
Alex Miller1968edf2014-02-27 18:11:36 -0800158 }
159
160 name = 'provision'
161
162
163class Cleanup(_SpecialTaskAction):
164 """
165 Cleanup runs after a test fails to try and remove artifacts of tests and
166 ensure the DUT will be in a sane state for the next test run.
167 """
168
169 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800170 'cleanup-reboot': actionables.RebootActionable(),
Alex Miller1968edf2014-02-27 18:11:36 -0800171 }
172
173 name = 'cleanup'
174
175
176class Repair(_SpecialTaskAction):
177 """
178 Repair runs when one of the other special tasks fails. It should be able
179 to take a component of the DUT that's in an unknown state and restore it to
180 a good state.
181 """
182
183 _actions = {
184 }
185
186 name = 'repair'
187
188
Alex Miller1968edf2014-02-27 18:11:36 -0800189
Alex Milleraa772002014-04-10 17:51:21 -0700190# TODO(milleral): crbug.com/364273
191# Label doesn't really mean label in this context. We're putting things into
192# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
193# doing that.
194def is_for_special_action(label):
195 """
196 If any special task handles the label specially, then we're using the label
197 to communicate that we want an action, and not as an actual dependency that
198 the test has.
199
200 @param label: A string label name.
201 @return True if any special task handles this label specially,
202 False if no special task handles this label.
203 """
204 return (Verify.acts_on(label) or
205 Provision.acts_on(label) or
206 Cleanup.acts_on(label) or
207 Repair.acts_on(label))
Alex Miller0516e4c2013-06-03 18:07:48 -0700208
209
210def filter_labels(labels):
211 """
212 Filter a list of labels into two sets: those labels that we know how to
213 change and those that we don't. For the ones we know how to change, split
214 them apart into the name of configuration type and its value.
215
216 @param labels: A list of strings of labels.
217 @returns: A tuple where the first element is a set of unprovisionable
218 labels, and the second element is a set of the provisionable
219 labels.
220
221 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
222 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
223
224 """
Alex Milleraa772002014-04-10 17:51:21 -0700225 return Provision.partition(labels)
Alex Miller0516e4c2013-06-03 18:07:48 -0700226
227
228def split_labels(labels):
229 """
230 Split a list of labels into a dict mapping name to value. All labels must
231 be provisionable labels, or else a ValueError
232
233 @param labels: list of strings of label names
234 @returns: A dict of where the key is the configuration name, and the value
235 is the configuration value.
236 @raises: ValueError if a label is not a provisionable label.
237
238 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
239 {'cros-version': 'lumpy-release/R28-3993.0.0'}
240 >>> split_labels(['bluetooth'])
241 Traceback (most recent call last):
242 ...
243 ValueError: Unprovisionable label bluetooth
244
245 """
246 configurations = dict()
247
248 for label in labels:
Alex Milleraa772002014-04-10 17:51:21 -0700249 if Provision.acts_on(label):
Alex Miller0516e4c2013-06-03 18:07:48 -0700250 name, value = label.split(':', 1)
251 configurations[name] = value
252 else:
253 raise ValueError('Unprovisionable label %s' % label)
254
255 return configurations
256
257
Alex Miller2229c9c2013-08-27 15:20:39 -0700258def join(provision_type, provision_value):
259 """
260 Combine the provision type and value into the label name.
261
262 @param provision_type: One of the constants that are the label prefixes.
263 @param provision_value: A string of the value for this provision type.
264 @returns: A string that is the label name for this (type, value) pair.
265
266 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
267 'cros-version:lumpy-release/R27-3773.0.0'
268
269 """
270 return '%s:%s' % (provision_type, provision_value)
271
272
Alex Miller667b5f22014-02-28 15:33:39 -0800273class SpecialTaskActionException(Exception):
274 """
275 Exception raised when a special task fails to successfully run a test that
276 is required.
277
278 This is also a literally meaningless exception. It's always just discarded.
279 """
280
281
282def run_special_task_actions(job, host, labels, task):
283 """
284 Iterate through all `label`s and run any tests on `host` that `task` has
285 corresponding to the passed in labels.
286
287 Emits status lines for each run test, and INFO lines for each skipped label.
288
289 @param job: A job object from a control file.
290 @param host: The host to run actions on.
291 @param labels: The list of job labels to work on.
292 @param task: An instance of _SpecialTaskAction.
293 @returns: None
294 @raises: SpecialTaskActionException if a test fails.
295
296 """
297 capabilities, configuration = filter_labels(labels)
298
299 for label in capabilities:
300 if task.acts_on(label):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800301 action_item = task.test_for(label)
302 success = action_item.execute(job=job, host=host)
Alex Miller667b5f22014-02-28 15:33:39 -0800303 if not success:
304 raise SpecialTaskActionException()
305 else:
306 job.record('INFO', None, task.name,
307 "Can't %s label '%s'." % (task.name, label))
308
309 for name, value in split_labels(configuration).items():
310 if task.acts_on(name):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800311 action_item = task.test_for(name)
312 success = action_item.execute(job=job, host=host, value=value)
Alex Miller667b5f22014-02-28 15:33:39 -0800313 if not success:
314 raise SpecialTaskActionException()
315 else:
316 job.record('INFO', None, task.name,
317 "Can't %s label '%s:%s'." % (task.name, name, value))
318
319
Alex Miller2229c9c2013-08-27 15:20:39 -0700320# This has been copied out of dynamic_suite's reimager.py, which no longer
321# exists. I'd prefer if this would go away by doing http://crbug.com/249424,
322# so that labels are just automatically made when we try to add them to a host.
Alex Miller0516e4c2013-06-03 18:07:48 -0700323def ensure_label_exists(name):
324 """
325 Ensure that a label called |name| exists in the autotest DB.
326
327 @param name: the label to check for/create.
328 @raises ValidationError: There was an error in the response that was
329 not because the label already existed.
330
331 """
332 afe = frontend.AFE()
333 try:
334 afe.create_label(name=name)
335 except proxy.ValidationError as ve:
336 if ('name' in ve.problem_keys and
337 'This value must be unique' in ve.problem_keys['name']):
338 logging.debug('Version label %s already exists', name)
339 else:
340 raise ve