1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright 2022 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Lifrom __future__ import absolute_import 7*9c5db199SXin Lifrom __future__ import division 8*9c5db199SXin Lifrom __future__ import print_function 9*9c5db199SXin Li 10*9c5db199SXin Liimport json 11*9c5db199SXin Liimport logging 12*9c5db199SXin Liimport os 13*9c5db199SXin Liimport re 14*9c5db199SXin Liimport time 15*9c5db199SXin Li 16*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 17*9c5db199SXin Lifrom autotest_lib.server.cros.minios import minios_util 18*9c5db199SXin Lifrom autotest_lib.server.cros.update_engine import update_engine_test 19*9c5db199SXin Li 20*9c5db199SXin Li 21*9c5db199SXin Liclass MiniOsTest(update_engine_test.UpdateEngineTest): 22*9c5db199SXin Li """ 23*9c5db199SXin Li Base class that sets up helper objects/functions for NBR tests. 24*9c5db199SXin Li 25*9c5db199SXin Li """ 26*9c5db199SXin Li 27*9c5db199SXin Li _MINIOS_CLIENT_CMD = 'minios_client' 28*9c5db199SXin Li _MINIOS_KERNEL_FLAG = 'cros_minios' 29*9c5db199SXin Li 30*9c5db199SXin Li # Period to wait for firmware screen in seconds. 31*9c5db199SXin Li # Value based on Brya, which is the slowest so far. 32*9c5db199SXin Li _FIRMWARE_SCREEN_TIMEOUT = 30 33*9c5db199SXin Li 34*9c5db199SXin Li # Number of times to attempt booting into MiniOS. 35*9c5db199SXin Li _MINIOS_BOOT_MAX_ATTEMPTS = 3 36*9c5db199SXin Li 37*9c5db199SXin Li # Timeout periods, given in seconds. 38*9c5db199SXin Li _MINIOS_SHUTDOWN_TIMEOUT = 30 39*9c5db199SXin Li 40*9c5db199SXin Li # Number of seconds to wait for the host to boot into MiniOS. Should always 41*9c5db199SXin Li # be greater than `_FIRMWARE_SCREEN_TIMEOUT`. 42*9c5db199SXin Li _MINIOS_WAIT_UP_TIME_SECONDS = 120 43*9c5db199SXin Li 44*9c5db199SXin Li # Version reported to OMAHA/NEBRASKA for recovery. 45*9c5db199SXin Li _RECOVERY_VERSION = '0.0.0.0' 46*9c5db199SXin Li 47*9c5db199SXin Li # Files used by the tests. 48*9c5db199SXin Li _DEPENDENCY_DIRS = ['bin', 'lib', 'lib64', 'libexec'] 49*9c5db199SXin Li _DEPENDENCY_INSTALL_DIR = '/usr/local' 50*9c5db199SXin Li _MINIOS_TEMP_STATEFUL_DIR = '/usr/local/tmp/stateful' 51*9c5db199SXin Li _STATEFUL_DEV_IMAGE_NAME = 'dev_image_new' 52*9c5db199SXin Li 53*9c5db199SXin Li def initialize(self, host): 54*9c5db199SXin Li """ 55*9c5db199SXin Li Sets default variables for the test. 56*9c5db199SXin Li 57*9c5db199SXin Li @param host: The DUT we will be running on. 58*9c5db199SXin Li 59*9c5db199SXin Li """ 60*9c5db199SXin Li super(MiniOsTest, self).initialize(host) 61*9c5db199SXin Li self._nebraska = None 62*9c5db199SXin Li self._servo = host.servo 63*9c5db199SXin Li self._servo.initialize_dut() 64*9c5db199SXin Li 65*9c5db199SXin Li def cleanup(self): 66*9c5db199SXin Li """Clean up minios autotests.""" 67*9c5db199SXin Li if self._nebraska: 68*9c5db199SXin Li self._nebraska.stop() 69*9c5db199SXin Li super(MiniOsTest, self).cleanup() 70*9c5db199SXin Li # Make sure to reboot DUT into CroS in case of failures. 71*9c5db199SXin Li self._host.reboot() 72*9c5db199SXin Li 73*9c5db199SXin Li def _boot_minios(self): 74*9c5db199SXin Li """Boot the DUT into MiniOS.""" 75*9c5db199SXin Li # Turn off usbkey to avoid booting into usb-recovery image. 76*9c5db199SXin Li self._servo.switch_usbkey('off') 77*9c5db199SXin Li psc = self._servo.get_power_state_controller() 78*9c5db199SXin Li psc.power_off() 79*9c5db199SXin Li psc.power_on(psc.REC_ON) 80*9c5db199SXin Li self._host.test_wait_for_shutdown(self._MINIOS_SHUTDOWN_TIMEOUT) 81*9c5db199SXin Li logging.info('Waiting for firmware screen') 82*9c5db199SXin Li time.sleep(self._FIRMWARE_SCREEN_TIMEOUT) 83*9c5db199SXin Li 84*9c5db199SXin Li # Attempt multiple times to boot into MiniOS. If all attempts fail then 85*9c5db199SXin Li # this is some kind of firmware issue. Since we failed to boot an OS use 86*9c5db199SXin Li # servo to reset the unit and then report test failure. 87*9c5db199SXin Li attempts = 0 88*9c5db199SXin Li minios_is_up = False 89*9c5db199SXin Li while not minios_is_up and attempts < self._MINIOS_BOOT_MAX_ATTEMPTS: 90*9c5db199SXin Li # Use Ctrl+R shortcut to boot 'MiniOS 91*9c5db199SXin Li logging.info('Try to boot MiniOS') 92*9c5db199SXin Li self._servo.ctrl_r() 93*9c5db199SXin Li minios_is_up = self._host.wait_up( 94*9c5db199SXin Li timeout=self._MINIOS_WAIT_UP_TIME_SECONDS, 95*9c5db199SXin Li host_is_down=True) 96*9c5db199SXin Li attempts += 1 97*9c5db199SXin Li 98*9c5db199SXin Li if minios_is_up: 99*9c5db199SXin Li # If mainfw_type is recovery then we are in MiniOS. 100*9c5db199SXin Li mainfw_type = self._host.run_output('crossystem mainfw_type') 101*9c5db199SXin Li if mainfw_type != 'recovery': 102*9c5db199SXin Li raise error.TestError( 103*9c5db199SXin Li 'Boot to MiniOS - invalid firmware: %s.' % mainfw_type) 104*9c5db199SXin Li # There are multiple types of recovery images, make sure we booted 105*9c5db199SXin Li # into minios. 106*9c5db199SXin Li pattern = r'\b%s\b' % self._MINIOS_KERNEL_FLAG 107*9c5db199SXin Li if not re.search(pattern, self._host.get_cmdline()): 108*9c5db199SXin Li raise error.TestError( 109*9c5db199SXin Li 'Boot to MiniOS - recovery image is not minios.') 110*9c5db199SXin Li else: 111*9c5db199SXin Li # Try to not leave unit on recovery firmware screen. 112*9c5db199SXin Li self._host.power_cycle() 113*9c5db199SXin Li raise error.TestError('Boot to MiniOS failed.') 114*9c5db199SXin Li 115*9c5db199SXin Li def _create_minios_hostlog(self): 116*9c5db199SXin Li """Create the minios hostlog file. 117*9c5db199SXin Li 118*9c5db199SXin Li To ensure the recovery was successful we need to compare the update 119*9c5db199SXin Li events against expected update events. This function creates the hostlog 120*9c5db199SXin Li for minios before the recovery reboots the DUT. 121*9c5db199SXin Li 122*9c5db199SXin Li """ 123*9c5db199SXin Li # Check that update logs exist. 124*9c5db199SXin Li if len(self._get_update_engine_logs()) < 1: 125*9c5db199SXin Li err_msg = 'update_engine logs are missing. Cannot verify recovery.' 126*9c5db199SXin Li raise error.TestFail(err_msg) 127*9c5db199SXin Li 128*9c5db199SXin Li # Download the logs instead of reading it over the network since it will 129*9c5db199SXin Li # disappear after MiniOS reboots the DUT. 130*9c5db199SXin Li logfile = os.path.join(self.resultsdir, 'minios_update_engine.log') 131*9c5db199SXin Li self._host.get_file(self._UPDATE_ENGINE_LOG, logfile) 132*9c5db199SXin Li logfile_content = None 133*9c5db199SXin Li with open(logfile) as f: 134*9c5db199SXin Li logfile_content = f.read() 135*9c5db199SXin Li minios_hostlog = os.path.join(self.resultsdir, 'hostlog_minios') 136*9c5db199SXin Li with open(minios_hostlog, 'w') as fp: 137*9c5db199SXin Li # There are four expected hostlog events during recovery. 138*9c5db199SXin Li extract_logs = self._extract_request_logs(logfile_content) 139*9c5db199SXin Li json.dump(extract_logs[-4:], fp) 140*9c5db199SXin Li return minios_hostlog 141*9c5db199SXin Li 142*9c5db199SXin Li def _install_test_dependencies(self, public_bucket=False): 143*9c5db199SXin Li """ 144*9c5db199SXin Li Install test dependencies from a downloaded stateful archive. 145*9c5db199SXin Li 146*9c5db199SXin Li @param public_bucket: True to download stateful from a public bucket. 147*9c5db199SXin Li 148*9c5db199SXin Li """ 149*9c5db199SXin Li if not self._job_repo_url: 150*9c5db199SXin Li raise error.TestError('No job repo url set.') 151*9c5db199SXin Li 152*9c5db199SXin Li statefuldev_url = self._stage_stateful(public_bucket) 153*9c5db199SXin Li logging.info('Installing dependencies from %s', statefuldev_url) 154*9c5db199SXin Li 155*9c5db199SXin Li # Create destination directories. 156*9c5db199SXin Li minios_dev_image_dir = os.path.join(self._MINIOS_TEMP_STATEFUL_DIR, 157*9c5db199SXin Li self._STATEFUL_DEV_IMAGE_NAME) 158*9c5db199SXin Li install_dirs = [ 159*9c5db199SXin Li os.path.join(self._DEPENDENCY_INSTALL_DIR, dir) 160*9c5db199SXin Li for dir in self._DEPENDENCY_DIRS 161*9c5db199SXin Li ] 162*9c5db199SXin Li self._run(['mkdir', '-p', minios_dev_image_dir] + install_dirs) 163*9c5db199SXin Li # Symlink the install dirs into the staging destination. 164*9c5db199SXin Li for dir in install_dirs: 165*9c5db199SXin Li self._run(['ln', '-s', dir, minios_dev_image_dir]) 166*9c5db199SXin Li 167*9c5db199SXin Li # Generate the list of stateful archive members that we want to extract. 168*9c5db199SXin Li members = [ 169*9c5db199SXin Li os.path.join(self._STATEFUL_DEV_IMAGE_NAME, dir) 170*9c5db199SXin Li for dir in self._DEPENDENCY_DIRS 171*9c5db199SXin Li ] 172*9c5db199SXin Li try: 173*9c5db199SXin Li self._download_and_extract_stateful(statefuldev_url, 174*9c5db199SXin Li self._MINIOS_TEMP_STATEFUL_DIR, 175*9c5db199SXin Li members=members, 176*9c5db199SXin Li keep_symlinks=True) 177*9c5db199SXin Li except error.AutoservRunError as e: 178*9c5db199SXin Li err_str = 'Failed to install the test dependencies' 179*9c5db199SXin Li raise error.TestFail('%s: %s' % (err_str, str(e))) 180*9c5db199SXin Li 181*9c5db199SXin Li self._setup_python_symlinks() 182*9c5db199SXin Li 183*9c5db199SXin Li # Clean-up unused files to save memory. 184*9c5db199SXin Li self._run(['rm', '-rf', self._MINIOS_TEMP_STATEFUL_DIR]) 185*9c5db199SXin Li 186*9c5db199SXin Li def _setup_python_symlinks(self): 187*9c5db199SXin Li """ 188*9c5db199SXin Li Create symlinks in the root to point to all python paths in /usr/local 189*9c5db199SXin Li for stateful installed python to work. This is needed because Gentoo 190*9c5db199SXin Li creates wrappers with hardcoded paths to the root (e.g. python-exec). 191*9c5db199SXin Li 192*9c5db199SXin Li """ 193*9c5db199SXin Li for path in self._DEPENDENCY_DIRS: 194*9c5db199SXin Li self._run([ 195*9c5db199SXin Li 'find', 196*9c5db199SXin Li os.path.join(self._DEPENDENCY_INSTALL_DIR, path), 197*9c5db199SXin Li '-maxdepth', '1', '\(', '-name', 'python*', '-o', '-name', 198*9c5db199SXin Li 'portage', '\)', '-exec', 'ln', '-s', '{}', 199*9c5db199SXin Li os.path.join('/usr', path), '\;' 200*9c5db199SXin Li ]) 201*9c5db199SXin Li 202*9c5db199SXin Li def _start_nebraska(self, payload_url=None): 203*9c5db199SXin Li """ 204*9c5db199SXin Li Initialize and start nebraska on the DUT. 205*9c5db199SXin Li 206*9c5db199SXin Li @param payload_url: The payload to served by nebraska. 207*9c5db199SXin Li 208*9c5db199SXin Li """ 209*9c5db199SXin Li if not self._nebraska: 210*9c5db199SXin Li self._nebraska = minios_util.NebraskaService( 211*9c5db199SXin Li self, self._host, payload_url) 212*9c5db199SXin Li self._nebraska.start() 213*9c5db199SXin Li 214*9c5db199SXin Li def _verify_reboot(self, old_boot_id): 215*9c5db199SXin Li """ 216*9c5db199SXin Li Verify that the unit rebooted using the boot_id. 217*9c5db199SXin Li 218*9c5db199SXin Li @param old_boot_id A boot id value obtained before the 219*9c5db199SXin Li reboot. 220*9c5db199SXin Li 221*9c5db199SXin Li """ 222*9c5db199SXin Li self._host.test_wait_for_shutdown(self._MINIOS_SHUTDOWN_TIMEOUT) 223*9c5db199SXin Li self._host.test_wait_for_boot(old_boot_id) 224