From f881c2a9acf118505d3cd73388d7be381ee0c52d Mon Sep 17 00:00:00 2001 From: Sebastian Mitterle Date: Thu, 9 Dec 2021 11:40:04 +0100 Subject: [PATCH] virttools: add new test type and virt-install test 1. /virttools: Add new test type for tools from virt-manager repository; by using avocado-vt we get access to many useful test functions ready to use for qemu/libvirt testing. The test type assumes the same default setup as for tp-libvirt/libvirt and builds on top of that. During testing, switching from a bootstrapped libvirt environment to a bootstrapped virttools environment worked and the test case passed. 2. hostdev_mdev: Add a new test case. a. 'run': This virt-install test case is currently ccw device specific but other types on other platforms can be implemented by creating a subclass for the MdevHandler for the desired mdev_type. b. 'clean_up/sleep': The introduction of 1sec sleep is necessary to make sure setting the device offline will work. Using the usual 'wait_for' here seemed to overcomplicate the code. c. 'virt_install_with_hostdev': The function hardcodes several vm installation parameters. This is to keep the code simple at this point but can be changed in the future. 3. spell.ignore: Add command and device names used in ccw.py and hostdev_mdev.py. 4. ccw.py: Add a new function to set devices offline to reset them to their previous state. Test cases with real ccw devices usually assume the test environment is configured in a way that the first unused ccw device will be used for the test case, possibly in a destructive way. Some functions have been added return value 'True' on success to allow for usage in 'wait_for' constructions. 5. README.md: Add readme file to give a bit more context and intention for the new test type. Signed-off-by: Sebastian Mitterle --- provider/vfio/ccw.py | 23 ++- spell.ignore | 9 + virttools/README.md | 11 ++ .../tests/cfg/virt_install/hostdev_mdev.cfg | 6 + .../tests/src/virt_install/hostdev_mdev.py | 181 ++++++++++++++++++ 5 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 virttools/README.md create mode 100644 virttools/tests/cfg/virt_install/hostdev_mdev.cfg create mode 100644 virttools/tests/src/virt_install/hostdev_mdev.py diff --git a/provider/vfio/ccw.py b/provider/vfio/ccw.py index 315f2e5fccd..067bdeda9dc 100644 --- a/provider/vfio/ccw.py +++ b/provider/vfio/ccw.py @@ -25,7 +25,7 @@ def read_write_operations_work(session, chpids, makefs=True): Per default the device gets a new filesystem setup. :param session: logged in guest session - :param chipds: string representing CHPIDs, e.g. 11122122 + :param chpids: string representing CHPIDs, e.g. 11122122 :param makefs: if False, the device is expected to have a valid filesystem already :return: True on success @@ -141,6 +141,21 @@ def mount(session): raise TestError("Couldn't mount partition. %s" % out) +def set_device_offline(device_id, session=None): + """ + Sets device offline + + :param device_id: cssid.ssid.devno, e.g. 0.0.560a + :param session: guest session, command is run on host if None + :raises TestError: if the device can't be set online + """ + + cmd = "chccwdev -d %s" % device_id + err, out = cmd_status_output(cmd, shell=True, session=session) + if err: + raise TestError("Could not set device offline. %s" % out) + + def set_device_online(device_id, session=None): """ Sets device online @@ -160,7 +175,7 @@ def get_first_device_identifiers(chpids, session): """ Gets the usual device identifier cssid.ssid.devno - :param chpids: chipids where the disk is connected, e.g. "11122122" + :param chpids: chpids where the disk is connected, e.g. "11122122" :param session: guest session :return: Pair of strings, "cssid.ssid.devno" "cssid.ssid.schid" :raises TestError: if the device can't be found inside guest @@ -171,7 +186,7 @@ def get_first_device_identifiers(chpids, session): devices_inside_guest = [x for x in paths.devices if x[paths.HEADER["CHPIDs"]] == chpids] if not devices_inside_guest: - raise TestError("Device with chipds %s wasn't" + raise TestError("Device with chpids %s wasn't" " found inside guest" % chpids) first = devices_inside_guest[0] return first[paths.HEADER["Device"]], first[paths.HEADER["Subchan."]] @@ -183,7 +198,7 @@ def device_is_listed(session, chpids): path ids. :param session: guest console session - :param chipds: chpids where the disk is connected, e.g. "11122122" + :param chpids: chpids where the disk is connected, e.g. "11122122" :return: True if device is listed """ diff --git a/spell.ignore b/spell.ignore index 92b0f59aa32..bf8a68d765d 100644 --- a/spell.ignore +++ b/spell.ignore @@ -112,6 +112,8 @@ chipset chmod chnaged chown +chpid +chpids chronyc Chunfu chwen @@ -165,6 +167,8 @@ dac DAC darget dargs +dasd +dasda datetime DAX dbus @@ -270,6 +274,7 @@ failover fallocate fc fd +fdasd fdisk fds fdset @@ -527,6 +532,7 @@ LXC lzop macvlan macvtap +makefs Makesure managedsave Managedsave @@ -548,6 +554,7 @@ mcast MCS md mdev +mdevctl mem memballoon memhog @@ -832,6 +839,7 @@ sasl sata scenaries schedinfo +schid scp scsi sd @@ -1031,6 +1039,7 @@ unprotect Unprotect Unregister unresettable +Unsets untar upadte Updae diff --git a/virttools/README.md b/virttools/README.md new file mode 100644 index 00000000000..b78f6ee0e40 --- /dev/null +++ b/virttools/README.md @@ -0,0 +1,11 @@ +The test type 'virttools' is meant to cover test cases for the tools in the +virt-manager repository, e.g. virt-xml, virt-clone, virt-install. + +By using avocado-vt we obtain access to many existing test functions for libvirt +and qemu. Please, note the related commit in avocado-vt to allow for the new +test type. + +'virttools' uses the same basic setup as the tp-libvirt/libvirt test type assuming +most tests will suppose there's at least one existing vm 'avocado-vt-vm1' with +image in the default location (avocado-vt/.../images/jeos-27-s390x.qcow2), e.g. +for 'virt-xml avocado-vt-vm1 --add-device...', 'virt-clone avocado-vt-vm1'. diff --git a/virttools/tests/cfg/virt_install/hostdev_mdev.cfg b/virttools/tests/cfg/virt_install/hostdev_mdev.cfg new file mode 100644 index 00000000000..747e5cd64cb --- /dev/null +++ b/virttools/tests/cfg/virt_install/hostdev_mdev.cfg @@ -0,0 +1,6 @@ +- virt_install.hostdev.mdev: + type = hostdev_mdev + variants: + - check_present_inside_guest: + only s390-virtio + mdev_type = vfio_ccw-io diff --git a/virttools/tests/src/virt_install/hostdev_mdev.py b/virttools/tests/src/virt_install/hostdev_mdev.py new file mode 100644 index 00000000000..f27233a6f60 --- /dev/null +++ b/virttools/tests/src/virt_install/hostdev_mdev.py @@ -0,0 +1,181 @@ +import logging + +from time import sleep +from uuid import uuid4 +from avocado.core.exceptions import TestError +from avocado.core.exceptions import TestFail +from provider.vfio import ccw +from virttest.libvirt_xml.vm_xml import VMXML +from virttest.utils_misc import cmd_status_output +from virttest import virsh + + +class MdevHandler(object): + """ Base class for mdev type specific implementations """ + + def create_nodedev(self): + """ Creates the mdev and returns its name """ + raise NotImplementedError() + + def get_target_address(self): + """ Returns a target address to use for hostdev """ + raise NotImplementedError() + + def check_device_present_inside_guest(self, session): + """ + Checks if the host device is present inside the guest + + :param session: guest session + """ + raise NotImplementedError() + + def clean_up(self): + """ Stops the mediated device and returns resources to the host """ + raise NotImplementedError() + + @staticmethod + def from_type(mdev_type): + """ + Creates implementing instance for mdev_type + + :param mdev_type: The mediated device type as by nodedev API + """ + if mdev_type == "vfio_ccw-io": + return CcwMdevHandler() + else: + raise TestError("Test doesn't know how to handle %s." % mdev_type) + + +class CcwMdevHandler(MdevHandler): + """ Class implementing test methods for vfio_ccw-io """ + + def __init__(self): + self.uuid = None + self.chpids = None + self.schid = None + self.target_address = None + self.expected_device_address = None + self.device_id = None + + def create_nodedev(self): + """ + Creates a mediated device of a specific type + and returns its name from libvirt. + """ + self.schid, self.chpids = ccw.get_device_info() + self.device_id, _ = ccw.get_first_device_identifiers(self.chpids, None) + ccw.set_override(self.schid) + self.uuid = str(uuid4()) + ccw.start_device(self.uuid, self.schid) + + return first_mdev_nodedev_name() + + def get_target_address(self): + """ + Returns a valid target device address + + :param address_type: guest device address type + """ + self.target_address = "address.type=ccw,address.cssid=0xfe,address.ssid=0x0,address.devno=0x1111" + self.expected_device_address = "0.0.1111" + return self.target_address + + def check_device_present_inside_guest(self, session): + """ + Fails the test if the device can't be found inside the guest. + + :param session: guest session + :raises: TestFail if device not found + """ + device, _ = ccw.get_first_device_identifiers(self.chpids, session) + if not device == self.expected_device_address: + raise TestFail("Couldn't find device inside guest." + "Expected address %s, found %s." % + (self.expected_device_address, device)) + logging.debug("Device was found inside guest with" + " expected id %s." % device) + + + def clean_up(self): + """ + Returns the mdev resources to the host. + """ + if self.uuid: + ccw.stop_device(self.uuid) + if self.schid: + ccw.unset_override(self.schid) + # need to sleep to avoid issue with setting device offline + # adding a wait_for would likely be more complicated + sleep(1) + if self.device_id: + ccw.set_device_offline(self.device_id) + + +def disk_for_import(vmxml): + """ + Returns the absolute path to a disk image for import. + Assume the boot image is the first disk and an image file. + + :param vmxml: VMXML instance + """ + disks = vmxml.get_disk_all() + disk_list = list(disks.values()) + first_disk = disk_list[0] + return first_disk.find('source').get('file') + + +def first_mdev_nodedev_name(): + """ Returns the first nodedev of type mdev known to libvirt """ + result = virsh.nodedev_list(cap="mdev", debug=True) + device_names = result.stdout.strip().splitlines() + if result.exit_status or len(device_names) == 0: + raise TestError("Couldn't create nodedev. %s. %s." % + (result.stderr, result.stdout)) + return device_names[0] + + +def virt_install_with_hostdev(vm_name, mdev_nodedev, target_address, disk_path): + """ Runs virt-install with hostdev""" + cmd = ("virt-install --import --name %s" + " --hostdev %s,%s" + " --disk %s" + " --vcpus 2 --memory 2048" + " --nographics --noautoconsole" % + (vm_name, mdev_nodedev, target_address, disk_path)) + err, out = cmd_status_output(cmd, shell=True, verbose=True) + if err: + raise TestError("Couldn't install vm with hostdev: %s" % out) + + +def run(test, params, env): + """ + Confirm that a mediated device can be used by virt-install. + For this we import a disk we know will boot and check the + result inside the guest. + The mediated device is created by the test and assumed + to be the only mediated device in the test environment. + """ + + vm_name = params.get("main_vm") + vm = env.get_vm(vm_name) + vmxml = VMXML.new_from_inactive_dumpxml(vm_name) + mdev_type = params.get("mdev_type", "vfio_ccw-io") + handler = None + + try: + + vm.undefine() + handler = MdevHandler.from_type(mdev_type) + disk = disk_for_import(vmxml) + mdev_nodedev = handler.create_nodedev() + target_address = handler.get_target_address() + + virt_install_with_hostdev(vm_name, mdev_nodedev, target_address, disk) + + session = vm.wait_for_login() + handler.check_device_present_inside_guest(session) + + finally: + vmxml.sync() + if handler: + handler.clean_up()