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