blob: c12f48fc319da1b9a51120ad4d00c13ed7fc9cbf [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 -07007
8import common
Fang Dengaf30e7c2014-11-15 13:57:03 -08009from autotest_lib.server.cros import provision_actionables as actionables
Alex Miller0516e4c2013-06-03 18:07:48 -070010
11
12### Constants for label prefixes
13CROS_VERSION_PREFIX = 'cros-version'
Simran Basi5ace6f22016-01-06 17:30:44 -080014ANDROID_BUILD_VERSION_PREFIX = 'ab-version'
Dan Shi0723bf52015-06-24 10:52:38 -070015FW_RW_VERSION_PREFIX = 'fwrw-version'
Dan Shi36cfd832014-10-10 13:38:51 -070016FW_RO_VERSION_PREFIX = 'fwro-version'
Alex Miller0516e4c2013-06-03 18:07:48 -070017
Chris Sosae92399e2015-04-24 11:32:59 -070018# Default number of provisions attempts to try if we believe the devserver is
19# flaky.
20FLAKY_DEVSERVER_ATTEMPTS = 2
21
Alex Miller0516e4c2013-06-03 18:07:48 -070022
23### Helpers to convert value to label
24def cros_version_to_label(image):
25 """
26 Returns the proper label name for a ChromeOS build of |image|.
27
28 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
29 @returns: A string that is the appropriate label name.
30
31 """
32 return CROS_VERSION_PREFIX + ':' + image
33
34
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +080035def fwro_version_to_label(image):
Dan Shi9cb0eec2014-06-03 09:04:50 -070036 """
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +080037 Returns the proper label name for a RO firmware build of |image|.
38
39 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
40 @returns: A string that is the appropriate label name.
41
42 """
43 return FW_RO_VERSION_PREFIX + ':' + image
44
45
46def fwrw_version_to_label(image):
47 """
48 Returns the proper label name for a RW firmware build of |image|.
Dan Shi9cb0eec2014-06-03 09:04:50 -070049
50 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
51 @returns: A string that is the appropriate label name.
52
53 """
Dan Shi0723bf52015-06-24 10:52:38 -070054 return FW_RW_VERSION_PREFIX + ':' + image
Dan Shi9cb0eec2014-06-03 09:04:50 -070055
56
Alex Miller1968edf2014-02-27 18:11:36 -080057class _SpecialTaskAction(object):
Alex Miller0516e4c2013-06-03 18:07:48 -070058 """
Alex Miller1968edf2014-02-27 18:11:36 -080059 Base class to give a template for mapping labels to tests.
Alex Miller0516e4c2013-06-03 18:07:48 -070060 """
Alex Miller1968edf2014-02-27 18:11:36 -080061
62 __metaclass__ = abc.ABCMeta
Alex Miller0516e4c2013-06-03 18:07:48 -070063
64
Alex Miller1968edf2014-02-27 18:11:36 -080065 # One cannot do
66 # @abc.abstractproperty
67 # _actions = {}
68 # so this is the next best thing
69 @abc.abstractproperty
70 def _actions(self):
71 """A dictionary mapping labels to test names."""
72 pass
73
74
75 @abc.abstractproperty
76 def name(self):
77 """The name of this special task to be used in output."""
78 pass
79
80
81 @classmethod
82 def acts_on(cls, label):
83 """
84 Returns True if the label is a label that we recognize as something we
85 know how to act on, given our _actions.
86
87 @param label: The label as a string.
88 @returns: True if there exists a test to run for this label.
89
90 """
91 return label.split(':')[0] in cls._actions
92
93
94 @classmethod
95 def test_for(cls, label):
96 """
97 Returns the test associated with the given (string) label name.
98
99 @param label: The label for which the action is being requested.
100 @returns: The string name of the test that should be run.
101 @raises KeyError: If the name was not recognized as one we care about.
102
103 """
104 return cls._actions[label]
105
106
Alex Milleraa772002014-04-10 17:51:21 -0700107 @classmethod
108 def partition(cls, labels):
109 """
110 Filter a list of labels into two sets: those labels that we know how to
111 act on and those that we don't know how to act on.
112
113 @param labels: A list of strings of labels.
114 @returns: A tuple where the first element is a set of unactionable
115 labels, and the second element is a set of the actionable
116 labels.
117 """
118 capabilities = set()
119 configurations = set()
120
121 for label in labels:
122 if cls.acts_on(label):
123 configurations.add(label)
124 else:
125 capabilities.add(label)
126
127 return capabilities, configurations
128
129
Alex Miller1968edf2014-02-27 18:11:36 -0800130class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -0700131 """
Alex Miller1968edf2014-02-27 18:11:36 -0800132 Tests to verify that the DUT is in a sane, known good state that we can run
133 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -0700134 """
Alex Miller1968edf2014-02-27 18:11:36 -0800135
136 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800137 'modem_repair': actionables.TestActionable('cellular_StaleModemReboot'),
Don Garrett6f7e8002015-07-23 22:45:37 +0000138 # TODO(crbug.com/404421): set rpm action to power_RPMTest after the RPM
139 # is stable in lab (destiny). The power_RPMTest failure led to reset job
140 # failure and that left dut in Repair Failed. Since the test will fail
141 # anyway due to the destiny lab issue, and test retry will retry the
142 # test in another DUT.
143 # This change temporarily disable the RPM check in reset job.
144 # Another way to do this is to remove rpm dependency from tests' control
145 # file. That will involve changes on multiple control files. This one
146 # line change here is a simple temporary fix.
147 'rpm': actionables.TestActionable('dummy_PassServer'),
Alex Miller1968edf2014-02-27 18:11:36 -0800148 }
149
150 name = 'verify'
151
152
153class Provision(_SpecialTaskAction):
154 """
155 Provisioning runs to change the configuration of the DUT from one state to
156 another. It will only be run on verified DUTs.
157 """
158
159 # TODO(milleral): http://crbug.com/249555
160 # Create some way to discover and register provisioning tests so that we
161 # don't need to hand-maintain a list of all of them.
162 _actions = {
Fang Deng9d548742015-02-03 11:35:02 -0800163 CROS_VERSION_PREFIX: actionables.TestActionable(
164 'provision_AutoUpdate',
165 extra_kwargs={'disable_sysinfo': False,
166 'disable_before_test_sysinfo': False,
167 'disable_before_iteration_sysinfo': True,
168 'disable_after_test_sysinfo': True,
169 'disable_after_iteration_sysinfo': True}),
Tom Wai-Hong Tam9a237612016-01-08 03:41:46 +0800170 FW_RO_VERSION_PREFIX: actionables.TestActionable(
Fang Dengaf30e7c2014-11-15 13:57:03 -0800171 'provision_FirmwareUpdate'),
Tom Wai-Hong Tam9a237612016-01-08 03:41:46 +0800172 FW_RW_VERSION_PREFIX: actionables.TestActionable(
173 'provision_FirmwareUpdate',
174 extra_kwargs={'rw_only': True}),
Simran Basi5ace6f22016-01-06 17:30:44 -0800175 ANDROID_BUILD_VERSION_PREFIX : actionables.TestActionable(
176 'provision_AndroidUpdate'),
Alex Miller1968edf2014-02-27 18:11:36 -0800177 }
178
179 name = 'provision'
180
181
182class Cleanup(_SpecialTaskAction):
183 """
184 Cleanup runs after a test fails to try and remove artifacts of tests and
185 ensure the DUT will be in a sane state for the next test run.
186 """
187
188 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800189 'cleanup-reboot': actionables.RebootActionable(),
Alex Miller1968edf2014-02-27 18:11:36 -0800190 }
191
192 name = 'cleanup'
193
194
195class Repair(_SpecialTaskAction):
196 """
197 Repair runs when one of the other special tasks fails. It should be able
198 to take a component of the DUT that's in an unknown state and restore it to
199 a good state.
200 """
201
202 _actions = {
203 }
204
205 name = 'repair'
206
207
Alex Miller1968edf2014-02-27 18:11:36 -0800208
Alex Milleraa772002014-04-10 17:51:21 -0700209# TODO(milleral): crbug.com/364273
210# Label doesn't really mean label in this context. We're putting things into
211# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
212# doing that.
213def is_for_special_action(label):
214 """
215 If any special task handles the label specially, then we're using the label
216 to communicate that we want an action, and not as an actual dependency that
217 the test has.
218
219 @param label: A string label name.
220 @return True if any special task handles this label specially,
221 False if no special task handles this label.
222 """
223 return (Verify.acts_on(label) or
224 Provision.acts_on(label) or
225 Cleanup.acts_on(label) or
226 Repair.acts_on(label))
Alex Miller0516e4c2013-06-03 18:07:48 -0700227
228
229def filter_labels(labels):
230 """
231 Filter a list of labels into two sets: those labels that we know how to
232 change and those that we don't. For the ones we know how to change, split
233 them apart into the name of configuration type and its value.
234
235 @param labels: A list of strings of labels.
236 @returns: A tuple where the first element is a set of unprovisionable
237 labels, and the second element is a set of the provisionable
238 labels.
239
240 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
241 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
242
243 """
Alex Milleraa772002014-04-10 17:51:21 -0700244 return Provision.partition(labels)
Alex Miller0516e4c2013-06-03 18:07:48 -0700245
246
247def split_labels(labels):
248 """
249 Split a list of labels into a dict mapping name to value. All labels must
250 be provisionable labels, or else a ValueError
251
252 @param labels: list of strings of label names
253 @returns: A dict of where the key is the configuration name, and the value
254 is the configuration value.
255 @raises: ValueError if a label is not a provisionable label.
256
257 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
258 {'cros-version': 'lumpy-release/R28-3993.0.0'}
259 >>> split_labels(['bluetooth'])
260 Traceback (most recent call last):
261 ...
262 ValueError: Unprovisionable label bluetooth
263
264 """
265 configurations = dict()
266
267 for label in labels:
Alex Milleraa772002014-04-10 17:51:21 -0700268 if Provision.acts_on(label):
Alex Miller0516e4c2013-06-03 18:07:48 -0700269 name, value = label.split(':', 1)
270 configurations[name] = value
271 else:
272 raise ValueError('Unprovisionable label %s' % label)
273
274 return configurations
275
276
Alex Miller2229c9c2013-08-27 15:20:39 -0700277def join(provision_type, provision_value):
278 """
279 Combine the provision type and value into the label name.
280
281 @param provision_type: One of the constants that are the label prefixes.
282 @param provision_value: A string of the value for this provision type.
283 @returns: A string that is the label name for this (type, value) pair.
284
285 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
286 'cros-version:lumpy-release/R27-3773.0.0'
287
288 """
289 return '%s:%s' % (provision_type, provision_value)
290
291
Alex Miller667b5f22014-02-28 15:33:39 -0800292class SpecialTaskActionException(Exception):
293 """
294 Exception raised when a special task fails to successfully run a test that
295 is required.
296
297 This is also a literally meaningless exception. It's always just discarded.
298 """
299
300
301def run_special_task_actions(job, host, labels, task):
302 """
303 Iterate through all `label`s and run any tests on `host` that `task` has
304 corresponding to the passed in labels.
305
306 Emits status lines for each run test, and INFO lines for each skipped label.
307
308 @param job: A job object from a control file.
309 @param host: The host to run actions on.
310 @param labels: The list of job labels to work on.
311 @param task: An instance of _SpecialTaskAction.
312 @returns: None
313 @raises: SpecialTaskActionException if a test fails.
314
315 """
316 capabilities, configuration = filter_labels(labels)
317
318 for label in capabilities:
319 if task.acts_on(label):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800320 action_item = task.test_for(label)
321 success = action_item.execute(job=job, host=host)
Alex Miller667b5f22014-02-28 15:33:39 -0800322 if not success:
323 raise SpecialTaskActionException()
324 else:
325 job.record('INFO', None, task.name,
326 "Can't %s label '%s'." % (task.name, label))
327
328 for name, value in split_labels(configuration).items():
329 if task.acts_on(name):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800330 action_item = task.test_for(name)
331 success = action_item.execute(job=job, host=host, value=value)
Alex Miller667b5f22014-02-28 15:33:39 -0800332 if not success:
333 raise SpecialTaskActionException()
334 else:
335 job.record('INFO', None, task.name,
336 "Can't %s label '%s:%s'." % (task.name, name, value))