• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020 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 glob
6import logging
7import os
8import re
9
10from autotest_lib.client.bin import utils
11
12_KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
13_KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
14
15# Time to wait for new kernel to be marked successful after auto update.
16_KERNEL_UPDATE_TIMEOUT = 120
17
18_BOOT_ERR_MSG = 'The active image slot did not change after the update.'
19
20def _run(cmd, host=None):
21    """
22    Function to execue commands.
23
24    This allows the util to be used by client and server tests.
25
26    @param cmd: The command to execute
27    @param host: The host to use in a server test. None to use utils.run.
28
29    """
30    if host is not None:
31        return host.run(cmd)
32    else:
33        return utils.run(cmd)
34
35def _cgpt(flag, kernel, host=None):
36    """
37    Helper function to return numeric cgpt value.
38
39    @param flag: Additional flag to pass to cgpt
40    @param kernel: The kernel we want to interact with.
41    @param host: The DUT to execute the command on. None to execute locally.
42
43    """
44    rootdev = _run(['rootdev','-s', '-d'], host).stdout.strip()
45    return int(_run(['cgpt', 'show', '-n', '-i', str(kernel['kernel']), flag,
46                     rootdev], host).stdout.strip())
47
48def get_kernel_state(host=None):
49    """
50    Returns the (<active>, <inactive>) kernel state as a pair.
51
52    @param host: The DUT to execute the command on. None to execute locally.
53
54    """
55    rootdev = _run(['rootdev','-s'], host).stdout.strip()
56    active_root = int(re.findall('\d+\Z', rootdev)[0])
57    if active_root == _KERNEL_A['root']:
58        return _KERNEL_A, _KERNEL_B
59    elif active_root == _KERNEL_B['root']:
60        return _KERNEL_B, _KERNEL_A
61    else:
62        raise Exception('Encountered unknown root partition: %s' % active_root)
63
64def get_next_kernel(host=None):
65    """
66    Return the kernel that has priority for the next boot.
67
68    @param host: The DUT to execute the command on. None to execute locally.
69
70    """
71    priority_a = _cgpt('-P', _KERNEL_A, host)
72    priority_b = _cgpt('-P', _KERNEL_B, host)
73    return _KERNEL_A if priority_a > priority_b else _KERNEL_B
74
75def get_kernel_success(kernel, host=None):
76    """
77    Return boolean success flag for the specified kernel.
78
79    @param kernel: Information of the given kernel, either _KERNEL_A
80                   or _KERNEL_B.
81    @param host: The DUT to execute the command on. None to execute locally.
82
83    """
84    return _cgpt('-S', kernel, host) != 0
85
86def get_kernel_tries(kernel, host=None):
87    """Return tries count for the specified kernel.
88
89    @param kernel: Information of the given kernel, either _KERNEL_A
90                   or _KERNEL_B.
91    @param host: The DUT to execute the command on. None to execute locally.
92
93    """
94    return _cgpt('-T', kernel, host)
95
96def verify_kernel_state_after_update(host=None):
97    """
98    Ensure the next kernel to boot is the currently inactive kernel.
99
100    This is useful for checking after completing an update.
101
102    @param host: The DUT to execute the command on. None to execute locally.
103    @returns the inactive kernel.
104
105    """
106    inactive_kernel = get_kernel_state(host)[1]
107    next_kernel = get_next_kernel(host)
108    if next_kernel != inactive_kernel:
109        raise Exception('The kernel for next boot is %s, but %s was expected.'
110                        % (next_kernel['name'], inactive_kernel['name']))
111    return inactive_kernel
112
113def verify_boot_expectations(expected_kernel, error_message=_BOOT_ERR_MSG,
114                             host=None):
115    """Verifies that we fully booted into the expected kernel state.
116
117    This method both verifies that we booted using the correct kernel
118    state and that the OS has marked the kernel as good.
119
120    @param expected_kernel: kernel that we are verifying with,
121                            eg I expect to be booted onto partition 4.
122    @param error_message: string include in except message text
123                          if we booted with the wrong partition.
124    @param host: The DUT to execute the command on. None to execute locally.
125
126    """
127    # Figure out the newly active kernel.
128    active_kernel = get_kernel_state(host)[0]
129
130    if active_kernel != expected_kernel:
131        # Kernel crash reports should be wiped between test runs, but
132        # may persist from earlier parts of the test, or from problems
133        # with provisioning.
134        #
135        # Kernel crash reports will NOT be present if the crash happened
136        # before encrypted stateful is mounted.
137        kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
138        if kernel_crashes:
139            error_message += ': kernel_crash'
140            logging.debug('Found %d kernel crash reports:',
141                          len(kernel_crashes))
142            # The crash names contain timestamps that may be useful:
143            #   kernel.20131207.005945.12345.0.kcrash
144            for crash in kernel_crashes:
145                logging.debug('  %s', os.path.basename(crash))
146
147        # Print out some information to make it easier to debug
148        # the rollback.
149        logging.debug('Dumping partition table.')
150        _run('cgpt show $(rootdev -s -d)', host)
151        logging.debug('Dumping crossystem for firmware debugging.')
152        _run('crossystem --all', host)
153        raise Exception(error_message)
154
155    # Make sure chromeos-setgoodkernel runs.
156    try:
157        utils.poll_for_condition(
158            lambda: (get_kernel_tries(active_kernel, host) == 0
159                     and get_kernel_success(active_kernel, host)),
160            timeout=_KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
161    except Exception:
162        services_status = _run(['status', 'system-services'], host).stdout
163        if services_status != 'system-services start/running\n':
164            raise Exception('Chrome failed to reach login screen')
165        else:
166            raise Exception('update-engine failed to call '
167                            'chromeos-setgoodkernel')
168