blob: db73d1328ec357017c451ca0f1176c66340cb618 [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
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +00005
Richard Barnette6c2b70a2017-01-26 13:40:51 -08006import re
Dan Shi7279a5a2016-04-07 11:04:28 -07007import sys
Allen Liaaabda82017-02-21 18:27:38 -08008import warnings
Alex Miller0516e4c2013-06-03 18:07:48 -07009
10import common
Fang Dengaf30e7c2014-11-15 13:57:03 -080011from autotest_lib.server.cros import provision_actionables as actionables
Allen Li89711f72017-02-21 18:21:52 -080012from autotest_lib.utils import labellib
13from autotest_lib.utils.labellib import Key
Alex Miller0516e4c2013-06-03 18:07:48 -070014
15
16### Constants for label prefixes
Allen Li89711f72017-02-21 18:21:52 -080017CROS_VERSION_PREFIX = Key.CROS_VERSION
18ANDROID_BUILD_VERSION_PREFIX = Key.ANDROID_BUILD_VERSION
19TESTBED_BUILD_VERSION_PREFIX = Key.TESTBED_VERSION
20FW_RW_VERSION_PREFIX = Key.FIRMWARE_RW_VERSION
21FW_RO_VERSION_PREFIX = Key.FIRMWARE_RO_VERSION
Alex Miller0516e4c2013-06-03 18:07:48 -070022
Richard Barnette6c2b70a2017-01-26 13:40:51 -080023_ANDROID_BUILD_REGEX = r'.+/.+/P?([0-9]+|LATEST)'
24_ANDROID_TESTBED_BUILD_REGEX = _ANDROID_BUILD_REGEX + '(,|(#[0-9]+))'
25
Dan Shie44f9c02016-02-18 13:25:05 -080026# Special label to skip provision and run reset instead.
27SKIP_PROVISION = 'skip_provision'
28
Chris Sosae92399e2015-04-24 11:32:59 -070029# Default number of provisions attempts to try if we believe the devserver is
30# flaky.
31FLAKY_DEVSERVER_ATTEMPTS = 2
32
Alex Miller0516e4c2013-06-03 18:07:48 -070033
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +000034### Helpers to convert value to label
Richard Barnette6c2b70a2017-01-26 13:40:51 -080035def get_version_label_prefix(image):
36 """
37 Determine a version label prefix from a given image name.
38
39 Parses `image` to determine what kind of image it refers
40 to, and returns the corresponding version label prefix.
41
42 Known version label prefixes are:
43 * `CROS_VERSION_PREFIX` for Chrome OS version strings.
44 These images have names like `cave-release/R57-9030.0.0`.
45 * `ANDROID_BUILD_VERSION_PREFIX` for Android build versions
46 These images have names like
47 `git_mnc-release/shamu-userdebug/2457013`.
48 * `TESTBED_BUILD_VERSION_PREFIX` for Android testbed version
49 specifications. These are either comma separated lists of
50 Android versions, or an Android version with a suffix like
51 '#2', indicating two devices running the given build.
52
53 @param image: The image name to be parsed.
54 @returns: A string that is the prefix of version labels for the type
55 of image identified by `image`.
56
57 """
58 if re.match(_ANDROID_TESTBED_BUILD_REGEX, image, re.I):
59 return TESTBED_BUILD_VERSION_PREFIX
60 elif re.match(_ANDROID_BUILD_REGEX, image, re.I):
61 return ANDROID_BUILD_VERSION_PREFIX
62 else:
63 return CROS_VERSION_PREFIX
64
65
66def image_version_to_label(image):
67 """
68 Return a version label appropriate to the given image name.
69
70 The type of version label is as determined described for
71 `get_version_label_prefix()`, meaning the label will identify a
72 CrOS, Android, or Testbed version.
73
74 @param image: The image name to be parsed.
75 @returns: A string that is the appropriate label name.
76
77 """
78 return get_version_label_prefix(image) + ':' + image
79
80
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +000081def cros_version_to_label(image):
Alex Miller0516e4c2013-06-03 18:07:48 -070082 """
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +000083 Returns the proper label name for a ChromeOS build of |image|.
Alex Miller0516e4c2013-06-03 18:07:48 -070084
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +000085 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
86 @returns: A string that is the appropriate label name.
Alex Miller0516e4c2013-06-03 18:07:48 -070087
88 """
Allen Liaaabda82017-02-21 18:27:38 -080089 warnings.warn('cros_version_to_label is deprecated', stacklevel=2)
90 keyval_label = labellib.KeyvalLabel(Key.CROS_VERSION, image)
91 return labellib.format_keyval_label(keyval_label)
Alex Miller0516e4c2013-06-03 18:07:48 -070092
93
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +000094def fwro_version_to_label(image):
Dan Shi9cb0eec2014-06-03 09:04:50 -070095 """
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +000096 Returns the proper label name for a RO firmware build of |image|.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +080097
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +000098 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
99 @returns: A string that is the appropriate label name.
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800100
101 """
Allen Liaaabda82017-02-21 18:27:38 -0800102 warnings.warn('fwro_version_to_label is deprecated', stacklevel=2)
103 keyval_label = labellib.KeyvalLabel(Key.FIRMWARE_RO_VERSION, image)
104 return labellib.format_keyval_label(keyval_label)
Tom Wai-Hong Tam2d00cb22016-01-08 06:40:50 +0800105
106
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000107def fwrw_version_to_label(image):
108 """
109 Returns the proper label name for a RW firmware build of |image|.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700110
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000111 @param image: A string of the form 'lumpy-release/R28-3993.0.0'
112 @returns: A string that is the appropriate label name.
Dan Shi9cb0eec2014-06-03 09:04:50 -0700113
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000114 """
Allen Liaaabda82017-02-21 18:27:38 -0800115 warnings.warn('fwrw_version_to_label is deprecated', stacklevel=2)
116 keyval_label = labellib.KeyvalLabel(Key.FIRMWARE_RW_VERSION, image)
117 return labellib.format_keyval_label(keyval_label)
Dan Shi9cb0eec2014-06-03 09:04:50 -0700118
119
Alex Miller1968edf2014-02-27 18:11:36 -0800120class _SpecialTaskAction(object):
Alex Miller0516e4c2013-06-03 18:07:48 -0700121 """
Alex Miller1968edf2014-02-27 18:11:36 -0800122 Base class to give a template for mapping labels to tests.
Alex Miller0516e4c2013-06-03 18:07:48 -0700123 """
Alex Miller1968edf2014-02-27 18:11:36 -0800124
Dan Shi7279a5a2016-04-07 11:04:28 -0700125 # A dictionary mapping labels to test names.
126 _actions = {}
Alex Miller0516e4c2013-06-03 18:07:48 -0700127
Dan Shi7279a5a2016-04-07 11:04:28 -0700128 # The name of this special task to be used in output.
129 name = None;
Alex Miller0516e4c2013-06-03 18:07:48 -0700130
Dan Shi7279a5a2016-04-07 11:04:28 -0700131 # Some special tasks require to run before others, e.g., ChromeOS image
132 # needs to be updated before firmware provision. List `_priorities` defines
133 # the order of each label prefix. An element with a smaller index has higher
134 # priority. Not listed ones have the lowest priority.
135 # This property should be overriden in subclass to define its own priorities
136 # across available label prefixes.
137 _priorities = []
Alex Miller1968edf2014-02-27 18:11:36 -0800138
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000139
Alex Miller1968edf2014-02-27 18:11:36 -0800140 @classmethod
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000141 def acts_on(cls, label):
Alex Miller1968edf2014-02-27 18:11:36 -0800142 """
143 Returns True if the label is a label that we recognize as something we
144 know how to act on, given our _actions.
145
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000146 @param label: The label as a string.
Alex Miller1968edf2014-02-27 18:11:36 -0800147 @returns: True if there exists a test to run for this label.
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000148
Alex Miller1968edf2014-02-27 18:11:36 -0800149 """
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000150 return label.split(':')[0] in cls._actions
151
Alex Miller1968edf2014-02-27 18:11:36 -0800152
153 @classmethod
154 def test_for(cls, label):
155 """
156 Returns the test associated with the given (string) label name.
157
158 @param label: The label for which the action is being requested.
159 @returns: The string name of the test that should be run.
160 @raises KeyError: If the name was not recognized as one we care about.
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000161
Alex Miller1968edf2014-02-27 18:11:36 -0800162 """
163 return cls._actions[label]
164
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000165
Alex Milleraa772002014-04-10 17:51:21 -0700166 @classmethod
167 def partition(cls, labels):
168 """
169 Filter a list of labels into two sets: those labels that we know how to
170 act on and those that we don't know how to act on.
171
172 @param labels: A list of strings of labels.
173 @returns: A tuple where the first element is a set of unactionable
174 labels, and the second element is a set of the actionable
175 labels.
176 """
177 capabilities = set()
178 configurations = set()
179
180 for label in labels:
Dan Shie44f9c02016-02-18 13:25:05 -0800181 if label == SKIP_PROVISION:
182 # skip_provision is neither actionable or a capability label.
183 # It doesn't need any handling.
184 continue
185 elif cls.acts_on(label):
Alex Milleraa772002014-04-10 17:51:21 -0700186 configurations.add(label)
187 else:
188 capabilities.add(label)
189
190 return capabilities, configurations
191
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000192
Dan Shi7279a5a2016-04-07 11:04:28 -0700193 @classmethod
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000194 def sort_configurations(cls, configurations):
Dan Shi7279a5a2016-04-07 11:04:28 -0700195 """
196 Sort configurations based on the priority defined in cls._priorities.
197
198 @param configurations: A list of actionable labels.
Dan Shi7279a5a2016-04-07 11:04:28 -0700199
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000200 @return: A sorted list of tuple of (label_prefix, value), the tuples are
201 sorted based on the label_prefix's index in cls._priorities.
Allen Lifda3e232016-10-17 14:54:12 -0700202 """
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000203 # Split a list of labels into a dict mapping name to value. All labels
204 # must be provisionable labels, or else a ValueError
205 # For example, label 'cros-version:lumpy-release/R28-3993.0.0' is split
206 # to {'cros-version': 'lumpy-release/R28-3993.0.0'}
207 split_configurations = dict()
208 for label in configurations:
209 name, _, value = label.partition(':')
210 split_configurations[name] = value
Allen Lifda3e232016-10-17 14:54:12 -0700211
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000212 sort_key = (lambda config:
213 (cls._priorities.index(config[0])
214 if (config[0] in cls._priorities) else sys.maxint))
215 return sorted(split_configurations.items(), key=sort_key)
Dan Shi7279a5a2016-04-07 11:04:28 -0700216
217
Alex Miller1968edf2014-02-27 18:11:36 -0800218class Verify(_SpecialTaskAction):
Alex Miller0516e4c2013-06-03 18:07:48 -0700219 """
Alex Miller1968edf2014-02-27 18:11:36 -0800220 Tests to verify that the DUT is in a sane, known good state that we can run
221 tests on. Failure to verify leads to running Repair.
Alex Miller0516e4c2013-06-03 18:07:48 -0700222 """
Alex Miller1968edf2014-02-27 18:11:36 -0800223
224 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800225 'modem_repair': actionables.TestActionable('cellular_StaleModemReboot'),
Don Garrett6f7e8002015-07-23 22:45:37 +0000226 # TODO(crbug.com/404421): set rpm action to power_RPMTest after the RPM
227 # is stable in lab (destiny). The power_RPMTest failure led to reset job
228 # failure and that left dut in Repair Failed. Since the test will fail
229 # anyway due to the destiny lab issue, and test retry will retry the
230 # test in another DUT.
231 # This change temporarily disable the RPM check in reset job.
232 # Another way to do this is to remove rpm dependency from tests' control
233 # file. That will involve changes on multiple control files. This one
234 # line change here is a simple temporary fix.
235 'rpm': actionables.TestActionable('dummy_PassServer'),
Alex Miller1968edf2014-02-27 18:11:36 -0800236 }
237
238 name = 'verify'
239
240
241class Provision(_SpecialTaskAction):
242 """
243 Provisioning runs to change the configuration of the DUT from one state to
244 another. It will only be run on verified DUTs.
245 """
246
Dan Shi7279a5a2016-04-07 11:04:28 -0700247 # ChromeOS update must happen before firmware install, so the dut has the
248 # correct ChromeOS version label when firmware install occurs. The ChromeOS
249 # version label is used for firmware update to stage desired ChromeOS image
250 # on to the servo USB stick.
251 _priorities = [CROS_VERSION_PREFIX,
252 FW_RO_VERSION_PREFIX,
253 FW_RW_VERSION_PREFIX]
254
Alex Miller1968edf2014-02-27 18:11:36 -0800255 # TODO(milleral): http://crbug.com/249555
256 # Create some way to discover and register provisioning tests so that we
257 # don't need to hand-maintain a list of all of them.
258 _actions = {
Fang Deng9d548742015-02-03 11:35:02 -0800259 CROS_VERSION_PREFIX: actionables.TestActionable(
260 'provision_AutoUpdate',
261 extra_kwargs={'disable_sysinfo': False,
262 'disable_before_test_sysinfo': False,
263 'disable_before_iteration_sysinfo': True,
264 'disable_after_test_sysinfo': True,
265 'disable_after_iteration_sysinfo': True}),
Tom Wai-Hong Tam9a237612016-01-08 03:41:46 +0800266 FW_RO_VERSION_PREFIX: actionables.TestActionable(
Fang Dengaf30e7c2014-11-15 13:57:03 -0800267 'provision_FirmwareUpdate'),
Tom Wai-Hong Tam9a237612016-01-08 03:41:46 +0800268 FW_RW_VERSION_PREFIX: actionables.TestActionable(
269 'provision_FirmwareUpdate',
Dan Shi61e407c2016-04-08 14:21:07 -0700270 extra_kwargs={'rw_only': True,
271 'tag': 'rw_only'}),
Simran Basi5ace6f22016-01-06 17:30:44 -0800272 ANDROID_BUILD_VERSION_PREFIX : actionables.TestActionable(
273 'provision_AndroidUpdate'),
Simran Basiadf31312016-06-28 14:23:05 -0700274 TESTBED_BUILD_VERSION_PREFIX : actionables.TestActionable(
275 'provision_TestbedUpdate'),
Alex Miller1968edf2014-02-27 18:11:36 -0800276 }
277
278 name = 'provision'
279
280
281class Cleanup(_SpecialTaskAction):
282 """
283 Cleanup runs after a test fails to try and remove artifacts of tests and
284 ensure the DUT will be in a sane state for the next test run.
285 """
286
287 _actions = {
Fang Dengaf30e7c2014-11-15 13:57:03 -0800288 'cleanup-reboot': actionables.RebootActionable(),
Alex Miller1968edf2014-02-27 18:11:36 -0800289 }
290
291 name = 'cleanup'
292
293
294class Repair(_SpecialTaskAction):
295 """
296 Repair runs when one of the other special tasks fails. It should be able
297 to take a component of the DUT that's in an unknown state and restore it to
298 a good state.
299 """
300
301 _actions = {
302 }
303
304 name = 'repair'
305
306
Alex Milleraa772002014-04-10 17:51:21 -0700307# TODO(milleral): crbug.com/364273
308# Label doesn't really mean label in this context. We're putting things into
309# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop
310# doing that.
311def is_for_special_action(label):
312 """
313 If any special task handles the label specially, then we're using the label
314 to communicate that we want an action, and not as an actual dependency that
315 the test has.
316
317 @param label: A string label name.
318 @return True if any special task handles this label specially,
319 False if no special task handles this label.
320 """
321 return (Verify.acts_on(label) or
322 Provision.acts_on(label) or
323 Cleanup.acts_on(label) or
Dan Shie44f9c02016-02-18 13:25:05 -0800324 Repair.acts_on(label) or
325 label == SKIP_PROVISION)
Alex Miller0516e4c2013-06-03 18:07:48 -0700326
327
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000328def join(provision_type, provision_value):
329 """
330 Combine the provision type and value into the label name.
331
332 @param provision_type: One of the constants that are the label prefixes.
333 @param provision_value: A string of the value for this provision type.
334 @returns: A string that is the label name for this (type, value) pair.
335
336 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0')
337 'cros-version:lumpy-release/R27-3773.0.0'
338
339 """
340 return '%s:%s' % (provision_type, provision_value)
341
342
Alex Miller667b5f22014-02-28 15:33:39 -0800343class SpecialTaskActionException(Exception):
344 """
345 Exception raised when a special task fails to successfully run a test that
346 is required.
347
348 This is also a literally meaningless exception. It's always just discarded.
349 """
350
351
352def run_special_task_actions(job, host, labels, task):
353 """
354 Iterate through all `label`s and run any tests on `host` that `task` has
355 corresponding to the passed in labels.
356
357 Emits status lines for each run test, and INFO lines for each skipped label.
358
359 @param job: A job object from a control file.
360 @param host: The host to run actions on.
361 @param labels: The list of job labels to work on.
362 @param task: An instance of _SpecialTaskAction.
363 @returns: None
364 @raises: SpecialTaskActionException if a test fails.
365
366 """
Dan Shi7279a5a2016-04-07 11:04:28 -0700367 capabilities, configurations = task.partition(labels)
Alex Miller667b5f22014-02-28 15:33:39 -0800368
369 for label in capabilities:
Dan Shi7279a5a2016-04-07 11:04:28 -0700370 job.record('INFO', None, task.name,
371 "Can't %s label '%s'." % (task.name, label))
Alex Miller667b5f22014-02-28 15:33:39 -0800372
Dan Shi7279a5a2016-04-07 11:04:28 -0700373 # Sort the configuration labels based on `task._priorities`.
Prathmesh Prabhu2c7471d2016-11-15 20:19:57 +0000374 sorted_configurations = task.sort_configurations(configurations)
375 for name, value in sorted_configurations:
Dan Shi7279a5a2016-04-07 11:04:28 -0700376 action_item = task.test_for(name)
377 success = action_item.execute(job=job, host=host, value=value)
378 if not success:
379 raise SpecialTaskActionException()