diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index e3164403df..11d132e952 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -439,6 +439,7 @@ virDomainVideoTypeFromString; virDomainVideoTypeToString; virDomainVirtioEventIdxTypeFromString; virDomainVirtioEventIdxTypeToString; +virDomainVirtTypeFromString; virDomainVirtTypeToString; virDomainWatchdogActionTypeFromString; virDomainWatchdogActionTypeToString; diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c index ac6b5c82c2..934a7b02b6 100644 --- a/src/qemu/qemu_capabilities.c +++ b/src/qemu/qemu_capabilities.c @@ -39,6 +39,7 @@ #include "virnodesuspend.h" #include "qemu_monitor.h" #include "virstring.h" +#include "qemu_hostdev.h" #include #include @@ -3565,3 +3566,92 @@ virQEMUCapsGetDefaultMachine(virQEMUCapsPtr qemuCaps) return NULL; return qemuCaps->machineTypes[0]; } + + +static void +virQEMUCapsFillDomainDeviceDiskCaps(virQEMUCapsPtr qemuCaps, + virDomainCapsDeviceDiskPtr disk) +{ + disk->device.supported = true; + /* QEMU supports all of these */ + VIR_DOMAIN_CAPS_ENUM_SET(disk->diskDevice, + VIR_DOMAIN_DISK_DEVICE_DISK, + VIR_DOMAIN_DISK_DEVICE_CDROM, + VIR_DOMAIN_DISK_DEVICE_FLOPPY); + + if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_BLK_SG_IO)) + VIR_DOMAIN_CAPS_ENUM_SET(disk->diskDevice, VIR_DOMAIN_DISK_DEVICE_LUN); + + VIR_DOMAIN_CAPS_ENUM_SET(disk->bus, + VIR_DOMAIN_DISK_BUS_IDE, + VIR_DOMAIN_DISK_BUS_FDC, + VIR_DOMAIN_DISK_BUS_SCSI, + VIR_DOMAIN_DISK_BUS_VIRTIO, + /* VIR_DOMAIN_DISK_BUS_SD */); + + if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_USB_STORAGE)) + VIR_DOMAIN_CAPS_ENUM_SET(disk->bus, VIR_DOMAIN_DISK_BUS_USB); +} + + +static void +virQEMUCapsFillDomainDeviceHostdevCaps(virQEMUCapsPtr qemuCaps, + virDomainCapsDeviceHostdevPtr hostdev) +{ + bool supportsPassthroughKVM = qemuHostdevHostSupportsPassthroughLegacy(); + bool supportsPassthroughVFIO = qemuHostdevHostSupportsPassthroughVFIO(); + + hostdev->device.supported = true; + /* VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES is for containers only */ + VIR_DOMAIN_CAPS_ENUM_SET(hostdev->mode, + VIR_DOMAIN_HOSTDEV_MODE_SUBSYS); + + VIR_DOMAIN_CAPS_ENUM_SET(hostdev->startupPolicy, + VIR_DOMAIN_STARTUP_POLICY_DEFAULT, + VIR_DOMAIN_STARTUP_POLICY_MANDATORY, + VIR_DOMAIN_STARTUP_POLICY_REQUISITE, + VIR_DOMAIN_STARTUP_POLICY_OPTIONAL); + + VIR_DOMAIN_CAPS_ENUM_SET(hostdev->subsysType, + VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB, + VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI); + if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_DRIVE) && + virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE) && + virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_SCSI_GENERIC)) + VIR_DOMAIN_CAPS_ENUM_SET(hostdev->subsysType, + VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI); + + /* No virDomainHostdevCapsType for QEMU */ + virDomainCapsEnumClear(&hostdev->capsType); + + virDomainCapsEnumClear(&hostdev->pciBackend); + if (supportsPassthroughVFIO && + virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VFIO_PCI)) { + VIR_DOMAIN_CAPS_ENUM_SET(hostdev->pciBackend, + VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT, + VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO); + } + + if (supportsPassthroughKVM && + (virQEMUCapsGet(qemuCaps, QEMU_CAPS_PCIDEVICE) || + virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE))) { + VIR_DOMAIN_CAPS_ENUM_SET(hostdev->pciBackend, + VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT, + VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM); + } +} + + +void +virQEMUCapsFillDomainCaps(virDomainCapsPtr domCaps, + virQEMUCapsPtr qemuCaps) +{ + virDomainCapsDeviceDiskPtr disk = &domCaps->disk; + virDomainCapsDeviceHostdevPtr hostdev = &domCaps->hostdev; + int maxvcpus = virQEMUCapsGetMachineMaxCpus(qemuCaps, domCaps->machine); + + domCaps->maxvcpus = maxvcpus; + + virQEMUCapsFillDomainDeviceDiskCaps(qemuCaps, disk); + virQEMUCapsFillDomainDeviceHostdevCaps(qemuCaps, hostdev); +} diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h index d0a1092ada..17be405b1c 100644 --- a/src/qemu/qemu_capabilities.h +++ b/src/qemu/qemu_capabilities.h @@ -28,6 +28,7 @@ # include "capabilities.h" # include "vircommand.h" # include "qemu_monitor.h" +# include "domain_capabilities.h" /* Internal flags to keep track of qemu command line capabilities */ typedef enum { @@ -314,4 +315,7 @@ int virQEMUCapsInitGuestFromBinary(virCapsPtr caps, virQEMUCapsPtr kvmbinCaps, virArch guestarch); +void virQEMUCapsFillDomainCaps(virDomainCapsPtr domCaps, + virQEMUCapsPtr qemuCaps); + #endif /* __QEMU_CAPABILITIES_H__*/ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 2a01c9ca6e..1983cef541 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -95,6 +95,7 @@ #include "viraccessapicheckqemu.h" #include "storage/storage_driver.h" #include "virhostdev.h" +#include "domain_capabilities.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -16885,6 +16886,105 @@ qemuNodeGetFreePages(virConnectPtr conn, } +static char * +qemuConnectGetDomainCapabilities(virConnectPtr conn, + const char *emulatorbin, + const char *arch_str, + const char *machine, + const char *virttype_str, + unsigned int flags) +{ + char *ret = NULL; + virQEMUDriverPtr driver = conn->privateData; + virQEMUCapsPtr qemuCaps = NULL; + int virttype; /* virDomainVirtType */ + virDomainCapsPtr domCaps = NULL; + int arch = VIR_ARCH_NONE; /* virArch */ + + virCheckFlags(0, ret); + virCheckNonNullArgReturn(virttype_str, ret); + + if (virConnectGetDomainCapabilitiesEnsureACL(conn) < 0) + return ret; + + if ((virttype = virDomainVirtTypeFromString(virttype_str)) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown virttype: %s"), + virttype_str); + goto cleanup; + } + + if (arch_str && (arch = virArchFromString(arch_str)) == VIR_ARCH_NONE) { + virReportError(VIR_ERR_INVALID_ARG, + _("unknown architecture: %s"), + arch_str); + goto cleanup; + } + + if (emulatorbin) { + virArch arch_from_caps; + + if (!(qemuCaps = virQEMUCapsCacheLookup(driver->qemuCapsCache, + emulatorbin))) + goto cleanup; + + arch_from_caps = virQEMUCapsGetArch(qemuCaps); + + if (arch == VIR_ARCH_NONE) + arch = arch_from_caps; + + if (arch_from_caps != arch) { + virReportError(VIR_ERR_INVALID_ARG, + _("architecture from emulator '%s' doesn't " + "match given architecture '%s'"), + virArchToString(arch_from_caps), + virArchToString(arch)); + goto cleanup; + } + } else if (arch_str) { + if (!(qemuCaps = virQEMUCapsCacheLookupByArch(driver->qemuCapsCache, + arch))) + goto cleanup; + + if (!emulatorbin) + emulatorbin = virQEMUCapsGetBinary(qemuCaps); + /* Deliberately not checking if provided @emulatorbin matches @arch, + * since if @emulatorbin was specified the match has been checked a few + * lines above. */ + } else { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("at least one of emulatorbin or " + "architecture fields must be present")); + goto cleanup; + } + + if (machine) { + /* Turn @machine into canonical name */ + machine = virQEMUCapsGetCanonicalMachine(qemuCaps, machine); + + if (!virQEMUCapsIsMachineSupported(qemuCaps, machine)) { + virReportError(VIR_ERR_INVALID_ARG, + _("the machine '%s' is not supported by emulator '%s'"), + machine, emulatorbin); + goto cleanup; + } + } else { + machine = virQEMUCapsGetDefaultMachine(qemuCaps); + } + + if (!(domCaps = virDomainCapsNew(emulatorbin, machine, arch, virttype))) + goto cleanup; + + virQEMUCapsFillDomainCaps(domCaps, qemuCaps); + + ret = virDomainCapsFormat(domCaps); + cleanup: + virObjectUnref(domCaps); + virObjectUnref(qemuCaps); + return ret; +} + + static virDriver qemuDriver = { .no = VIR_DRV_QEMU, .name = QEMU_DRIVER_NAME, @@ -17080,6 +17180,7 @@ static virDriver qemuDriver = { .domainGetTime = qemuDomainGetTime, /* 1.2.5 */ .domainSetTime = qemuDomainSetTime, /* 1.2.5 */ .nodeGetFreePages = qemuNodeGetFreePages, /* 1.2.6 */ + .connectGetDomainCapabilities = qemuConnectGetDomainCapabilities, /* 1.2.7 */ }; diff --git a/tests/Makefile.am b/tests/Makefile.am index 97af0d9d37..a262c7ba7f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -835,6 +835,11 @@ domaincapstest_SOURCES = \ domaincapstest.c testutils.h testutils.c domaincapstest_LDADD = $(LDADDS) +if WITH_QEMU +domaincapstest_SOURCES += testutilsqemu.c testutilsqemu.h +domaincapstest_LDADD += $(qemu_LDADDS) +endif WITH_QEMU + if WITH_LIBVIRTD libvirtdconftest_SOURCES = \ libvirtdconftest.c testutils.h testutils.c \ diff --git a/tests/domaincapsschemadata/domaincaps-qemu_1.6.50-1.xml b/tests/domaincapsschemadata/domaincaps-qemu_1.6.50-1.xml new file mode 100644 index 0000000000..8b63993e51 --- /dev/null +++ b/tests/domaincapsschemadata/domaincaps-qemu_1.6.50-1.xml @@ -0,0 +1,45 @@ + + /usr/bin/qemu-system-x86_64 + kvm + pc-1.2 + x86_64 + + + + disk + cdrom + floppy + lun + + + ide + fdc + scsi + virtio + usb + + + + + subsystem + + + default + mandatory + requisite + optional + + + usb + pci + scsi + + + + default + kvm + vfio + + + + diff --git a/tests/domaincapstest.c b/tests/domaincapstest.c index 6cdd086dcb..78197e2350 100644 --- a/tests/domaincapstest.c +++ b/tests/domaincapstest.c @@ -54,6 +54,30 @@ fillAll(virDomainCapsPtr domCaps, SET_ALL_BITS(hostdev->pciBackend); } + +#ifdef WITH_QEMU +# include "testutilsqemu.h" +static void +fillQemuCaps(virDomainCapsPtr domCaps, + void *opaque) +{ + virQEMUCapsPtr qemuCaps = (virQEMUCapsPtr) opaque; + + virQEMUCapsFillDomainCaps(domCaps, qemuCaps); + + /* The function above tries to query host's KVM & VFIO capabilities by + * calling qemuHostdevHostSupportsPassthroughLegacy() and + * qemuHostdevHostSupportsPassthroughVFIO() which, however, can't be + * successfully mocked as they are not exposed as internal APIs. Therefore, + * instead of mocking set the expected values here by hand. */ + VIR_DOMAIN_CAPS_ENUM_SET(domCaps->hostdev.pciBackend, + VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT, + VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM, + VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO); +} +#endif /* WITH_QEMU */ + + static virDomainCapsPtr buildVirDomainCaps(const char *emulatorbin, const char *machine, @@ -143,6 +167,27 @@ mymain(void) DO_TEST("full", "/bin/emulatorbin", "my-machine-type", VIR_ARCH_X86_64, VIR_DOMAIN_VIRT_KVM, .fillFunc = fillAll); +#ifdef WITH_QEMU + +# define DO_TEST_QEMU(Filename, QemuCapsFile, Emulatorbin, Machine, Arch, Type, ...) \ + do { \ + const char *capsPath = abs_srcdir "/qemucapabilitiesdata/" QemuCapsFile ".caps"; \ + virQEMUCapsPtr qemuCaps = qemuTestParseCapabilities(capsPath); \ + struct test_virDomainCapsFormatData data = {.filename = Filename, \ + .emulatorbin = Emulatorbin, .machine = Machine, .arch = Arch, \ + .type = Type, .fillFunc = fillQemuCaps, .opaque = qemuCaps}; \ + if (!qemuCaps) { \ + fprintf(stderr, "Unable to build qemu caps from %s\n", capsPath); \ + ret = -1; \ + } else if (virtTestRun(Filename, test_virDomainCapsFormat, &data) < 0) \ + ret = -1; \ + } while (0) + + DO_TEST_QEMU("qemu_1.6.50-1", "caps_1.6.50-1", "/usr/bin/qemu-system-x86_64", + "pc-1.2", VIR_ARCH_X86_64, VIR_DOMAIN_VIRT_KVM); + +#endif /* WITH_QEMU */ + return ret; }