On 9/24/20 5:45 PM, Jonathon Jongsma wrote:
By using the new qemu monitor functions to handle passing and removing
file descriptors, we can support hotplug of vdpa devices.
Signed-off-by: Jonathon Jongsma <jjongsma@xxxxxxxxxx>
---
src/qemu/qemu_hotplug.c | 60 +++++++++++++++++--
tests/qemuhotplugmock.c | 9 +++
tests/qemuhotplugtest.c | 16 +++++
.../qemuhotplug-interface-vdpa.xml | 4 ++
.../qemuhotplug-base-live+interface-vdpa.xml | 57 ++++++++++++++++++
5 files changed, 142 insertions(+), 4 deletions(-)
create mode 100644 tests/qemuhotplugtestdevices/qemuhotplug-interface-vdpa.xml
create mode 100644 tests/qemuhotplugtestdomains/qemuhotplug-base-live+interface-vdpa.xml
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index 0582b78f97..3a2aff607c 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -1152,6 +1152,8 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver,
virErrorPtr originalError = NULL;
g_autofree char *slirpfdName = NULL;
int slirpfd = -1;
+ g_autofree char *vdpafdName = NULL;
+ int vdpafd = -1;
char **tapfdName = NULL;
int *tapfd = NULL;
size_t tapfdSize = 0;
@@ -1335,12 +1337,16 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver,
/* hostdev interfaces were handled earlier in this function */
break;
+ case VIR_DOMAIN_NET_TYPE_VDPA:
+ if ((vdpafd = qemuInterfaceVDPAConnect(net)) < 0)
+ goto cleanup;
+ break;
+
case VIR_DOMAIN_NET_TYPE_SERVER:
case VIR_DOMAIN_NET_TYPE_CLIENT:
case VIR_DOMAIN_NET_TYPE_MCAST:
case VIR_DOMAIN_NET_TYPE_INTERNAL:
case VIR_DOMAIN_NET_TYPE_UDP:
- case VIR_DOMAIN_NET_TYPE_VDPA:
case VIR_DOMAIN_NET_TYPE_LAST:
virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
_("hotplug of interface type of %s is not implemented yet"),
@@ -1386,14 +1392,28 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver,
for (i = 0; i < vhostfdSize; i++)
vhostfdName[i] = g_strdup_printf("vhostfd-%s%zu", net->info.alias, i);
+ qemuDomainObjEnterMonitor(driver, vm);
So this was moved up ahead of the call to qemuBuildHostNetStr()...
+
+ if (vdpafd > 0) {
+ /* vhost-vdpa only takes a filename for the dev, but we want to pass an
+ * open fd to qemu. Passing -1 as the fdset-id will create a new fdset
+ * and return the id of that fdset */
+ qemuMonitorAddFdInfo fdinfo;
+ if (qemuMonitorAddFileHandleToSet(priv->mon, vdpafd, -1,
+ net->data.vdpa.devicepath,
+ &fdinfo) < 0) {
+ ignore_value(qemuDomainObjExitMonitor(driver, vm));
+ goto cleanup;
... and here you ExitMonitor prior to goto cleanup on failure...
+ }
+ vdpafdName = g_strdup_printf("/dev/fdset/%d", fdinfo.fdset);
+ }
+
if (!(netprops = qemuBuildHostNetStr(net,
tapfdName, tapfdSize,
vhostfdName, vhostfdSize,
- slirpfdName, NULL)))
+ slirpfdName, vdpafdName)))
goto cleanup;
...but here you don't. (and should)
(NB: this change does put qemuBuildHostNetStr() inside the Monitor
section, but it just does a bit of string shuffling/creation, so that's
not a big deal)
- qemuDomainObjEnterMonitor(driver, vm);
-
(^^ old location of qemuDomainObjEnterMonitor())
if (actualType == VIR_DOMAIN_NET_TYPE_VHOSTUSER) {
if (qemuMonitorAttachCharDev(priv->mon, charDevAlias, net->data.vhostuser) < 0) {
ignore_value(qemuDomainObjExitMonitor(driver, vm));
@@ -1518,6 +1538,7 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver,
VIR_FREE(vhostfdName);
virDomainCCWAddressSetFree(ccwaddrs);
VIR_FORCE_CLOSE(slirpfd);
+ VIR_FORCE_CLOSE(vdpafd);
return ret;
@@ -4586,8 +4607,39 @@ qemuDomainRemoveNetDevice(virQEMUDriverPtr driver,
* to just ignore the error and carry on.
*/
}
+ } else if (actualType == VIR_DOMAIN_NET_TYPE_VDPA) {
+ int vdpafdset = -1;
+ g_autoptr(qemuMonitorFdsets) fdsets = NULL;
+
+ /* query qemu for which fdset is associated with the fd that we passed
+ * to qemu via 'add-fd' for this vdpa device. If we don't remove the
+ * fd, qemu will keep it open */
+ if (qemuMonitorQueryFdsets(priv->mon, &fdsets) == 0) {
+ for (i = 0; i < fdsets->nfdsets && vdpafdset < 0; i++) {
+ size_t j;
+ qemuMonitorFdsetInfoPtr set = &fdsets->fdsets[i];
+
+ for (j = 0; j < set->nfds; j++) {
+ qemuMonitorFdsetFdInfoPtr fdinfo = &set->fds[j];
+ if (STREQ_NULLABLE(fdinfo->opaque, net->data.vdpa.devicepath)) {
+ vdpafdset = set->id;
+ break;
+ }
+ }
+ }
+ }
+
+ if (vdpafdset < 0) {
+ VIR_WARN("Cannot determine fdset for vdpa device");
+ } else {
+ if (qemuMonitorRemoveFdset(priv->mon, vdpafdset) < 0) {
+ /* if it fails, there's not much we can do... just carry on */
+ VIR_WARN("failed to close vdpa device");
+ }
+ }
I agree there's not much we can do here to make the situation better,
but is it really going to be okay to inform the user that the device is
now free, since it apparently isn't? If we go ahead and send the
DEVICE_DELETED event up to the application, then it will think that the
same vdpa device is now available to be re-used elsewhere. Do you have
an idea what are the odds on that being true? (I don't, that's why I'm
asking :-)).
It may be safer to return failure, so the device is just stuck shown as
in-use by this guest; that would be bad, but maybe not as bad as if it
was still actually being used by this guest somehow (possible, since the
fd couldn't be deleted), and a 2nd guest started using it too. (I really
don't know what the consequences of any of this might be; just trying to
inject my sunny disposition into the mix; truthfully I'd be willing to
accept either way, just wanted to make sure it's considered).
}
+
if (qemuDomainObjExitMonitor(driver, vm) < 0)
return -1;
diff --git a/tests/qemuhotplugmock.c b/tests/qemuhotplugmock.c
index 29fac8a598..d2e32ecf7e 100644
--- a/tests/qemuhotplugmock.c
+++ b/tests/qemuhotplugmock.c
@@ -19,11 +19,13 @@
#include <config.h>
#include "qemu/qemu_hotplug.h"
+#include "qemu/qemu_interface.h"
#include "qemu/qemu_process.h"
#include "conf/domain_conf.h"
#include "virdevmapper.h"
#include "virutil.h"
#include "virmock.h"
+#include <fcntl.h>
static int (*real_virGetDeviceID)(const char *path, int *maj, int *min);
static bool (*real_virFileExists)(const char *path);
@@ -106,3 +108,10 @@ void
qemuProcessKillManagedPRDaemon(virDomainObjPtr vm G_GNUC_UNUSED)
{
}
+
+int
+qemuInterfaceVDPAConnect(virDomainNetDefPtr net G_GNUC_UNUSED)
+{
+ /* need a valid fd or sendmsg won't work. Just open /dev/null */
+ return open("/dev/null", O_RDONLY);
+}
diff --git a/tests/qemuhotplugtest.c b/tests/qemuhotplugtest.c
index 2d12cacf28..b7cebfc0e7 100644
--- a/tests/qemuhotplugtest.c
+++ b/tests/qemuhotplugtest.c
@@ -89,6 +89,7 @@ qemuHotplugCreateObjects(virDomainXMLOptionPtr xmlopt,
virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_SPICE_FILE_XFER_DISABLE);
virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_PR_MANAGER_HELPER);
virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_SCSI_BLOCK);
+ virQEMUCapsSet(priv->qemuCaps, QEMU_CAPS_NETDEV_VHOST_VDPA);
if (qemuTestCapsCacheInsert(driver.qemuCapsCache, priv->qemuCaps) < 0)
return -1;
@@ -140,6 +141,9 @@ testQemuHotplugAttach(virDomainObjPtr vm,
case VIR_DOMAIN_DEVICE_HOSTDEV:
ret = qemuDomainAttachHostDevice(&driver, vm, dev->data.hostdev);
break;
+ case VIR_DOMAIN_DEVICE_NET:
+ ret = qemuDomainAttachNetDevice(&driver, vm, dev->data.net);
+ break;
Nice attention to detail - nobody before you has bothered with a hotplug
test for a network device :-)
default:
VIR_TEST_VERBOSE("device type '%s' cannot be attached",
virDomainDeviceTypeToString(dev->type));
@@ -162,6 +166,7 @@ testQemuHotplugDetach(virDomainObjPtr vm,
case VIR_DOMAIN_DEVICE_SHMEM:
case VIR_DOMAIN_DEVICE_WATCHDOG:
case VIR_DOMAIN_DEVICE_HOSTDEV:
+ case VIR_DOMAIN_DEVICE_NET:
ret = qemuDomainDetachDeviceLive(vm, dev, &driver, async);
break;
default:
@@ -823,6 +828,17 @@ mymain(void)
DO_TEST_DETACH("pseries-base-live", "hostdev-pci", false, false,
"device_del", QMP_DEVICE_DELETED("hostdev0") QMP_OK);
+ DO_TEST_ATTACH("base-live", "interface-vdpa", false, true,
+ "add-fd", "{ \"return\": { \"fdset-id\": 1, \"fd\": 95 }}",
+ "netdev_add", QMP_OK, "device_add", QMP_OK);
+ DO_TEST_DETACH("base-live", "interface-vdpa", false, false,
+ "device_del", QMP_DEVICE_DELETED("net0") QMP_OK,
+ "netdev_del", QMP_OK,
+ "query-fdsets",
+ "{ \"return\": [{\"fds\": [{\"fd\": 95, \"opaque\": \"/dev/vhost-vdpa-0\"}], \"fdset-id\": 1}]}",
+ "remove-fd", QMP_OK
+ );
+
DO_TEST_ATTACH("base-live", "watchdog", false, true,
"watchdog-set-action", QMP_OK,
"device_add", QMP_OK);
diff --git a/tests/qemuhotplugtestdevices/qemuhotplug-interface-vdpa.xml b/tests/qemuhotplugtestdevices/qemuhotplug-interface-vdpa.xml
new file mode 100644
index 0000000000..e42ca08d31
--- /dev/null
+++ b/tests/qemuhotplugtestdevices/qemuhotplug-interface-vdpa.xml
@@ -0,0 +1,4 @@
+<interface type='vdpa'>
+ <mac address='52:54:00:39:5f:04'/>
+ <source dev='/dev/vhost-vdpa-0'/>
+</interface>
diff --git a/tests/qemuhotplugtestdomains/qemuhotplug-base-live+interface-vdpa.xml b/tests/qemuhotplugtestdomains/qemuhotplug-base-live+interface-vdpa.xml
new file mode 100644
index 0000000000..066180bb3c
--- /dev/null
+++ b/tests/qemuhotplugtestdomains/qemuhotplug-base-live+interface-vdpa.xml
@@ -0,0 +1,57 @@
+<domain type='kvm' id='7'>
+ <name>hotplug</name>
+ <uuid>d091ea82-29e6-2e34-3005-f02617b36e87</uuid>
+ <memory unit='KiB'>4194304</memory>
+ <currentMemory unit='KiB'>4194304</currentMemory>
+ <vcpu placement='static'>4</vcpu>
+ <os>
+ <type arch='x86_64' machine='pc'>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <features>
+ <acpi/>
+ <apic/>
+ <pae/>
+ </features>
+ <clock offset='utc'/>
+ <on_poweroff>destroy</on_poweroff>
+ <on_reboot>restart</on_reboot>
+ <on_crash>restart</on_crash>
+ <devices>
+ <emulator>/usr/bin/qemu-system-x86_64</emulator>
+ <controller type='usb' index='0'>
+ <alias name='usb'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
+ </controller>
+ <controller type='ide' index='0'>
+ <alias name='ide'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
+ </controller>
+ <controller type='scsi' index='0' model='virtio-scsi'>
+ <alias name='scsi0'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
+ </controller>
+ <controller type='pci' index='0' model='pci-root'>
+ <alias name='pci'/>
+ </controller>
+ <controller type='virtio-serial' index='0'>
+ <alias name='virtio-serial0'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+ </controller>
+ <interface type='vdpa'>
+ <mac address='52:54:00:39:5f:04'/>
+ <source dev='/dev/vhost-vdpa-0'/>
+ <model type='virtio'/>
+ <alias name='net0'/>
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
+ </interface>
+ <input type='mouse' bus='ps2'>
+ <alias name='input0'/>
+ </input>
+ <input type='keyboard' bus='ps2'>
+ <alias name='input1'/>
+ </input>
+ <memballoon model='none'/>
+ </devices>
+ <seclabel type='none' model='none'/>
+</domain>
With the ExitMonitor() added where indicated, consideration of possibly
failing if the fd can't be deleted, and (as with the rest of the series)
as long as it's been possible to test with real hardware:
Reviewed-by: Laine Stump <laine@xxxxxxxxxx>