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