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