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