23 Nov
2023
23 Nov
'23
1:48 a.m.
ping
> -----Original Message-----
> From: Thanos Makatos <thanos.makatos@nutanix.com>
> Sent: Thursday, November 9, 2023 11:15 AM
> To: devel@lists.libvirt.org
> Cc: Thanos Makatos <thanos.makatos@nutanix.com>
> Subject: [PATCH V5] support for hotplug/hotunplug in test hypervisor
>
> Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com>
>
> ---
>
> Changed since v4:
> * removed inadvertent calls to functions
> virNWFilterReadLockFilterUpdates/virNWFilterUnlockFilterUpdates
> (original patch was based on v8.0.0)
>
> ---
> src/test/test_driver.c | 382 ++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 380 insertions(+), 2 deletions(-)
>
> diff --git a/src/test/test_driver.c b/src/test/test_driver.c
> index e87d7cfd44..effb7f9880 100644
> --- a/src/test/test_driver.c
> +++ b/src/test/test_driver.c
> @@ -26,8 +26,6 @@
> #include <unistd.h>
> #include <sys/stat.h>
> #include <libxml/xmlsave.h>
> -
> -
> #include "virerror.h"
> #include "datatypes.h"
> #include "test_driver.h"
> @@ -38,9 +36,12 @@
> #include "virnetworkobj.h"
> #include "interface_conf.h"
> #include "checkpoint_conf.h"
> +#include "domain_addr.h"
> +#include "domain_audit.h"
> #include "domain_conf.h"
> #include "domain_driver.h"
> #include "domain_event.h"
> +#include "domain_validate.h"
> #include "network_event.h"
> #include "snapshot_conf.h"
> #include "virfdstream.h"
> @@ -50,6 +51,7 @@
> #include "node_device_conf.h"
> #include "virnodedeviceobj.h"
> #include "node_device_event.h"
> +#include "vircgroup.h"
> #include "virxml.h"
> #include "virthread.h"
> #include "virlog.h"
> @@ -10035,6 +10037,379 @@
> testConnectGetDomainCapabilities(virConnectPtr conn G_GNUC_UNUSED,
> return virDomainCapsFormat(domCaps);
> }
>
> +static int
> +testDomainAttachHostPCIDevice(testDriver *driver G_GNUC_UNUSED,
> + virDomainObj *vm,
> + virDomainHostdevDef *hostdev)
> +{
> + int backend = hostdev->source.subsys.u.pci.backend;
> +
> + switch ((virDomainHostdevSubsysPCIBackendType)backend) {
> + case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO:
> + case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT:
> + case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_KVM:
> + break;
> +
> + case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_XEN:
> + case VIR_DOMAIN_HOSTDEV_PCI_BACKEND_TYPE_LAST:
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> + _("test hypervisor does not support device assignment mode
> '%s'"),
> + virDomainHostdevSubsysPCIBackendTypeToString(backend));
> + return -1;
> + }
> +
> + if (!virDomainObjIsActive(vm)) {
> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> + _("guest unexpectedly quit during hotplug"));
> + return -1;
> + }
> +
> + VIR_REALLOC_N(vm->def->hostdevs, vm->def->nhostdevs + 1);
> +
> + virDomainAuditHostdev(vm, hostdev, "attach", true);
> +
> + vm->def->hostdevs[vm->def->nhostdevs++] = hostdev;
> +
> + return 0;
> +}
> +
> +static int
> +testDomainAttachHostDevice(testDriver *driver,
> + virDomainObj *vm,
> + virDomainHostdevDef *hostdev)
> +{
> + if (hostdev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) {
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> + _("hotplug is not supported for hostdev mode '%s'"),
> + virDomainHostdevModeTypeToString(hostdev->mode));
> + return -1;
> + }
> +
> + if (hostdev->source.subsys.type !=
> VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) {
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> + _("hotplug is not supported for hostdev subsys type '%s'"),
> + virDomainHostdevSubsysTypeToString(hostdev-
> >source.subsys.type));
> + return -1;
> + }
> +
> + return testDomainAttachHostPCIDevice(driver, vm, hostdev);
> +}
> +
> +static int
> +testDomainAttachDeviceLive(virDomainObj *vm,
> + virDomainDeviceDef *dev,
> + testDriver *driver)
> +{
> + int ret = -1;
> + const char *alias = NULL;
> +
> + if ((virDomainDeviceType)dev->type != VIR_DOMAIN_DEVICE_HOSTDEV) {
> + virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> + _("live attach of device '%s' is not supported"),
> + virDomainDeviceTypeToString(dev->type));
> + return -1;
> + }
> +
> + testDomainObjCheckHostdevTaint(vm, dev->data.hostdev);
> + if ((ret = testDomainAttachHostDevice(driver, vm, dev->data.hostdev)) !=
> 0)
> + return ret;
> +
> + alias = dev->data.hostdev->info->alias;
> + dev->data.hostdev = NULL;
> +
> + if (alias) {
> + virObjectEvent *event;
> + event = virDomainEventDeviceAddedNewFromObj(vm, alias);
> + virObjectEventStateQueue(driver->eventState, event);
> + }
> +
> + return 0;
> +}
> +
> +static int
> +testDomainAttachDeviceLiveAndConfig(virDomainObj *vm,
> + testDriver *driver,
> + const char *xml,
> + unsigned int flags)
> +{
> + g_autoptr(virDomainDeviceDef) devConf = NULL;
> + g_autoptr(virDomainDeviceDef) devLive = NULL;
> + unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE |
> + VIR_DOMAIN_DEF_PARSE_ABI_UPDATE;
> +
> + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE, -1);
> +
> + if (flags & VIR_DOMAIN_AFFECT_LIVE) {
> + if (!(devLive = virDomainDeviceDefParse(xml, vm->def,
> + driver->xmlopt, NULL,
> + parse_flags)))
> + return -1;
> +
> + if (virDomainDeviceValidateAliasForHotplug(vm, devLive,
> + VIR_DOMAIN_AFFECT_LIVE) < 0)
> + return -1;
> +
> + if (virDomainDefCompatibleDevice(vm->def, devLive, NULL,
> + VIR_DOMAIN_DEVICE_ACTION_ATTACH,
> + true) < 0)
> + return -1;
> +
> + if (testDomainAttachDeviceLive(vm, devLive, driver) < 0)
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +testDomainAttachDeviceFlags(virDomainPtr domain,
> + const char *xml,
> + unsigned int flags) {
> +
> + testDriver *driver = domain->conn->privateData;
> + virDomainObj *vm = NULL;
> + int ret = -1;
> +
> + if (!(vm = testDomObjFromDomain(domain)))
> + return -1;
> +
> + if (virDomainObjUpdateModificationImpact(vm, &flags) < 0)
> + goto cleanup;
> +
> + if (testDomainAttachDeviceLiveAndConfig(vm, driver, xml, flags) < 0)
> + goto cleanup;
> +
> + ret = 0;
> +
> + cleanup:
> + virDomainObjEndAPI(&vm);
> + return ret;
> +}
> +
> +static int
> +testDomainAttachDevice(virDomainPtr domain, const char *xml)
> +{
> + return testDomainAttachDeviceFlags(domain, xml, 0);
> +}
> +
> +/* search for a hostdev matching dev and detach it */
> +static int
> +testDomainDetachPrepHostdev(virDomainObj *vm,
> + virDomainHostdevDef *match,
> + virDomainHostdevDef **detach)
> +{
> + virDomainHostdevSubsys *subsys = &match->source.subsys;
> + virDomainHostdevSubsysPCI *pcisrc = &subsys->u.pci;
> + virDomainHostdevDef *hostdev = NULL;
> +
> + if (match->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) {
> + virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
> + _("hot unplug is not supported for hostdev mode '%s'"),
> + virDomainHostdevModeTypeToString(match->mode));
> + return -1;
> + }
> +
> + if (virDomainHostdevFind(vm->def, match, &hostdev) < 0) {
> + if (subsys->type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) {
> + virReportError(VIR_ERR_DEVICE_MISSING,
> + _("host pci device " VIR_PCI_DEVICE_ADDRESS_FMT
> + " not found"),
> + pcisrc->addr.domain, pcisrc->addr.bus,
> + pcisrc->addr.slot, pcisrc->addr.function);
> + } else {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("unexpected hostdev type %d"), subsys->type);
> + }
> + return -1;
> + }
> +
> + *detach = hostdev;
> +
> + return 0;
> +}
> +
> +static int
> +testDomainRemoveHostDevice(testDriver *driver G_GNUC_UNUSED,
> + virDomainObj *vm,
> + virDomainHostdevDef *hostdev)
> +{
> + virDomainNetDef *net = NULL;
> + size_t i;
> +
> + VIR_DEBUG("Removing host device %s from domain %p %s",
> + hostdev->info->alias, vm, vm->def->name);
> +
> + if (hostdev->parentnet) {
> + net = hostdev->parentnet;
> + for (i = 0; i < vm->def->nnets; i++) {
> + if (vm->def->nets[i] == hostdev->parentnet) {
> + virDomainNetRemove(vm->def, i);
> + break;
> + }
> + }
> + }
> +
> + for (i = 0; i < vm->def->nhostdevs; i++) {
> + if (vm->def->hostdevs[i] == hostdev) {
> + virDomainHostdevRemove(vm->def, i);
> + break;
> + }
> + }
> +
> + virDomainAuditHostdev(vm, hostdev, "detach", true);
> +
> + virDomainHostdevDefFree(hostdev);
> +
> + if (net) {
> + if (net->type == VIR_DOMAIN_NET_TYPE_NETWORK) {
> + g_autoptr(virConnect) conn = virGetConnectNetwork();
> + if (conn)
> + virDomainNetReleaseActualDevice(conn, vm->def, net);
> + else
> + VIR_WARN("Unable to release network device '%s'", NULLSTR(net-
> >ifname));
> + }
> + virDomainNetDefFree(net);
> + }
> +
> + return 0;
> +}
> +
> +static int
> +testDomainRemoveDevice(testDriver *driver,
> + virDomainObj *vm,
> + virDomainDeviceDef *dev)
> +{
> + virDomainDeviceInfo *info;
> + virObjectEvent *event;
> + g_autofree char *alias = NULL;
> +
> + /*
> + * save the alias to use when sending a DEVICE_REMOVED event after
> + * all other teardown is complete
> + */
> + if ((info = virDomainDeviceGetInfo(dev)))
> + alias = g_strdup(info->alias);
> + info = NULL;
> +
> + if ((virDomainDeviceType)dev->type != VIR_DOMAIN_DEVICE_HOSTDEV) {
> + virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> + _("don't know how to remove a %s device"),
> + virDomainDeviceTypeToString(dev->type));
> + goto out;
> + }
> +
> + if (testDomainRemoveHostDevice(driver, vm, dev->data.hostdev) < 0)
> + return -1;
> +
> +out:
> + event = virDomainEventDeviceRemovedNewFromObj(vm, alias);
> + virObjectEventStateQueue(driver->eventState, event);
> +
> + return 0;
> +}
> +
> +static int
> +testDomainDetachDeviceLive(virDomainObj *vm,
> + virDomainDeviceDef *match,
> + testDriver *driver)
> +{
> + virDomainDeviceDef detach = { .type = match->type };
> + virDomainDeviceInfo *info = NULL;
> +
> + if ((virDomainDeviceType)match->type !=
> VIR_DOMAIN_DEVICE_HOSTDEV) {
> + virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> + _("live detach of device '%s' is not supported"),
> + virDomainDeviceTypeToString(match->type));
> + return -1;
> + }
> +
> + if (testDomainDetachPrepHostdev(vm, match->data.hostdev,
> + &detach.data.hostdev) < 0)
> + return -1;
> +
> + /* "detach" now points to the actual device we want to detach */
> +
> + if (!(info = virDomainDeviceGetInfo(&detach))) {
> + /*
> + * This should never happen, since all of the device types in
> + * the switch cases that end with a "break" instead of a
> + * return have a virDeviceInfo in them.
> + */
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("device of type '%s' has no device info"),
> + virDomainDeviceTypeToString(detach.type));
> + return -1;
> + }
> +
> + /* Make generic validation checks common to all device types */
> +
> + if (!info->alias) {
> + virReportError(VIR_ERR_INTERNAL_ERROR,
> + _("Cannot detach %s device with no alias"),
> + virDomainDeviceTypeToString(detach.type));
> + return -1;
> + }
> +
> + return testDomainRemoveDevice(driver, vm, &detach);
> +}
> +
> +static int
> +testDomainDetachDeviceAliasLiveAndConfig(testDriver *driver,
> + virDomainObj *vm,
> + const char *alias,
> + unsigned int flags)
> +{
> + virDomainDef *def = NULL;
> + g_autoptr(virDomainDef) vmdef = NULL;
> +
> + virCheckFlags(VIR_DOMAIN_AFFECT_LIVE, -1);
> +
> + if (virDomainObjGetDefs(vm, flags, &def, NULL) < 0)
> + return -1;
> +
> + if (def) {
> + virDomainDeviceDef dev;
> +
> + if (virDomainDefFindDevice(def, alias, &dev, true) < 0)
> + return -1;
> +
> + if (testDomainDetachDeviceLive(vm, &dev, driver) < 0)
> + return -1;
> + }
> +
> + if (vmdef) {
> + if (virDomainDefSave(vmdef, driver->xmlopt, NULL) < 0)
> + return -1;
> + virDomainObjAssignDef(vm, &vmdef, false, NULL);
> + }
> +
> + return 0;
> +}
> +
> +static int
> +testDomainDetachDeviceAlias(virDomainPtr dom,
> + const char *alias,
> + unsigned int flags)
> +{
> + testDriver *driver = dom->conn->privateData;
> + virDomainObj *vm = NULL;
> + int ret = -1;
> +
> + if (!(vm = testDomObjFromDomain(dom)))
> + return -1;
> +
> + if (virDomainObjUpdateModificationImpact(vm, &flags) < 0)
> + goto cleanup;
> +
> + if (testDomainDetachDeviceAliasLiveAndConfig(driver, vm, alias, flags) < 0)
> + goto cleanup;
> +
> + ret = 0;
> +
> + cleanup:
> + virDomainObjEndAPI(&vm);
> + return ret;
> +}
>
> /*
> * Test driver
> @@ -10058,6 +10433,9 @@ static virHypervisorDriver testHypervisorDriver =
> {
> .connectListDomains = testConnectListDomains, /* 0.1.1 */
> .connectNumOfDomains = testConnectNumOfDomains, /* 0.1.1 */
> .connectListAllDomains = testConnectListAllDomains, /* 0.9.13 */
> + .domainAttachDevice = testDomainAttachDevice, /* 9.10.0 */
> + .domainAttachDeviceFlags = testDomainAttachDeviceFlags, /* 9.10.0 */
> + .domainDetachDeviceAlias = testDomainDetachDeviceAlias, /* 9.10.0 */
> .domainCreateXML = testDomainCreateXML, /* 0.1.4 */
> .domainCreateXMLWithFiles = testDomainCreateXMLWithFiles, /* 5.7.0 */
> .domainLookupByID = testDomainLookupByID, /* 0.1.1 */
> --
> 2.27.0