Home | History | Annotate | Download | only in cros
      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 
      6 import abc
      7 
      8 import common
      9 from autotest_lib.server.cros import provision_actionables as actionables
     10 
     11 
     12 ### Constants for label prefixes
     13 CROS_VERSION_PREFIX = 'cros-version'
     14 ANDROID_BUILD_VERSION_PREFIX = 'ab-version'
     15 FW_RW_VERSION_PREFIX = 'fwrw-version'
     16 FW_RO_VERSION_PREFIX = 'fwro-version'
     17 
     18 # Default number of provisions attempts to try if we believe the devserver is
     19 # flaky.
     20 FLAKY_DEVSERVER_ATTEMPTS = 2
     21 
     22 
     23 ### Helpers to convert value to label
     24 def 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 
     35 def 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 
     46 def 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 
     57 class _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 
    130 class 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 
    153 class 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 
    182 class 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 
    195 class 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.
    213 def 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 
    229 def 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 
    247 def 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 
    277 def 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 
    292 class 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 
    301 def 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