• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 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 logging
6
7import common
8from autotest_lib.client.common_lib import error
9from autotest_lib.server import test
10
11
12def _assert_equal(expected, actual):
13    """Compares objects.
14
15    @param expected: the expected value.
16    @param actual: the actual value.
17
18    @raises error.TestFail
19    """
20    if expected != actual:
21        raise error.TestFail('Expected: %r, actual: %r' % (expected, actual))
22
23
24class brillo_BootLoader(test.test):
25    """A/B tests for boot loader and boot_control HAL implementation."""
26    version = 1
27
28
29    def get_slots_and_suffix(self):
30        """Gets number of slots supported and slot suffixes used.
31
32        Prerequisite: The DUT is in ADB mode.
33        """
34        self.num_slots = int(self.dut.run_output('bootctl get-number-slots'))
35        logging.info('Number of slots: %d', self.num_slots)
36        self.suffix_a = self.dut.run_output('bootctl get-suffix 0')
37        self.suffix_b = self.dut.run_output('bootctl get-suffix 1')
38        logging.info('Slot 0 suffix: "%s"', self.suffix_a)
39        logging.info('Slot 1 suffix: "%s"', self.suffix_b)
40        _assert_equal(2, self.num_slots)
41        # We're going to need the size of the boot partitions later.
42        self.boot_a_size = int(self.dut.run_output(
43            'blockdev --getsize64 /dev/block/by-name/boot%s' % self.suffix_a))
44        self.boot_b_size = int(self.dut.run_output(
45            'blockdev --getsize64 /dev/block/by-name/boot%s' % self.suffix_b))
46        if self.boot_a_size != self.boot_b_size:
47            raise error.TestFail('boot partitions are not the same size')
48        logging.info('boot partition size: %d bytes', self.boot_a_size)
49
50
51    def fastboot_get_var(self, variable_name):
52        """Gets a fastboot variable.
53
54        Returns a string with the value or the empty string if the
55        variable does not exist.
56
57        Prerequisite: The DUT is in bootloader mode.
58
59        @param variable_name: Name of fastboot variable to get.
60
61        """
62        cmd_output = self.dut.fastboot_run('getvar %s' % variable_name)
63        # Gah, 'fastboot getvar' prints requested output on stderr
64        # instead of stdout as you'd expect.
65        lines = cmd_output.stderr.split('\n')
66        if lines[0].startswith(variable_name + ': '):
67            return (lines[0])[len(variable_name + ': '):]
68        return ''
69
70
71    def check_fastboot_variables(self):
72        """Checks that fastboot bootloader has necessary variables for A/B.
73
74        Prerequisite: The DUT is in ADB mode.
75        """
76        logging.info('Checking fastboot-compliant bootloader has necessary '
77                     'variables for A/B.')
78        self.dut.ensure_bootloader_mode()
79        # The slot-suffixes looks like '_a,_b' and may have a trailing comma.
80        suffixes = self.fastboot_get_var('slot-suffixes')
81        if suffixes.rstrip(',').split(',') != [self.suffix_a, self.suffix_b]:
82            raise error.TestFail('Variable slot-suffixes has unexpected '
83                                 'value "%s"' % suffixes)
84        # Back to ADB mode.
85        self.dut.fastboot_reboot()
86
87
88    def get_current_slot(self):
89        """Gets the current slot the DUT is running from.
90
91        Prerequisite: The DUT is in ADB mode.
92        """
93        return int(self.dut.run_output('bootctl get-current-slot'))
94
95
96    def assert_current_slot(self, slot_number):
97        """Checks that DUT is running from the given slot.
98
99        Prerequisite: The DUT is in ADB mode.
100
101        @param slot_number: Zero-based index of slot to be running from.
102        """
103        _assert_equal(slot_number, self.get_current_slot())
104
105
106    def set_active_slot(self, slot_number):
107        """Instructs the DUT to attempt booting from given slot.
108
109        Prerequisite: The DUT is in ADB mode.
110
111        @param slot_number: Zero-based index of slot to make active.
112        """
113        logging.info('Setting slot %d active.', slot_number)
114        self.dut.run('bootctl set-active-boot-slot %d' % slot_number)
115
116
117    def ensure_running_slot(self, slot_number):
118        """Ensures that DUT is running from the given slot.
119
120        Prerequisite: The DUT is in ADB mode.
121
122        @param slot_number: Zero-based index of slot to be running from.
123        """
124        logging.info('Ensuring device is running from slot %d.', slot_number)
125        if self.get_current_slot() != slot_number:
126            logging.info('Rebooting into slot %d', slot_number)
127            self.set_active_slot(slot_number)
128            self.dut.reboot()
129            self.assert_current_slot(slot_number)
130
131
132    def copy_a_to_b(self):
133        """Copies contents of slot A to slot B.
134
135        Prerequisite: The DUT is in ADB mode and booted from slot A.
136        """
137        self.assert_current_slot(0)
138        for i in ['boot', 'system']:
139            logging.info('Copying %s%s to %s%s.',
140                         i, self.suffix_a, i, self.suffix_b)
141            self.dut.run('dd if=/dev/block/by-name/%s%s '
142                         'of=/dev/block/by-name/%s%s bs=4096' %
143                         (i, self.suffix_a, i, self.suffix_b))
144
145
146    def check_bootctl_set_active(self):
147        """Checks that setActiveBootSlot in the boot_control HAL work.
148
149        Prerequisite: The DUT is in ADB mode with populated A and B slots.
150        """
151        logging.info('Check setActiveBootSlot() in boot_control HAL.')
152        self.set_active_slot(0)
153        self.dut.reboot()
154        self.assert_current_slot(0)
155        self.set_active_slot(1)
156        self.dut.reboot()
157        self.assert_current_slot(1)
158
159
160    def check_fastboot_set_active(self):
161        """Checks that 'fastboot set_active <SUFFIX>' work.
162
163        Prerequisite: The DUT is in ADB mode with populated A and B slots.
164        """
165        logging.info('Check set_active command in fastboot-compliant bootloader.')
166        self.dut.ensure_bootloader_mode()
167        self.dut.fastboot_run('set_active %s' % self.suffix_a)
168        self.dut.fastboot_reboot()
169        self.dut.adb_run('wait-for-device')
170        self.assert_current_slot(0)
171        self.dut.ensure_bootloader_mode()
172        self.dut.fastboot_run('set_active %s' % self.suffix_b)
173        self.dut.fastboot_reboot()
174        self.dut.adb_run('wait-for-device')
175        self.assert_current_slot(1)
176
177
178    def check_bootloader_fallback_on_invalid(self):
179        """Checks bootloader fallback if current slot is invalid.
180
181        Prerequisite: The DUT is in ADB mode with populated A and B slots.
182        """
183        logging.info('Checking bootloader fallback if current slot '
184                     'is invalid.')
185        # Make sure we're in slot B, then zero out boot_b (so slot B
186        # is invalid), reboot and check that the bootloader correctly
187        # fell back to A.
188        self.ensure_running_slot(1)
189        self.dut.run('dd if=/dev/zero of=/dev/block/by-name/boot%s '
190                     'count=%d bs=4096' % (self.suffix_b,
191                                           self.boot_b_size/4096))
192        self.dut.reboot()
193        self.assert_current_slot(0)
194        # Restore boot_b for use in future test cases.
195        self.dut.run('dd if=/dev/block/by-name/boot%s '
196                     'of=/dev/block/by-name/boot%s bs=4096' %
197                     (self.suffix_a, self.suffix_b))
198
199
200    def check_bootloader_fallback_on_retries(self):
201        """Checks bootloader fallback if slot made active runs out of tries.
202
203        Prerequisite: The DUT is in ADB mode with populated A and B slots.
204
205        @raises error.TestFail
206        """
207        logging.info('Checking bootloader fallback if slot made active '
208                     'runs out of tries.')
209        self.ensure_running_slot(0)
210        self.set_active_slot(1)
211        self.dut.reboot()
212        num_retries = 1
213        while num_retries < 10 and self.get_current_slot() == 1:
214            logging.info('Still with B after %d retries', num_retries)
215            num_retries += 1
216            self.dut.reboot()
217        if self.get_current_slot() != 0:
218            raise error.TestFail('Bootloader did not fall back after '
219                                 '%d retries without the slot being marked '
220                                 'as GOOD' % num_retries)
221        logging.info('Fell back to A after %d retries.', num_retries)
222
223
224    def check_bootloader_mark_successful(self):
225        """Checks bootloader stays with slot after it has been marked good.
226
227        Prerequisite: The DUT is in ADB mode with populated A and B slots.
228        """
229        logging.info('Checking bootloader is staying with a slot after it has '
230                     'been marked as GOOD for at least 10 reboots.')
231        self.ensure_running_slot(0)
232        self.dut.run('bootctl mark-boot-successful')
233        num_reboots = 0
234        while num_reboots < 10:
235            self.dut.reboot()
236            self.assert_current_slot(0)
237            num_reboots += 1
238            logging.info('Still with A after %d reboots', num_reboots)
239
240
241    def run_once(self, dut=None):
242        """A/B tests for boot loader and boot_control HAL implementation.
243
244        Verifies that boot loader and boot_control HAL implementation
245        implements A/B correctly.
246
247        Prerequisite: The DUT is in ADB mode.
248
249        @param dut: host object representing the device under test.
250        """
251        self.dut = dut
252        self.get_slots_and_suffix()
253        self.check_fastboot_variables()
254        self.ensure_running_slot(0)
255        self.copy_a_to_b()
256        self.check_bootctl_set_active()
257        self.check_fastboot_set_active()
258        self.check_bootloader_fallback_on_invalid()
259        self.check_bootloader_fallback_on_retries()
260        self.check_bootloader_mark_successful()
261