168 lines
5.9 KiB
Python
168 lines
5.9 KiB
Python
# Copyright 2020 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import glob
|
|
import logging
|
|
import os
|
|
import re
|
|
|
|
from autotest_lib.client.bin import utils
|
|
|
|
_KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3}
|
|
_KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5}
|
|
|
|
# Time to wait for new kernel to be marked successful after auto update.
|
|
_KERNEL_UPDATE_TIMEOUT = 120
|
|
|
|
_BOOT_ERR_MSG = 'The active image slot did not change after the update.'
|
|
|
|
def _run(cmd, host=None):
|
|
"""
|
|
Function to execue commands.
|
|
|
|
This allows the util to be used by client and server tests.
|
|
|
|
@param cmd: The command to execute
|
|
@param host: The host to use in a server test. None to use utils.run.
|
|
|
|
"""
|
|
if host is not None:
|
|
return host.run(cmd)
|
|
else:
|
|
return utils.run(cmd)
|
|
|
|
def _cgpt(flag, kernel, host=None):
|
|
"""
|
|
Helper function to return numeric cgpt value.
|
|
|
|
@param flag: Additional flag to pass to cgpt
|
|
@param kernel: The kernel we want to interact with.
|
|
@param host: The DUT to execute the command on. None to execute locally.
|
|
|
|
"""
|
|
rootdev = _run(['rootdev','-s', '-d'], host).stdout.strip()
|
|
return int(_run(['cgpt', 'show', '-n', '-i', str(kernel['kernel']), flag,
|
|
rootdev], host).stdout.strip())
|
|
|
|
def get_kernel_state(host=None):
|
|
"""
|
|
Returns the (<active>, <inactive>) kernel state as a pair.
|
|
|
|
@param host: The DUT to execute the command on. None to execute locally.
|
|
|
|
"""
|
|
rootdev = _run(['rootdev','-s'], host).stdout.strip()
|
|
active_root = int(re.findall('\d+\Z', rootdev)[0])
|
|
if active_root == _KERNEL_A['root']:
|
|
return _KERNEL_A, _KERNEL_B
|
|
elif active_root == _KERNEL_B['root']:
|
|
return _KERNEL_B, _KERNEL_A
|
|
else:
|
|
raise Exception('Encountered unknown root partition: %s' % active_root)
|
|
|
|
def get_next_kernel(host=None):
|
|
"""
|
|
Return the kernel that has priority for the next boot.
|
|
|
|
@param host: The DUT to execute the command on. None to execute locally.
|
|
|
|
"""
|
|
priority_a = _cgpt('-P', _KERNEL_A, host)
|
|
priority_b = _cgpt('-P', _KERNEL_B, host)
|
|
return _KERNEL_A if priority_a > priority_b else _KERNEL_B
|
|
|
|
def get_kernel_success(kernel, host=None):
|
|
"""
|
|
Return boolean success flag for the specified kernel.
|
|
|
|
@param kernel: Information of the given kernel, either _KERNEL_A
|
|
or _KERNEL_B.
|
|
@param host: The DUT to execute the command on. None to execute locally.
|
|
|
|
"""
|
|
return _cgpt('-S', kernel, host) != 0
|
|
|
|
def get_kernel_tries(kernel, host=None):
|
|
"""Return tries count for the specified kernel.
|
|
|
|
@param kernel: Information of the given kernel, either _KERNEL_A
|
|
or _KERNEL_B.
|
|
@param host: The DUT to execute the command on. None to execute locally.
|
|
|
|
"""
|
|
return _cgpt('-T', kernel, host)
|
|
|
|
def verify_kernel_state_after_update(host=None):
|
|
"""
|
|
Ensure the next kernel to boot is the currently inactive kernel.
|
|
|
|
This is useful for checking after completing an update.
|
|
|
|
@param host: The DUT to execute the command on. None to execute locally.
|
|
@returns the inactive kernel.
|
|
|
|
"""
|
|
inactive_kernel = get_kernel_state(host)[1]
|
|
next_kernel = get_next_kernel(host)
|
|
if next_kernel != inactive_kernel:
|
|
raise Exception('The kernel for next boot is %s, but %s was expected.'
|
|
% (next_kernel['name'], inactive_kernel['name']))
|
|
return inactive_kernel
|
|
|
|
def verify_boot_expectations(expected_kernel, error_message=_BOOT_ERR_MSG,
|
|
host=None):
|
|
"""Verifies that we fully booted into the expected kernel state.
|
|
|
|
This method both verifies that we booted using the correct kernel
|
|
state and that the OS has marked the kernel as good.
|
|
|
|
@param expected_kernel: kernel that we are verifying with,
|
|
eg I expect to be booted onto partition 4.
|
|
@param error_message: string include in except message text
|
|
if we booted with the wrong partition.
|
|
@param host: The DUT to execute the command on. None to execute locally.
|
|
|
|
"""
|
|
# Figure out the newly active kernel.
|
|
active_kernel = get_kernel_state(host)[0]
|
|
|
|
if active_kernel != expected_kernel:
|
|
# Kernel crash reports should be wiped between test runs, but
|
|
# may persist from earlier parts of the test, or from problems
|
|
# with provisioning.
|
|
#
|
|
# Kernel crash reports will NOT be present if the crash happened
|
|
# before encrypted stateful is mounted.
|
|
kernel_crashes = glob.glob('/var/spool/crash/kernel.*.kcrash')
|
|
if kernel_crashes:
|
|
error_message += ': kernel_crash'
|
|
logging.debug('Found %d kernel crash reports:',
|
|
len(kernel_crashes))
|
|
# The crash names contain timestamps that may be useful:
|
|
# kernel.20131207.005945.12345.0.kcrash
|
|
for crash in kernel_crashes:
|
|
logging.debug(' %s', os.path.basename(crash))
|
|
|
|
# Print out some information to make it easier to debug
|
|
# the rollback.
|
|
logging.debug('Dumping partition table.')
|
|
_run('cgpt show $(rootdev -s -d)', host)
|
|
logging.debug('Dumping crossystem for firmware debugging.')
|
|
_run('crossystem --all', host)
|
|
raise Exception(error_message)
|
|
|
|
# Make sure chromeos-setgoodkernel runs.
|
|
try:
|
|
utils.poll_for_condition(
|
|
lambda: (get_kernel_tries(active_kernel, host) == 0
|
|
and get_kernel_success(active_kernel, host)),
|
|
timeout=_KERNEL_UPDATE_TIMEOUT, sleep_interval=5)
|
|
except Exception:
|
|
services_status = _run(['status', 'system-services'], host).stdout
|
|
if services_status != 'system-services start/running\n':
|
|
raise Exception('Chrome failed to reach login screen')
|
|
else:
|
|
raise Exception('update-engine failed to call '
|
|
'chromeos-setgoodkernel')
|