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