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