Signed-off-by: Thanos Makatos <thanos.makatos(a)nutanix.com>
---
src/test/test_driver.c | 387 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 385 insertions(+), 2 deletions(-)
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index e87d7cfd44..a3b4799a0f 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,10 +36,14 @@
#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 "nwfilter_conf.h"
#include "snapshot_conf.h"
#include "virfdstream.h"
#include "storage_conf.h"
@@ -50,6 +52,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 +10038,383 @@ 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;
+
+ virNWFilterReadLockFilterUpdates();
+
+ 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);
+ virNWFilterUnlockFilterUpdates();
+ 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 +10438,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