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