1 # 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 6 import re 7 import sys 8 9 import common 10 from autotest_lib.server.cros import provision_actionables as actionables 11 12 13 ### Constants for label prefixes 14 CROS_VERSION_PREFIX = 'cros-version' 15 ANDROID_BUILD_VERSION_PREFIX = 'ab-version' 16 TESTBED_BUILD_VERSION_PREFIX = 'testbed-version' 17 FW_RW_VERSION_PREFIX = 'fwrw-version' 18 FW_RO_VERSION_PREFIX = 'fwro-version' 19 20 _ANDROID_BUILD_REGEX = r'.+/.+/P?([0-9]+|LATEST)' 21 _ANDROID_TESTBED_BUILD_REGEX = _ANDROID_BUILD_REGEX + '(,|(#[0-9]+))' 22 23 # Special label to skip provision and run reset instead. 24 SKIP_PROVISION = 'skip_provision' 25 26 # Default number of provisions attempts to try if we believe the devserver is 27 # flaky. 28 FLAKY_DEVSERVER_ATTEMPTS = 2 29 30 31 ### Helpers to convert value to label 32 def get_version_label_prefix(image): 33 """ 34 Determine a version label prefix from a given image name. 35 36 Parses `image` to determine what kind of image it refers 37 to, and returns the corresponding version label prefix. 38 39 Known version label prefixes are: 40 * `CROS_VERSION_PREFIX` for Chrome OS version strings. 41 These images have names like `cave-release/R57-9030.0.0`. 42 * `ANDROID_BUILD_VERSION_PREFIX` for Android build versions 43 These images have names like 44 `git_mnc-release/shamu-userdebug/2457013`. 45 * `TESTBED_BUILD_VERSION_PREFIX` for Android testbed version 46 specifications. These are either comma separated lists of 47 Android versions, or an Android version with a suffix like 48 '#2', indicating two devices running the given build. 49 50 @param image: The image name to be parsed. 51 @returns: A string that is the prefix of version labels for the type 52 of image identified by `image`. 53 54 """ 55 if re.match(_ANDROID_TESTBED_BUILD_REGEX, image, re.I): 56 return TESTBED_BUILD_VERSION_PREFIX 57 elif re.match(_ANDROID_BUILD_REGEX, image, re.I): 58 return ANDROID_BUILD_VERSION_PREFIX 59 else: 60 return CROS_VERSION_PREFIX 61 62 63 def image_version_to_label(image): 64 """ 65 Return a version label appropriate to the given image name. 66 67 The type of version label is as determined described for 68 `get_version_label_prefix()`, meaning the label will identify a 69 CrOS, Android, or Testbed version. 70 71 @param image: The image name to be parsed. 72 @returns: A string that is the appropriate label name. 73 74 """ 75 return get_version_label_prefix(image) + ':' + image 76 77 78 def cros_version_to_label(image): 79 """ 80 Returns the proper label name for a ChromeOS build of |image|. 81 82 @param image: A string of the form 'lumpy-release/R28-3993.0.0' 83 @returns: A string that is the appropriate label name. 84 85 """ 86 return CROS_VERSION_PREFIX + ':' + image 87 88 89 def fwro_version_to_label(image): 90 """ 91 Returns the proper label name for a RO firmware build of |image|. 92 93 @param image: A string of the form 'lumpy-release/R28-3993.0.0' 94 @returns: A string that is the appropriate label name. 95 96 """ 97 return FW_RO_VERSION_PREFIX + ':' + image 98 99 100 def fwrw_version_to_label(image): 101 """ 102 Returns the proper label name for a RW firmware build of |image|. 103 104 @param image: A string of the form 'lumpy-release/R28-3993.0.0' 105 @returns: A string that is the appropriate label name. 106 107 """ 108 return FW_RW_VERSION_PREFIX + ':' + image 109 110 111 class _SpecialTaskAction(object): 112 """ 113 Base class to give a template for mapping labels to tests. 114 """ 115 116 # A dictionary mapping labels to test names. 117 _actions = {} 118 119 # The name of this special task to be used in output. 120 name = None; 121 122 # Some special tasks require to run before others, e.g., ChromeOS image 123 # needs to be updated before firmware provision. List `_priorities` defines 124 # the order of each label prefix. An element with a smaller index has higher 125 # priority. Not listed ones have the lowest priority. 126 # This property should be overriden in subclass to define its own priorities 127 # across available label prefixes. 128 _priorities = [] 129 130 131 @classmethod 132 def acts_on(cls, label): 133 """ 134 Returns True if the label is a label that we recognize as something we 135 know how to act on, given our _actions. 136 137 @param label: The label as a string. 138 @returns: True if there exists a test to run for this label. 139 140 """ 141 return label.split(':')[0] in cls._actions 142 143 144 @classmethod 145 def test_for(cls, label): 146 """ 147 Returns the test associated with the given (string) label name. 148 149 @param label: The label for which the action is being requested. 150 @returns: The string name of the test that should be run. 151 @raises KeyError: If the name was not recognized as one we care about. 152 153 """ 154 return cls._actions[label] 155 156 157 @classmethod 158 def partition(cls, labels): 159 """ 160 Filter a list of labels into two sets: those labels that we know how to 161 act on and those that we don't know how to act on. 162 163 @param labels: A list of strings of labels. 164 @returns: A tuple where the first element is a set of unactionable 165 labels, and the second element is a set of the actionable 166 labels. 167 """ 168 capabilities = set() 169 configurations = set() 170 171 for label in labels: 172 if label == SKIP_PROVISION: 173 # skip_provision is neither actionable or a capability label. 174 # It doesn't need any handling. 175 continue 176 elif cls.acts_on(label): 177 configurations.add(label) 178 else: 179 capabilities.add(label) 180 181 return capabilities, configurations 182 183 184 @classmethod 185 def sort_configurations(cls, configurations): 186 """ 187 Sort configurations based on the priority defined in cls._priorities. 188 189 @param configurations: A list of actionable labels. 190 191 @return: A sorted list of tuple of (label_prefix, value), the tuples are 192 sorted based on the label_prefix's index in cls._priorities. 193 """ 194 # Split a list of labels into a dict mapping name to value. All labels 195 # must be provisionable labels, or else a ValueError 196 # For example, label 'cros-version:lumpy-release/R28-3993.0.0' is split 197 # to {'cros-version': 'lumpy-release/R28-3993.0.0'} 198 split_configurations = dict() 199 for label in configurations: 200 name, _, value = label.partition(':') 201 split_configurations[name] = value 202 203 sort_key = (lambda config: 204 (cls._priorities.index(config[0]) 205 if (config[0] in cls._priorities) else sys.maxint)) 206 return sorted(split_configurations.items(), key=sort_key) 207 208 209 class Verify(_SpecialTaskAction): 210 """ 211 Tests to verify that the DUT is in a sane, known good state that we can run 212 tests on. Failure to verify leads to running Repair. 213 """ 214 215 _actions = { 216 'modem_repair': actionables.TestActionable('cellular_StaleModemReboot'), 217 # TODO(crbug.com/404421): set rpm action to power_RPMTest after the RPM 218 # is stable in lab (destiny). The power_RPMTest failure led to reset job 219 # failure and that left dut in Repair Failed. Since the test will fail 220 # anyway due to the destiny lab issue, and test retry will retry the 221 # test in another DUT. 222 # This change temporarily disable the RPM check in reset job. 223 # Another way to do this is to remove rpm dependency from tests' control 224 # file. That will involve changes on multiple control files. This one 225 # line change here is a simple temporary fix. 226 'rpm': actionables.TestActionable('dummy_PassServer'), 227 } 228 229 name = 'verify' 230 231 232 class Provision(_SpecialTaskAction): 233 """ 234 Provisioning runs to change the configuration of the DUT from one state to 235 another. It will only be run on verified DUTs. 236 """ 237 238 # ChromeOS update must happen before firmware install, so the dut has the 239 # correct ChromeOS version label when firmware install occurs. The ChromeOS 240 # version label is used for firmware update to stage desired ChromeOS image 241 # on to the servo USB stick. 242 _priorities = [CROS_VERSION_PREFIX, 243 FW_RO_VERSION_PREFIX, 244 FW_RW_VERSION_PREFIX] 245 246 # TODO(milleral): http://crbug.com/249555 247 # Create some way to discover and register provisioning tests so that we 248 # don't need to hand-maintain a list of all of them. 249 _actions = { 250 CROS_VERSION_PREFIX: actionables.TestActionable( 251 'provision_AutoUpdate', 252 extra_kwargs={'disable_sysinfo': False, 253 'disable_before_test_sysinfo': False, 254 'disable_before_iteration_sysinfo': True, 255 'disable_after_test_sysinfo': True, 256 'disable_after_iteration_sysinfo': True}), 257 FW_RO_VERSION_PREFIX: actionables.TestActionable( 258 'provision_FirmwareUpdate'), 259 FW_RW_VERSION_PREFIX: actionables.TestActionable( 260 'provision_FirmwareUpdate', 261 extra_kwargs={'rw_only': True, 262 'tag': 'rw_only'}), 263 ANDROID_BUILD_VERSION_PREFIX : actionables.TestActionable( 264 'provision_AndroidUpdate'), 265 TESTBED_BUILD_VERSION_PREFIX : actionables.TestActionable( 266 'provision_TestbedUpdate'), 267 } 268 269 name = 'provision' 270 271 272 class Cleanup(_SpecialTaskAction): 273 """ 274 Cleanup runs after a test fails to try and remove artifacts of tests and 275 ensure the DUT will be in a sane state for the next test run. 276 """ 277 278 _actions = { 279 'cleanup-reboot': actionables.RebootActionable(), 280 } 281 282 name = 'cleanup' 283 284 285 class Repair(_SpecialTaskAction): 286 """ 287 Repair runs when one of the other special tasks fails. It should be able 288 to take a component of the DUT that's in an unknown state and restore it to 289 a good state. 290 """ 291 292 _actions = { 293 } 294 295 name = 'repair' 296 297 298 # TODO(milleral): crbug.com/364273 299 # Label doesn't really mean label in this context. We're putting things into 300 # DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop 301 # doing that. 302 def is_for_special_action(label): 303 """ 304 If any special task handles the label specially, then we're using the label 305 to communicate that we want an action, and not as an actual dependency that 306 the test has. 307 308 @param label: A string label name. 309 @return True if any special task handles this label specially, 310 False if no special task handles this label. 311 """ 312 return (Verify.acts_on(label) or 313 Provision.acts_on(label) or 314 Cleanup.acts_on(label) or 315 Repair.acts_on(label) or 316 label == SKIP_PROVISION) 317 318 319 def join(provision_type, provision_value): 320 """ 321 Combine the provision type and value into the label name. 322 323 @param provision_type: One of the constants that are the label prefixes. 324 @param provision_value: A string of the value for this provision type. 325 @returns: A string that is the label name for this (type, value) pair. 326 327 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0') 328 'cros-version:lumpy-release/R27-3773.0.0' 329 330 """ 331 return '%s:%s' % (provision_type, provision_value) 332 333 334 class SpecialTaskActionException(Exception): 335 """ 336 Exception raised when a special task fails to successfully run a test that 337 is required. 338 339 This is also a literally meaningless exception. It's always just discarded. 340 """ 341 342 343 def run_special_task_actions(job, host, labels, task): 344 """ 345 Iterate through all `label`s and run any tests on `host` that `task` has 346 corresponding to the passed in labels. 347 348 Emits status lines for each run test, and INFO lines for each skipped label. 349 350 @param job: A job object from a control file. 351 @param host: The host to run actions on. 352 @param labels: The list of job labels to work on. 353 @param task: An instance of _SpecialTaskAction. 354 @returns: None 355 @raises: SpecialTaskActionException if a test fails. 356 357 """ 358 capabilities, configurations = task.partition(labels) 359 360 for label in capabilities: 361 job.record('INFO', None, task.name, 362 "Can't %s label '%s'." % (task.name, label)) 363 364 # Sort the configuration labels based on `task._priorities`. 365 sorted_configurations = task.sort_configurations(configurations) 366 for name, value in sorted_configurations: 367 action_item = task.test_for(name) 368 success = action_item.execute(job=job, host=host, value=value) 369 if not success: 370 raise SpecialTaskActionException() 371