blob: 564e3b5f87b79e4b4a553a9d07b3960f305990fa [file] [log] [blame]
# Lint as: python3
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import os
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import kernel_utils
from autotest_lib.server.cros.minios import minios_test
class autoupdate_MiniOS(minios_test.MiniOsTest):
"""Tests MiniOS update. """
version = 1
_EXCLUSION_PREFS_DIR = "exclusion"
_MINIOS_BACKUP = "minios.gz"
_MINIOS_PREFS_DIR = "minios"
_TMP_DIR = "/usr/local/tmp"
_MINIOS_BOOT_EXCLUDED_MODELS = ("tomato", )
def initialize(self, host=None, wifi_configs=None, running_at_desk=None):
"""
Clear test related prefs on the DUT before starting the test.
@param host: The DUT we will be running on.
@param wifi_configs: List containing access point configuration dict and
wifi client configuration dict.
@param running_at_desk: indicates test is run locally from a
workstation.
"""
super(autoupdate_MiniOS,
self).initialize(host=host,
wifi_configs=wifi_configs,
running_at_desk=running_at_desk,
skip_provisioning='true')
self._remove_minios_update_prefs()
def cleanup(self):
try:
# Restore active miniOS partition back to the original one.
self._restore_minios(self._active_minios['partition'])
kernel_utils.set_minios_priority(
host=self._host, partition=self._active_minios['name'])
except AttributeError:
logging.error("Skip restoring minios priority as the original "
"minios partition is not backed-up.")
self._save_extra_update_engine_logs(number_of_logs=2)
self._remove_minios_update_prefs()
# Run cleanup from base class and reboot after restoring active miniOS
# partition. It is needed for update_engine to get the updated
# minios_priority value because crossystem lib caches NVRAM contents.
super(autoupdate_MiniOS, self).cleanup()
def _get_partition_path(self, partition):
"""Get the device file path of a partition.
@param partition: The partition number.
"""
rootdev = self._run(['rootdev', '-s', '-d']).stdout.strip()
sep = "p" if rootdev[-1].isdigit() else ""
return rootdev + sep + str(partition)
def _md5_hash(self, path):
"""Get the md5 hash of a file on DUT.
@param path: The file path.
"""
return self._run(['md5sum', '-b', path],
ignore_status=True).stdout.split()[0]
def _backup_minios(self, partition):
"""Backup given miniOS partition and get its hash.
@param partition: The miniOS partition number.
"""
part = self._get_partition_path(partition)
# Get miniOS partition hash.
self._minios_hash = self._md5_hash(part)
logging.info(
"Backing up the original active miniOS partition, md5sum: %s",
self._minios_hash)
# Backup miniOS partition.
backup_src = os.path.join(self._TMP_DIR, self._MINIOS_BACKUP)
backup_dst = os.path.join(self.resultsdir, self._MINIOS_BACKUP)
self._run(['mkdir', '-p', self._TMP_DIR], ignore_status=True)
self._run(['dd', f"if={part}", '|', 'gzip', '>', backup_src],
ignore_status=True)
self._get_file(backup_src, backup_dst)
def _restore_minios(self, partition):
"""Verify miniOS hash and restore backed-up miniOS if needed.
@param partition: The miniOS partition number.
"""
restore_src = os.path.join(self.resultsdir, self._MINIOS_BACKUP)
if not os.path.exists(restore_src):
logging.info("The miniOS partition backup does not exists, "
"skip restoring.")
return
part = self._get_partition_path(partition)
# Verify miniOS partition hash.
hash = self._md5_hash(part)
if hash == self._minios_hash:
logging.info("The active miniOS partition is unchanged, "
"leave it as it is.")
else:
logging.info(
"The active miniOS partition hash is %s, different from "
"the original. Restoring miniOS partition.", hash)
# Restore miniOS partition.
with open(restore_src, 'r') as f:
self._run(['gzip', '-d', '|', 'dd', f"of={part}"],
stdin=f,
ignore_status=True)
os.remove(restore_src)
def _remove_minios_update_prefs(self):
for pref in ((self._EXCLUSION_PREFS_DIR, True),
(self._MINIOS_PREFS_DIR, True)):
self._remove_update_engine_pref(pref=pref[0], is_dir=pref[1])
def _setup_minios_update(self,
verify_minios_boot,
with_exclusion=False,
build=None):
# Get payload URL for the MiniOS update.
# We'll always need a full payload for MiniOS update.
payload_url = self.get_payload_for_nebraska(
build=build,
full_payload=True,
payload_type=self._PAYLOAD_TYPE.MINIOS,
public_bucket=self._running_at_desk)
self._payload_urls.append(payload_url)
# Test that MiniOS payload can be excluded by creating a pref file.
# This simulates that the update engine tries to exclude MiniOS payload
# after getting certain types of MiniOS update failure.
if with_exclusion:
self._create_update_engine_pref(
pref_name=self._get_exclusion_name(payload_url),
sub_dir=self._EXCLUSION_PREFS_DIR)
# MiniOS booting to be verified.
if verify_minios_boot:
self._verifications.append(self._verify_boot_minios)
def _setup_cros_update(self, has_update, build=None):
if has_update:
# Get payload URL for the platform (OS) update.
self._payload_urls.append(
self.get_payload_for_nebraska(
build=build,
full_payload=self._full_payload,
public_bucket=self._running_at_desk))
# Platform (OS) update to be verified.
self._verifications.append(lambda: self._verify_cros_update(
updated=has_update))
def _setup_dlc_update(self, build=None):
# Payload URLs for sample-dlc, a test DLC package.
# We'll always need a full payload for DLC installation,
# and optionally a delta payload if required by the test.
self._payload_urls.append(
self.get_payload_for_nebraska(
build=build,
full_payload=True,
payload_type=self._PAYLOAD_TYPE.DLC,
public_bucket=self._running_at_desk))
if not self._full_payload:
self._payload_urls.append(
self.get_payload_for_nebraska(
build=build,
full_payload=False,
payload_type=self._PAYLOAD_TYPE.DLC,
public_bucket=self._running_at_desk))
# DLC update to be verified.
self._verifications.append(self._verify_dlc_update)
def _verify_cros_update(self, updated):
if updated:
# Verify the platform (OS) update completed successfully.
kernel_utils.verify_boot_expectations(self._inactive_cros,
host=self._host)
rootfs_hostlog, _ = self._create_hostlog_files()
self.verify_update_events(self._FORCED_UPDATE, rootfs_hostlog)
else:
# Verify the Platform (OS) boot expectation unchanged.
kernel_utils.verify_boot_expectations(self._active_cros,
host=self._host)
def _verify_dlc_update(self):
# Verify the DLC update completed successfully.
dlc_rootfs_hostlog, _ = self._create_dlc_hostlog_files()
logging.info('Checking DLC update events')
self.verify_update_events(
self._FORCED_UPDATE,
dlc_rootfs_hostlog[self._dlc_util._SAMPLE_DLC_ID])
# Verify the DLC was successfully installed.
try:
self._dlc_util.disable_preloaded([self._dlc_util._SAMPLE_DLC_ID])
self._dlc_util.install(self._dlc_util._SAMPLE_DLC_ID,
omaha_url='fake_url')
finally:
self._dlc_util.restore_preloaded([self._dlc_util._SAMPLE_DLC_ID])
if not self._dlc_util.is_installed(self._dlc_util._SAMPLE_DLC_ID):
raise error.TestFail('Test DLC was not installed.')
def _verify_boot_minios(self):
logging.info('Trying boot into updated miniOS.')
self._boot_minios()
logging.info('The updated miniOS is verified to be bootable, '
'rebooting back to chromeOS.')
self._host.reboot()
def run_once(self,
full_payload=True,
with_os=False,
with_dlc=False,
with_exclusion=False,
running_at_desk=False,
build=None,
m2n=False):
"""
Tests that we can successfully update MiniOS along with the OS.
@param full_payload: True for full OS and DLC payloads. False for delta.
@param with_os: True for MiniOS update along with Platform (OS)
update. False for MiniOS only update.
@param with_dlc: True for MiniOS update with Platform (OS) and DLC.
False for turning off DLC update.
@param with_exclusion: True for excluding MiniOS payload.
@param running_at_desk: Indicates test is run locally from a
workstation.
@param build: An optional parameter to specify the target build for the
update when running locally. If no build is supplied, the
current version on the DUT will be used.
@m2n: M -> N update. This means we install the current stable version
of this board before updating to ToT.
"""
self._full_payload = full_payload
self._running_at_desk = running_at_desk
if not with_os and with_dlc:
logging.info("DLC only updates with the platform (OS), "
"automatically set with_os to True.")
with_os = True
if m2n:
skip_board_suffixes = ['-kernelnext', '-manatee']
self.provision_dut(public_bucket=running_at_desk,
skip_board_suffixes=skip_board_suffixes,
with_minios=True)
# Record DUT state before the update.
(self._active_cros,
self._inactive_cros) = kernel_utils.get_kernel_state(self._host)
(self._active_minios,
self._inactive_minios) = kernel_utils.get_minios_priority(self._host)
# Backup the original active miniOS partition before test.
self._backup_minios(self._active_minios['partition'])
minios_update = with_os and not with_exclusion
# MiniOS update to be verified.
self._verifications = [
lambda: kernel_utils.verify_minios_priority_after_update(
self._host,
expected=self._inactive_minios['name']
if minios_update else self._active_minios['name'])
]
# Get payload URLs and setup tests.
self._payload_urls = []
self._setup_cros_update(has_update=with_os, build=build)
if with_dlc:
self._setup_dlc_update(build=build)
verify_minios_boot = (minios_update
and not self._host.get_model_from_cros_config()
in self._MINIOS_BOOT_EXCLUDED_MODELS)
self._setup_minios_update(verify_minios_boot=verify_minios_boot,
with_exclusion=with_exclusion,
build=build)
# Update MiniOS.
if with_dlc:
self._run_client_test_and_check_result(
'autoupdate_InstallAndUpdateDLC',
payload_urls=self._payload_urls,
allow_failure=not with_os)
else:
self._run_client_test_and_check_result(
'autoupdate_CannedOmahaUpdate',
payload_url=self._payload_urls,
allow_failure=not with_os)
if with_os:
self._host.reboot()
# Verify updates.
for verify in self._verifications:
verify()