blob: acd2cefac9385ca524c6000c38ba73fb6f00074e [file] [log] [blame]
# Lint as: python2, python3
# Copyright 2022 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.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import json
import logging
import os
import re
import time
from autotest_lib.client.common_lib import error
from autotest_lib.server.cros.minios import minios_util
from autotest_lib.server.cros.update_engine import update_engine_test
class MiniOsTest(update_engine_test.UpdateEngineTest):
"""
Base class that sets up helper objects/functions for NBR tests.
"""
_MINIOS_CLIENT_CMD = 'minios_client'
_MINIOS_KERNEL_FLAG = 'cros_minios'
# Period to wait for firmware screen in seconds.
# Value based on Brya, which is the slowest so far.
_FIRMWARE_SCREEN_TIMEOUT = 30
# Number of times to attempt booting into MiniOS.
_MINIOS_BOOT_MAX_ATTEMPTS = 3
# Timeout periods, given in seconds.
_MINIOS_SHUTDOWN_TIMEOUT = 30
# Number of seconds to wait for the host to boot into MiniOS. Should always
# be greater than `_FIRMWARE_SCREEN_TIMEOUT`.
_MINIOS_WAIT_UP_TIME_SECONDS = 120
# Version reported to OMAHA/NEBRASKA for recovery.
_RECOVERY_VERSION = '0.0.0.0'
# Files used by the tests.
_DEPENDENCY_DIRS = ['bin', 'lib', 'lib64', 'libexec']
_DEPENDENCY_INSTALL_DIR = '/usr/local'
_MINIOS_TEMP_STATEFUL_DIR = '/usr/local/tmp/stateful'
_STATEFUL_DEV_IMAGE_NAME = 'dev_image_new'
def initialize(self, host):
"""
Sets default variables for the test.
@param host: The DUT we will be running on.
"""
super(MiniOsTest, self).initialize(host)
self._nebraska = None
self._servo = host.servo
self._servo.initialize_dut()
def cleanup(self):
"""Clean up minios autotests."""
if self._nebraska:
self._nebraska.stop()
super(MiniOsTest, self).cleanup()
# Make sure to reboot DUT into CroS in case of failures.
self._host.reboot()
def _boot_minios(self):
"""Boot the DUT into MiniOS."""
# Turn off usbkey to avoid booting into usb-recovery image.
self._servo.switch_usbkey('off')
psc = self._servo.get_power_state_controller()
psc.power_off()
psc.power_on(psc.REC_ON)
self._host.test_wait_for_shutdown(self._MINIOS_SHUTDOWN_TIMEOUT)
logging.info('Waiting for firmware screen')
time.sleep(self._FIRMWARE_SCREEN_TIMEOUT)
# Attempt multiple times to boot into MiniOS. If all attempts fail then
# this is some kind of firmware issue. Since we failed to boot an OS use
# servo to reset the unit and then report test failure.
attempts = 0
minios_is_up = False
while not minios_is_up and attempts < self._MINIOS_BOOT_MAX_ATTEMPTS:
# Use Ctrl+R shortcut to boot 'MiniOS
logging.info('Try to boot MiniOS')
self._servo.ctrl_r()
minios_is_up = self._host.wait_up(
timeout=self._MINIOS_WAIT_UP_TIME_SECONDS,
host_is_down=True)
attempts += 1
if minios_is_up:
# If mainfw_type is recovery then we are in MiniOS.
mainfw_type = self._host.run_output('crossystem mainfw_type')
if mainfw_type != 'recovery':
raise error.TestError(
'Boot to MiniOS - invalid firmware: %s.' % mainfw_type)
# There are multiple types of recovery images, make sure we booted
# into minios.
pattern = r'\b%s\b' % self._MINIOS_KERNEL_FLAG
if not re.search(pattern, self._host.get_cmdline()):
raise error.TestError(
'Boot to MiniOS - recovery image is not minios.')
else:
# Try to not leave unit on recovery firmware screen.
self._host.power_cycle()
raise error.TestError('Boot to MiniOS failed.')
def _create_minios_hostlog(self):
"""Create the minios hostlog file.
To ensure the recovery was successful we need to compare the update
events against expected update events. This function creates the hostlog
for minios before the recovery reboots the DUT.
"""
# Check that update logs exist.
if len(self._get_update_engine_logs()) < 1:
err_msg = 'update_engine logs are missing. Cannot verify recovery.'
raise error.TestFail(err_msg)
# Download the logs instead of reading it over the network since it will
# disappear after MiniOS reboots the DUT.
logfile = os.path.join(self.resultsdir, 'minios_update_engine.log')
self._host.get_file(self._UPDATE_ENGINE_LOG, logfile)
logfile_content = None
with open(logfile) as f:
logfile_content = f.read()
minios_hostlog = os.path.join(self.resultsdir, 'hostlog_minios')
with open(minios_hostlog, 'w') as fp:
# There are four expected hostlog events during recovery.
extract_logs = self._extract_request_logs(logfile_content)
json.dump(extract_logs[-4:], fp)
return minios_hostlog
def _install_test_dependencies(self, public_bucket=False):
"""
Install test dependencies from a downloaded stateful archive.
@param public_bucket: True to download stateful from a public bucket.
"""
if not self._job_repo_url:
raise error.TestError('No job repo url set.')
statefuldev_url = self._stage_stateful(public_bucket)
logging.info('Installing dependencies from %s', statefuldev_url)
# Create destination directories.
minios_dev_image_dir = os.path.join(self._MINIOS_TEMP_STATEFUL_DIR,
self._STATEFUL_DEV_IMAGE_NAME)
install_dirs = [
os.path.join(self._DEPENDENCY_INSTALL_DIR, dir)
for dir in self._DEPENDENCY_DIRS
]
self._run(['mkdir', '-p', minios_dev_image_dir] + install_dirs)
# Symlink the install dirs into the staging destination.
for dir in install_dirs:
self._run(['ln', '-s', dir, minios_dev_image_dir])
# Generate the list of stateful archive members that we want to extract.
members = [
os.path.join(self._STATEFUL_DEV_IMAGE_NAME, dir)
for dir in self._DEPENDENCY_DIRS
]
try:
self._download_and_extract_stateful(statefuldev_url,
self._MINIOS_TEMP_STATEFUL_DIR,
members=members,
keep_symlinks=True)
except error.AutoservRunError as e:
err_str = 'Failed to install the test dependencies'
raise error.TestFail('%s: %s' % (err_str, str(e)))
self._setup_python_symlinks()
# Clean-up unused files to save memory.
self._run(['rm', '-rf', self._MINIOS_TEMP_STATEFUL_DIR])
def _setup_python_symlinks(self):
"""
Create symlinks in the root to point to all python paths in /usr/local
for stateful installed python to work. This is needed because Gentoo
creates wrappers with hardcoded paths to the root (e.g. python-exec).
"""
for path in self._DEPENDENCY_DIRS:
self._run([
'find',
os.path.join(self._DEPENDENCY_INSTALL_DIR, path),
'-maxdepth', '1', '\(', '-name', 'python*', '-o', '-name',
'portage', '\)', '-exec', 'ln', '-s', '{}',
os.path.join('/usr', path), '\;'
])
def _start_nebraska(self, payload_url=None):
"""
Initialize and start nebraska on the DUT.
@param payload_url: The payload to served by nebraska.
"""
if not self._nebraska:
self._nebraska = minios_util.NebraskaService(
self, self._host, payload_url)
self._nebraska.start()
def _verify_reboot(self, old_boot_id):
"""
Verify that the unit rebooted using the boot_id.
@param old_boot_id A boot id value obtained before the
reboot.
"""
self._host.test_wait_for_shutdown(self._MINIOS_SHUTDOWN_TIMEOUT)
self._host.test_wait_for_boot(old_boot_id)