blob: ac2800c07215bd0b708619a54a05a6e7d6423b6c [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
Dan Shie44f9c02016-02-18 13:25:05 -080018# Special label to skip provision and run reset instead.
19SKIP_PROVISION = 'skip_provision'
20
Chris Sosae92399e2015-04-24 11:32:59 -070021# Default number of provisions attempts to try if we believe the devserver is
22# flaky.
23FLAKY_DEVSERVER_ATTEMPTS = 2
24
Alex Miller0516e4c2013-06-03 18:07:48 -070025
26### Helpers to convert value to label
27def cros_version_to_label(image):
28 """
29 Returns the proper label name for a ChromeOS build of |image|.
30
31 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
32 @returns: A string that is the appropriate label name.
33
34 """
35 return CROS_VERSION_PREFIX + ':' + image
36
37
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +080038def fwro_version_to_label(image):
Dan Shi9cb0eec2014-06-03 09:04:50 -070039 """
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +080040 Returns the proper label name for a RO firmware build of |image|.
41
42 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
43 @returns: A string that is the appropriate label name.
44
45 """
46 return FW_RO_VERSION_PREFIX + ':' + image
47
48
49def fwrw_version_to_label(image):
50 """
51 Returns the proper label name for a RW firmware build of |image|.
Dan Shi9cb0eec2014-06-03 09:04:50 -070052
53 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
54 @returns: A string that is the appropriate label name.
55
56 """
Dan Shi0723bf52015-06-24 10:52:38 -070057 return FW_RW_VERSION_PREFIX + ':' + image
Dan Shi9cb0eec2014-06-03 09:04:50 -070058
59
Alex Miller1968edf2014-02-27 18:11:36 -080060class _SpecialTaskAction(object):
Alex Miller0516e4c2013-06-03 18:07:48 -070061 """
Alex Miller1968edf2014-02-27 18:11:36 -080062 Base class to give a template for mapping labels to tests.
Alex Miller0516e4c2013-06-03 18:07:48 -070063 """
Alex Miller1968edf2014-02-27 18:11:36 -080064
65 __metaclass__ = abc.ABCMeta
Alex Miller0516e4c2013-06-03 18:07:48 -070066
67
Alex Miller1968edf2014-02-27 18:11:36 -080068 # One cannot do
69 # @abc.abstractproperty
70 # _actions = {}
71 # so this is the next best thing
72 @abc.abstractproperty
73 def _actions(self):
74 """A dictionary mapping labels to test names."""
75 pass
76
77
78 @abc.abstractproperty
79 def name(self):
80 """The name of this special task to be used in output."""
81 pass
82
83
84 @classmethod
85 def acts_on(cls, label):
86 """
87 Returns True if the label is a label that we recognize as something we
88 know how to act on, given our _actions.
89
90 @param label: The label as a string.
91 @returns: True if there exists a test to run for this label.
92
93 """
94 return label.split(':')[0] in cls._actions
95
96
97 @classmethod
98 def test_for(cls, label):
99 """
100 Returns the test associated with the given (string) label name.
101
102 @param label: The label for which the action is being requested.
103 @returns: The string name of the test that should be run.
104 @raises KeyError: If the name was not recognized as one we care about.
105
106 """
107 return cls._actions[label]
108
109
Alex Milleraa772002014-04-10 17:51:21 -0700110 @classmethod
111 def partition(cls, labels):
112 """
113 Filter a list of labels into two sets: those labels that we know how to
114 act on and those that we don't know how to act on.
115
116 @param labels: A list of strings of labels.
117 @returns: A tuple where the first element is a set of unactionable
118 labels, and the second element is a set of the actionable
119 labels.
120 """
121 capabilities = set()
122 configurations = set()
123
124 for label in labels:
Dan Shie44f9c02016-02-18 13:25:05 -0800125 if label == SKIP_PROVISION:
126 # skip_provision is neither actionable or a capability label.
127 # It doesn't need any handling.
128 continue
129 elif cls.acts_on(label):
Alex Milleraa772002014-04-10 17:51:21 -0700130 configurations.add(label)
131 else:
132 capabilities.add(label)
133
134 return capabilities, configurations
135
136
Alex Miller1968edf2014-02-27 18:11:36 -0800137class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -0700138 """
Alex Miller1968edf2014-02-27 18:11:36 -0800139 Tests to verify that the DUT is in a sane, known good state that we can run
140 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -0700141 """
Alex Miller1968edf2014-02-27 18:11:36 -0800142
143 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800144 'modem_repair': actionables.TestActionable('cellular_StaleModemReboot'),
Don Garrett6f7e8002015-07-23 22:45:37 +0000145 # TODO(crbug.com/404421): set rpm action to power_RPMTest after the RPM
146 # is stable in lab (destiny). The power_RPMTest failure led to reset job
147 # failure and that left dut in Repair Failed. Since the test will fail
148 # anyway due to the destiny lab issue, and test retry will retry the
149 # test in another DUT.
150 # This change temporarily disable the RPM check in reset job.
151 # Another way to do this is to remove rpm dependency from tests' control
152 # file. That will involve changes on multiple control files. This one
153 # line change here is a simple temporary fix.
154 'rpm': actionables.TestActionable('dummy_PassServer'),
Alex Miller1968edf2014-02-27 18:11:36 -0800155 }
156
157 name = 'verify'
158
159
160class Provision(_SpecialTaskAction):
161 """
162 Provisioning runs to change the configuration of the DUT from one state to
163 another. It will only be run on verified DUTs.
164 """
165
166 # TODO(milleral): http://crbug.com/249555
167 # Create some way to discover and register provisioning tests so that we
168 # don't need to hand-maintain a list of all of them.
169 _actions = {
Fang Deng9d548742015-02-03 11:35:02 -0800170 CROS_VERSION_PREFIX: actionables.TestActionable(
171 'provision_AutoUpdate',
172 extra_kwargs={'disable_sysinfo': False,
173 'disable_before_test_sysinfo': False,
174 'disable_before_iteration_sysinfo': True,
175 'disable_after_test_sysinfo': True,
176 'disable_after_iteration_sysinfo': True}),
Tom Wai-Hong Tam9a237612016-01-08 03:41:46 +0800177 FW_RO_VERSION_PREFIX: actionables.TestActionable(
Fang Dengaf30e7c2014-11-15 13:57:03 -0800178 'provision_FirmwareUpdate'),
Tom Wai-Hong Tam9a237612016-01-08 03:41:46 +0800179 FW_RW_VERSION_PREFIX: actionables.TestActionable(
180 'provision_FirmwareUpdate',
Dan Shi61e407c2016-04-08 14:21:07 -0700181 extra_kwargs={'rw_only': True,
182 'tag': 'rw_only'}),
Simran Basi5ace6f22016-01-06 17:30:44 -0800183 ANDROID_BUILD_VERSION_PREFIX : actionables.TestActionable(
184 'provision_AndroidUpdate'),
Alex Miller1968edf2014-02-27 18:11:36 -0800185 }
186
187 name = 'provision'
188
189
190class Cleanup(_SpecialTaskAction):
191 """
192 Cleanup runs after a test fails to try and remove artifacts of tests and
193 ensure the DUT will be in a sane state for the next test run.
194 """
195
196 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800197 'cleanup-reboot': actionables.RebootActionable(),
Alex Miller1968edf2014-02-27 18:11:36 -0800198 }
199
200 name = 'cleanup'
201
202
203class Repair(_SpecialTaskAction):
204 """
205 Repair runs when one of the other special tasks fails. It should be able
206 to take a component of the DUT that's in an unknown state and restore it to
207 a good state.
208 """
209
210 _actions = {
211 }
212
213 name = 'repair'
214
215
Alex Miller1968edf2014-02-27 18:11:36 -0800216
Alex Milleraa772002014-04-10 17:51:21 -0700217# TODO(milleral): crbug.com/364273
218# Label doesn't really mean label in this context. We're putting things into
219# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
220# doing that.
221def is_for_special_action(label):
222 """
223 If any special task handles the label specially, then we're using the label
224 to communicate that we want an action, and not as an actual dependency that
225 the test has.
226
227 @param label: A string label name.
228 @return True if any special task handles this label specially,
229 False if no special task handles this label.
230 """
231 return (Verify.acts_on(label) or
232 Provision.acts_on(label) or
233 Cleanup.acts_on(label) or
Dan Shie44f9c02016-02-18 13:25:05 -0800234 Repair.acts_on(label) or
235 label == SKIP_PROVISION)
Alex Miller0516e4c2013-06-03 18:07:48 -0700236
237
238def filter_labels(labels):
239 """
240 Filter a list of labels into two sets: those labels that we know how to
241 change and those that we don't. For the ones we know how to change, split
242 them apart into the name of configuration type and its value.
243
244 @param labels: A list of strings of labels.
245 @returns: A tuple where the first element is a set of unprovisionable
246 labels, and the second element is a set of the provisionable
247 labels.
248
249 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0'])
250 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0']))
251
252 """
Alex Milleraa772002014-04-10 17:51:21 -0700253 return Provision.partition(labels)
Alex Miller0516e4c2013-06-03 18:07:48 -0700254
255
256def split_labels(labels):
257 """
258 Split a list of labels into a dict mapping name to value. All labels must
259 be provisionable labels, or else a ValueError
260
261 @param labels: list of strings of label names
262 @returns: A dict of where the key is the configuration name, and the value
263 is the configuration value.
264 @raises: ValueError if a label is not a provisionable label.
265
266 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0'])
267 {'cros-version': 'lumpy-release/R28-3993.0.0'}
268 >>> split_labels(['bluetooth'])
269 Traceback (most recent call last):
270 ...
271 ValueError: Unprovisionable label bluetooth
272
273 """
274 configurations = dict()
275
276 for label in labels:
Alex Milleraa772002014-04-10 17:51:21 -0700277 if Provision.acts_on(label):
Alex Miller0516e4c2013-06-03 18:07:48 -0700278 name, value = label.split(':', 1)
279 configurations[name] = value
280 else:
281 raise ValueError('Unprovisionable label %s' % label)
282
283 return configurations
284
285
Alex Miller2229c9c2013-08-27 15:20:39 -0700286def join(provision_type, provision_value):
287 """
288 Combine the provision type and value into the label name.
289
290 @param provision_type: One of the constants that are the label prefixes.
291 @param provision_value: A string of the value for this provision type.
292 @returns: A string that is the label name for this (type, value) pair.
293
294 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
295 'cros-version:lumpy-release/R27-3773.0.0'
296
297 """
298 return '%s:%s' % (provision_type, provision_value)
299
300
Alex Miller667b5f22014-02-28 15:33:39 -0800301class SpecialTaskActionException(Exception):
302 """
303 Exception raised when a special task fails to successfully run a test that
304 is required.
305
306 This is also a literally meaningless exception. It's always just discarded.
307 """
308
309
310def run_special_task_actions(job, host, labels, task):
311 """
312 Iterate through all `label`s and run any tests on `host` that `task` has
313 corresponding to the passed in labels.
314
315 Emits status lines for each run test, and INFO lines for each skipped label.
316
317 @param job: A job object from a control file.
318 @param host: The host to run actions on.
319 @param labels: The list of job labels to work on.
320 @param task: An instance of _SpecialTaskAction.
321 @returns: None
322 @raises: SpecialTaskActionException if a test fails.
323
324 """
325 capabilities, configuration = filter_labels(labels)
326
327 for label in capabilities:
328 if task.acts_on(label):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800329 action_item = task.test_for(label)
330 success = action_item.execute(job=job, host=host)
Alex Miller667b5f22014-02-28 15:33:39 -0800331 if not success:
332 raise SpecialTaskActionException()
333 else:
334 job.record('INFO', None, task.name,
335 "Can't %s label '%s'." % (task.name, label))
336
337 for name, value in split_labels(configuration).items():
338 if task.acts_on(name):
Fang Dengaf30e7c2014-11-15 13:57:03 -0800339 action_item = task.test_for(name)
340 success = action_item.execute(job=job, host=host, value=value)
Alex Miller667b5f22014-02-28 15:33:39 -0800341 if not success:
342 raise SpecialTaskActionException()
343 else:
344 job.record('INFO', None, task.name,
345 "Can't %s label '%s:%s'." % (task.name, name, value))