diff --git a/provider/vfio/ccw.py b/provider/vfio/ccw.py index 4133fdb540..43f0b5ec9f 100644 --- a/provider/vfio/ccw.py +++ b/provider/vfio/ccw.py @@ -30,7 +30,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 @@ -146,6 +146,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 offline + """ + + 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 @@ -165,7 +180,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 @@ -176,7 +191,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."]] @@ -188,7 +203,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 4b84da0cd5..c2b8594384 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 @@ -528,6 +533,7 @@ LXC lzop macvlan macvtap +makefs Makesure managedsave Managedsave @@ -549,6 +555,7 @@ mcast MCS md mdev +mdevctl mem memballoon memhog @@ -833,6 +840,7 @@ sasl sata scenaries schedinfo +schid scp scsi sd @@ -1032,6 +1040,7 @@ unprotect Unprotect Unregister unresettable +Unsets untar upadte Updae diff --git a/virttools/README.md b/virttools/README.md new file mode 100644 index 0000000000..b78f6ee0e4 --- /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 0000000000..747e5cd64c --- /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 0000000000..8337409046 --- /dev/null +++ b/virttools/tests/src/virt_install/hostdev_mdev.py @@ -0,0 +1,200 @@ +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 + +LOG = logging.getLogger('avocado.' + __name__) + + +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 + self.session = None + + def create_nodedev(self): + """ + Creates a mediated device of a specific type + and returns its name from libvirt. + + :return: name of mdev device as node device + """ + 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 get_first_mdev_nodedev_name() + + def get_target_address(self): + """ + Returns a valid target device address + + :return: hostdev target address + """ + 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 + """ + self.session = session + device, _ = ccw.get_first_device_identifiers(self.chpids, session) + if device != self.expected_device_address: + raise TestFail("Couldn't find device inside guest." + "Expected address %s, found %s." % + (self.expected_device_address, device)) + LOG.debug("Device was found inside guest with" + " expected id %s." % device) + + def clean_up(self): + """ + Returns the mdev resources to the host. + """ + if self.session: + self.session.close() + 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 get_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 + :return: absolute path to the guest's first disk image file + """ + disks = vmxml.get_disk_all() + disk_list = list(disks.values()) + first_disk = disk_list[0] + return first_disk.find('source').get('file') + + +def get_first_mdev_nodedev_name(): + """ + Returns the first nodedev of type mdev known to libvirt + + :return: the first listed mdev node device + """ + 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 + + :param vm_name: guest name + :param mdev_nodedev: mdev name as node device + :param target_address: hostdev target address definition + :param disk_path: path to the disk image for import + """ + 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 = get_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()