• 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_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