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