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