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 5 6import abc 7 8import common 9from autotest_lib.server.cros import provision_actionables as actionables 10 11 12### Constants for label prefixes 13CROS_VERSION_PREFIX = 'cros-version' 14ANDROID_BUILD_VERSION_PREFIX = 'ab-version' 15FW_RW_VERSION_PREFIX = 'fwrw-version' 16FW_RO_VERSION_PREFIX = 'fwro-version' 17 18# Default number of provisions attempts to try if we believe the devserver is 19# flaky. 20FLAKY_DEVSERVER_ATTEMPTS = 2 21 22 23### Helpers to convert value to label 24def cros_version_to_label(image): 25 """ 26 Returns the proper label name for a ChromeOS build of |image|. 27 28 @param image: A string of the form 'lumpy-release/R28-3993.0.0' 29 @returns: A string that is the appropriate label name. 30 31 """ 32 return CROS_VERSION_PREFIX + ':' + image 33 34 35def fwro_version_to_label(image): 36 """ 37 Returns the proper label name for a RO firmware build of |image|. 38 39 @param image: A string of the form 'lumpy-release/R28-3993.0.0' 40 @returns: A string that is the appropriate label name. 41 42 """ 43 return FW_RO_VERSION_PREFIX + ':' + image 44 45 46def fwrw_version_to_label(image): 47 """ 48 Returns the proper label name for a RW firmware build of |image|. 49 50 @param image: A string of the form 'lumpy-release/R28-3993.0.0' 51 @returns: A string that is the appropriate label name. 52 53 """ 54 return FW_RW_VERSION_PREFIX + ':' + image 55 56 57class _SpecialTaskAction(object): 58 """ 59 Base class to give a template for mapping labels to tests. 60 """ 61 62 __metaclass__ = abc.ABCMeta 63 64 65 # One cannot do 66 # @abc.abstractproperty 67 # _actions = {} 68 # so this is the next best thing 69 @abc.abstractproperty 70 def _actions(self): 71 """A dictionary mapping labels to test names.""" 72 pass 73 74 75 @abc.abstractproperty 76 def name(self): 77 """The name of this special task to be used in output.""" 78 pass 79 80 81 @classmethod 82 def acts_on(cls, label): 83 """ 84 Returns True if the label is a label that we recognize as something we 85 know how to act on, given our _actions. 86 87 @param label: The label as a string. 88 @returns: True if there exists a test to run for this label. 89 90 """ 91 return label.split(':')[0] in cls._actions 92 93 94 @classmethod 95 def test_for(cls, label): 96 """ 97 Returns the test associated with the given (string) label name. 98 99 @param label: The label for which the action is being requested. 100 @returns: The string name of the test that should be run. 101 @raises KeyError: If the name was not recognized as one we care about. 102 103 """ 104 return cls._actions[label] 105 106 107 @classmethod 108 def partition(cls, labels): 109 """ 110 Filter a list of labels into two sets: those labels that we know how to 111 act on and those that we don't know how to act on. 112 113 @param labels: A list of strings of labels. 114 @returns: A tuple where the first element is a set of unactionable 115 labels, and the second element is a set of the actionable 116 labels. 117 """ 118 capabilities = set() 119 configurations = set() 120 121 for label in labels: 122 if cls.acts_on(label): 123 configurations.add(label) 124 else: 125 capabilities.add(label) 126 127 return capabilities, configurations 128 129 130class Verify(_SpecialTaskAction): 131 """ 132 Tests to verify that the DUT is in a sane, known good state that we can run 133 tests on. Failure to verify leads to running Repair. 134 """ 135 136 _actions = { 137 'modem_repair': actionables.TestActionable('cellular_StaleModemReboot'), 138 # TODO(crbug.com/404421): set rpm action to power_RPMTest after the RPM 139 # is stable in lab (destiny). The power_RPMTest failure led to reset job 140 # failure and that left dut in Repair Failed. Since the test will fail 141 # anyway due to the destiny lab issue, and test retry will retry the 142 # test in another DUT. 143 # This change temporarily disable the RPM check in reset job. 144 # Another way to do this is to remove rpm dependency from tests' control 145 # file. That will involve changes on multiple control files. This one 146 # line change here is a simple temporary fix. 147 'rpm': actionables.TestActionable('dummy_PassServer'), 148 } 149 150 name = 'verify' 151 152 153class Provision(_SpecialTaskAction): 154 """ 155 Provisioning runs to change the configuration of the DUT from one state to 156 another. It will only be run on verified DUTs. 157 """ 158 159 # TODO(milleral): http://crbug.com/249555 160 # Create some way to discover and register provisioning tests so that we 161 # don't need to hand-maintain a list of all of them. 162 _actions = { 163 CROS_VERSION_PREFIX: actionables.TestActionable( 164 'provision_AutoUpdate', 165 extra_kwargs={'disable_sysinfo': False, 166 'disable_before_test_sysinfo': False, 167 'disable_before_iteration_sysinfo': True, 168 'disable_after_test_sysinfo': True, 169 'disable_after_iteration_sysinfo': True}), 170 FW_RO_VERSION_PREFIX: actionables.TestActionable( 171 'provision_FirmwareUpdate'), 172 FW_RW_VERSION_PREFIX: actionables.TestActionable( 173 'provision_FirmwareUpdate', 174 extra_kwargs={'rw_only': True}), 175 ANDROID_BUILD_VERSION_PREFIX : actionables.TestActionable( 176 'provision_AndroidUpdate'), 177 } 178 179 name = 'provision' 180 181 182class Cleanup(_SpecialTaskAction): 183 """ 184 Cleanup runs after a test fails to try and remove artifacts of tests and 185 ensure the DUT will be in a sane state for the next test run. 186 """ 187 188 _actions = { 189 'cleanup-reboot': actionables.RebootActionable(), 190 } 191 192 name = 'cleanup' 193 194 195class Repair(_SpecialTaskAction): 196 """ 197 Repair runs when one of the other special tasks fails. It should be able 198 to take a component of the DUT that's in an unknown state and restore it to 199 a good state. 200 """ 201 202 _actions = { 203 } 204 205 name = 'repair' 206 207 208 209# TODO(milleral): crbug.com/364273 210# Label doesn't really mean label in this context. We're putting things into 211# DEPENDENCIES that really aren't DEPENDENCIES, and we should probably stop 212# doing that. 213def is_for_special_action(label): 214 """ 215 If any special task handles the label specially, then we're using the label 216 to communicate that we want an action, and not as an actual dependency that 217 the test has. 218 219 @param label: A string label name. 220 @return True if any special task handles this label specially, 221 False if no special task handles this label. 222 """ 223 return (Verify.acts_on(label) or 224 Provision.acts_on(label) or 225 Cleanup.acts_on(label) or 226 Repair.acts_on(label)) 227 228 229def filter_labels(labels): 230 """ 231 Filter a list of labels into two sets: those labels that we know how to 232 change and those that we don't. For the ones we know how to change, split 233 them apart into the name of configuration type and its value. 234 235 @param labels: A list of strings of labels. 236 @returns: A tuple where the first element is a set of unprovisionable 237 labels, and the second element is a set of the provisionable 238 labels. 239 240 >>> filter_labels(['bluetooth', 'cros-version:lumpy-release/R28-3993.0.0']) 241 (set(['bluetooth']), set(['cros-version:lumpy-release/R28-3993.0.0'])) 242 243 """ 244 return Provision.partition(labels) 245 246 247def split_labels(labels): 248 """ 249 Split a list of labels into a dict mapping name to value. All labels must 250 be provisionable labels, or else a ValueError 251 252 @param labels: list of strings of label names 253 @returns: A dict of where the key is the configuration name, and the value 254 is the configuration value. 255 @raises: ValueError if a label is not a provisionable label. 256 257 >>> split_labels(['cros-version:lumpy-release/R28-3993.0.0']) 258 {'cros-version': 'lumpy-release/R28-3993.0.0'} 259 >>> split_labels(['bluetooth']) 260 Traceback (most recent call last): 261 ... 262 ValueError: Unprovisionable label bluetooth 263 264 """ 265 configurations = dict() 266 267 for label in labels: 268 if Provision.acts_on(label): 269 name, value = label.split(':', 1) 270 configurations[name] = value 271 else: 272 raise ValueError('Unprovisionable label %s' % label) 273 274 return configurations 275 276 277def join(provision_type, provision_value): 278 """ 279 Combine the provision type and value into the label name. 280 281 @param provision_type: One of the constants that are the label prefixes. 282 @param provision_value: A string of the value for this provision type. 283 @returns: A string that is the label name for this (type, value) pair. 284 285 >>> join(CROS_VERSION_PREFIX, 'lumpy-release/R27-3773.0.0') 286 'cros-version:lumpy-release/R27-3773.0.0' 287 288 """ 289 return '%s:%s' % (provision_type, provision_value) 290 291 292class SpecialTaskActionException(Exception): 293 """ 294 Exception raised when a special task fails to successfully run a test that 295 is required. 296 297 This is also a literally meaningless exception. It's always just discarded. 298 """ 299 300 301def run_special_task_actions(job, host, labels, task): 302 """ 303 Iterate through all `label`s and run any tests on `host` that `task` has 304 corresponding to the passed in labels. 305 306 Emits status lines for each run test, and INFO lines for each skipped label. 307 308 @param job: A job object from a control file. 309 @param host: The host to run actions on. 310 @param labels: The list of job labels to work on. 311 @param task: An instance of _SpecialTaskAction. 312 @returns: None 313 @raises: SpecialTaskActionException if a test fails. 314 315 """ 316 capabilities, configuration = filter_labels(labels) 317 318 for label in capabilities: 319 if task.acts_on(label): 320 action_item = task.test_for(label) 321 success = action_item.execute(job=job, host=host) 322 if not success: 323 raise SpecialTaskActionException() 324 else: 325 job.record('INFO', None, task.name, 326 "Can't %s label '%s'." % (task.name, label)) 327 328 for name, value in split_labels(configuration).items(): 329 if task.acts_on(name): 330 action_item = task.test_for(name) 331 success = action_item.execute(job=job, host=host, value=value) 332 if not success: 333 raise SpecialTaskActionException() 334 else: 335 job.record('INFO', None, task.name, 336 "Can't %s label '%s:%s'." % (task.name, name, value)) 337