Source code for common.OpTestQemu

#!/usr/bin/env python3
#
# OpenPOWER Automated Test Project
#
# Contributors Listed Below - COPYRIGHT 2015,2017
# [+] International Business Machines Corp.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.

"""
Support testing against Qemu simulator
"""

import atexit
import sys
import time
import pexpect
import subprocess
import tempfile
import os

from common.Exceptions import CommandFailed
from . import OPexpect
from .OpTestUtil import OpTestUtil
import OpTestConfiguration

import logging
import OpTestLogger
log = OpTestLogger.optest_logger_glob.get_logger(__name__)


[docs]class ConsoleState(): DISCONNECTED = 0 CONNECTED = 1
[docs]class QemuConsole(): """ A 'connection' to the Qemu Console involves *launching* qemu. Closing a connection will *terminate* the qemu process. """ def __init__(self, qemu_binary=None, pnor=None, skiboot=None, prompt=None, kernel=None, initramfs=None, block_setup_term=None, delaybeforesend=None, logfile=sys.stdout, disks=None, cdrom=None): self.qemu_binary = qemu_binary self.pnor = pnor self.skiboot = skiboot self.kernel = kernel self.initramfs = initramfs self.disks = disks self.state = ConsoleState.DISCONNECTED self.logfile = logfile self.delaybeforesend = delaybeforesend self.system = None self.cdrom = cdrom # OpTestUtil instance is NOT conf's self.util = OpTestUtil() self.prompt = prompt self.expect_prompt = self.util.build_prompt(prompt) + "$" self.pty = None # allows caller specific control of when to block setup_term self.block_setup_term = block_setup_term # tells setup_term to not throw exceptions, like when system off self.setup_term_quiet = 0 # flags the object to abandon setup_term operations, like when system off self.setup_term_disable = 0 self.mac_str = '52:54:00:22:34:56' # state tracking, reset on boot and state changes # console tracking done on System object for the system console self.PS1_set = -1 self.LOGIN_set = -1 self.SUDO_set = -1
[docs] def set_system(self, system): self.system = system
[docs] def set_system_setup_term(self, flag): self.system.block_setup_term = flag
[docs] def get_system_setup_term(self): return self.system.block_setup_term
[docs] def set_block_setup_term(self, flag): self.block_setup_term = flag
[docs] def get_block_setup_term(self): return self.block_setup_term
[docs] def enable_setup_term_quiet(self): self.setup_term_quiet = 1 self.setup_term_disable = 0
[docs] def disable_setup_term_quiet(self): self.setup_term_quiet = 0 self.setup_term_disable = 0
# Because this makes sense for the console
[docs] def update_disks(self, disks): self.disks = disks
[docs] def close(self): self.util.clear_state(self) try: rc_child = self.pty.close() exitCode = signalstatus = None if self.pty.status != -1: # leaving for debug if os.WIFEXITED(self.pty.status): exitCode = os.WEXITSTATUS(self.pty.status) else: signalstatus = os.WTERMSIG(self.pty.status) self.state = ConsoleState.DISCONNECTED except pexpect.ExceptionPexpect as e: self.state = ConsoleState.DISCONNECTED raise "Qemu Console: failed to close console" except Exception as e: self.state = ConsoleState.DISCONNECTED pass log.debug("Qemu close -> TERMINATE")
[docs] def connect(self): if self.state == ConsoleState.CONNECTED: return self.pty else: self.util.clear_state(self) # clear when coming in DISCONNECTED log.debug("#Qemu Console CONNECT") cmd = ("%s" % (self.qemu_binary) + " -machine powernv -m 4G" + " -nographic -nodefaults" ) if self.pnor: cmd = cmd + " -drive file={},format=raw,if=mtd".format(self.pnor) if self.skiboot: skibootdir = os.path.dirname(self.skiboot) skibootfile = os.path.basename(self.skiboot) if skibootfile: cmd = cmd + " -bios %s" % (skibootfile) if skibootdir: cmd = cmd + " -L %s" % (skibootdir) if self.kernel: cmd = cmd + " -kernel %s" % (self.kernel) if self.initramfs is not None: cmd = cmd + " -initrd %s" % (self.initramfs) # So in the powernv(9) QEMU model we have 6 PHBs with one slot free each. # We can add a pcie bridge to each of these, and each bridge has 31 # slots.. if you see where I'm going.. cmd = (cmd + " -device pcie-pci-bridge,id=pcie.6,bus=pcie.0,addr=0x0" + " -device pcie-pci-bridge,id=pcie.7,bus=pcie.1,addr=0x0" + " -device pcie-pci-bridge,id=pcie.8,bus=pcie.2,addr=0x0" + " -device pcie-pci-bridge,id=pcie.9,bus=pcie.3,addr=0x0" + " -device pcie-pci-bridge,id=pcie.10,bus=pcie.4,addr=0x0" + " -device pcie-pci-bridge,id=pcie.11,bus=pcie.5,addr=0x0" ) # Put the NIC in slot 2 of the second PHB (1st is reserved for later) cmd = (cmd + " -netdev user,id=u1 -device e1000e,netdev=u1,mac={},bus=pcie.7,addr=2" .format(self.mac_str) ) prefilled_slots = 1 if self.cdrom is not None: # Put the CDROM in slot 3 of the second PHB cmd = (cmd + " -drive file={},id=cdrom01,if=none,media=cdrom".format(self.cdrom) + " -device virtio-blk-pci,drive=cdrom01,id=virtio02,bus=pcie.7,addr=3" ) prefilled_slots += 1 bridges = [] bridges.append({'bus': 3, 'n_devices': 0, 'bridged': False}) bridges.append({'bus': 4, 'n_devices': 0, 'bridged': False}) bridges.append({'bus': 5, 'n_devices': 0, 'bridged': False}) bridges.append({'bus': 6, 'n_devices': 0, 'bridged': False}) bridges.append( {'bus': 7, 'n_devices': prefilled_slots, 'bridged': False}) bridges.append({'bus': 8, 'n_devices': 0, 'bridged': False}) bridges.append({'bus': 9, 'n_devices': 0, 'bridged': False}) bridges.append({'bus': 10, 'n_devices': 0, 'bridged': False}) bridges.append({'bus': 11, 'n_devices': 0, 'bridged': False}) # For any amount of disks we have, start finding spots for them in the PHBs if self.disks: diskid = 0 bid = 0 for disk in self.disks: bridge = bridges[bid] if bridge['n_devices'] >= 30: # This bridge is full if bid == len(bridges) - 1: # All bridges full, find one to extend if [x for x in bridges if x['bridged'] == False] == []: # We messed up and filled up all our slots raise OpTestError("Oops! We ran out of slots!") for i in range(0, bid): if not bridges[i]['bridged']: # We can add a bridge here parent = bridges[i]['bus'] new = bridges[-1]['bus'] + 1 print(("Adding new bridge {} on bridge {}".format( new, parent))) bridges.append( {'bus': new, 'n_devices': 0, 'bridged': False}) cmd = cmd + \ " -device pcie-pci-bridge,id=pcie.{},bus=pcie.{},addr=0x1".format( new, parent) bid = bid + 1 bridges[i]['bridged'] = True bridge = bridges[bid] break else: # Just move to the next one, subsequent bridge should # always have slots bid = bid + 1 bridge = bridges[bid] if bridge['n_devices'] >= 30: raise OpTestError("Lost track of our PCI bridges!") # Got a bridge, let's go! # Valid bridge slots are 1..31, but keep 1 free for more bridges addr = 2 + bridge['n_devices'] print(("Adding disk {} on bus {} at address {}".format( diskid, bridge['bus'], addr))) cmd = cmd + \ " -drive file={},id=disk{},if=none".format( disk.name, diskid) cmd = cmd + " -device virtio-blk-pci,drive=disk{},id=virtio{},bus=pcie.{},addr={}".format( diskid, diskid, bridge['bus'], hex(addr)) diskid += 1 bridge['n_devices'] += 1 # typical host ip=10.0.2.2 and typical skiroot 10.0.2.15 # use skiroot as the source, no sshd in skiroot fru_path = os.path.join( OpTestConfiguration.conf.basedir, "test_binaries", "qemu_fru") cmd = cmd + " -device ipmi-bmc-sim,id=bmc0,frudatafile=" + \ fru_path + " -device isa-ipmi-bt,bmc=bmc0,irq=10" cmd = cmd + " -serial none -device isa-serial,chardev=s1 -chardev stdio,id=s1,signal=off" print(cmd) try: self.pty = OPexpect.spawn(cmd, logfile=self.logfile) except Exception as e: self.state = ConsoleState.DISCONNECTED raise CommandFailed('OPexpect.spawn', 'OPexpect.spawn encountered a problem: ' + str(e), -1) self.state = ConsoleState.CONNECTED self.pty.setwinsize(1000, 1000) if self.delaybeforesend: self.pty.delaybeforesend = self.delaybeforesend if self.system.SUDO_set != 1 or self.system.LOGIN_set != 1 or self.system.PS1_set != 1: self.util.setup_term(self.system, self.pty, None, self.system.block_setup_term) # Wait a moment for isalive() to read a correct value and then check # if the command has already exited. If it has then QEMU has most # likely encountered an error and there's no point proceeding. time.sleep(0.2) if not self.pty.isalive(): raise CommandFailed(cmd, self.pty.read(), self.pty.status) return self.pty
[docs] def get_console(self): if self.state == ConsoleState.DISCONNECTED: self.util.clear_state(self) self.connect() else: if self.system.SUDO_set != 1 or self.system.LOGIN_set != 1 or self.system.PS1_set != 1: self.util.setup_term(self.system, self.pty, None, self.system.block_setup_term) return self.pty
[docs] def run_command(self, command, timeout=60, retry=0): return self.util.run_command(self, command, timeout, retry)
[docs] def run_command_ignore_fail(self, command, timeout=60, retry=0): return self.util.run_command_ignore_fail(self, command, timeout, retry)
[docs]class QemuIPMI(): """ Qemu has fairly limited IPMI capability, and we probably need to extend the capability checks so that more of the IPMI test suite gets skipped. """ def __init__(self, console): self.console = console
[docs] def ipmi_power_off(self): """For Qemu, this just kills the simulator""" self.console.close()
[docs] def ipmi_wait_for_standby_state(self, i_timeout=10): """For Qemu, we just kill the simulator""" self.console.close()
[docs] def ipmi_set_boot_to_petitboot(self): return 0
[docs] def ipmi_sel_check(self, i_string="Transition to Non-recoverable"): pass
[docs] def ipmi_sel_elist(self, dump=False): pass
[docs] def ipmi_set_no_override(self): pass
[docs] def sys_set_bootdev_no_override(self): pass
[docs]class OpTestQemu(): def __init__(self, conf=None, qemu_binary=None, pnor=None, skiboot=None, kernel=None, initramfs=None, cdrom=None, logfile=sys.stdout): self.disks = [] # need the conf object to properly bind opened object # we need to be able to cleanup/close the temp file in signal handler self.conf = conf if self.conf.args.qemu_scratch_disk and self.conf.args.qemu_scratch_disk.strip(): try: # starts as name string log.debug("OpTestQemu opening file={}" .format(self.conf.args.qemu_scratch_disk)) self.conf.args.qemu_scratch_disk = \ open(self.conf.args.qemu_scratch_disk, 'wb') # now is a file-like object except Exception as e: log.error("OpTestQemu encountered a problem " "opening file={} Exception={}" .format(self.conf.args.qemu_scratch_disk, e)) else: # update with new object to close in cleanup self.conf.args.qemu_scratch_disk = \ tempfile.NamedTemporaryFile(delete=True) # now a file-like object try: create_hda = subprocess.check_call(["qemu-img", "create", "-fqcow2", self.conf.args.qemu_scratch_disk.name, "10G"]) except Exception as e: log.error("OpTestQemu encountered a problem with qemu-img," " check that you have qemu-utils installed first" " and then retry.") raise e self.disks.append(self.conf.args.qemu_scratch_disk) atexit.register(self.cleanup) self.console = QemuConsole(qemu_binary=qemu_binary, pnor=pnor, skiboot=skiboot, kernel=kernel, initramfs=initramfs, logfile=logfile, disks=self.disks, cdrom=cdrom) self.ipmi = QemuIPMI(self.console) self.system = None
[docs] def cleanup(self): for fd in self.disks: log.debug("OpTestQemu cleaning up qemu_scratch_disk={}" .format(self.conf.args.qemu_scratch_disk)) try: fd.close() except Exception as e: log.error("OpTestQemu cleanup, ignoring Exception={}" .format(e)) self.disks = []
[docs] def set_system(self, system): self.console.system = system
[docs] def get_host_console(self): return self.console
[docs] def run_command(self, command, timeout=10, retry=0): # qemu only supports system console object, not this bmc object return None # at least return something and have the testcase handle
[docs] def get_ipmi(self): return self.ipmi
[docs] def power_off(self): self.console.close()
[docs] def power_on(self): self.console.connect()
[docs] def get_rest_api(self): return None
[docs] def has_os_boot_sensor(self): return False
[docs] def has_occ_active_sensor(self): return False
[docs] def has_host_status_sensor(self): return False
[docs] def has_inband_bootdev(self): return False
[docs] def supports_ipmi_dcmi(self): return False
[docs] def has_ipmi_sel(self): return False
[docs] def add_temporary_disk(self, size): self.console.close() fd = tempfile.NamedTemporaryFile(delete=True) self.disks.append(fd) create_hda = subprocess.check_call(["qemu-img", "create", "-fqcow2", fd.name, size]) self.console.update_disks(self.disks)