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