RE: [PATCH V5] support for hotplug/hotunplug in test hypervisor

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> -----Original Message-----
> From: Michal Prívozník <mprivozn@xxxxxxxxxx>
> Sent: Tuesday, November 28, 2023 1:19 PM
> To: Thanos Makatos <thanos.makatos@xxxxxxxxxxx>; devel@xxxxxxxxxxxxxxxxx
> Subject: Re: [PATCH V5] support for hotplug/hotunplug in test hypervisor
> 
> On 11/9/23 12:14, Thanos Makatos wrote:
> > Signed-off-by: Thanos Makatos <thanos.makatos@xxxxxxxxxxx>
> >
> > ---
> >
> > 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(-)
> 
> Ooops, this has missed my attention and unfortunately upcoming release
> too as we are in freeze. Sorry. To make it up to you, I'm fixing all the
> small issues I'm raising below and will push once after the release.
> 
> I've uploaded the fixup patch (that I'll squash into yours before
> merging) here, if you want to take a look:
> 
> https://urldefense.proofpoint.com/v2/url?u=https-
> 3A__gitlab.com_MichalPrivoznik_libvirt_-2D_commits_test-5Fdriver-
> 5Fhotplug_-3Fref-5Ftype-
> 3Dheads&d=DwICaQ&c=s883GpUCOChKOHiocYtGcg&r=XTpYsh5Ps2zJvtw6ogtti
> 46atk736SI4vgsJiUKIyDE&m=o5_V_Q03_HbW5P4oVf5Tez4K2mYV_jf35yvol4O5
> KioGaofPI3B07QN9CvepCKAc&s=kiKbd_XHWn3_p3luPq89G0pku0iApEIs18MAtL
> u_d88&e=
> 
> >
> > 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>
> > -
> > -
> 
> We like to keep these separate to make it more obvious where public
> header section ends and private header section starts.
> 
> >  #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"
> 
> This shouldn't be needed. domain_audit contains functions to audit real
> resource auditing (e.g. assigning a real device to a guest) - test
> driver should not do such thing.
> 
> >  #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"
> 
> And this too shouldn't be here. We do not set up any cgroups for domains
> under the test driver.
> 
> >  #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'"),
> 
> I'll raise it here: for translatable strings, we've switched to using
> more robust format: %1$s because it bit us in the past when one of
> locales contained wrong data causing us to crash. This should have been
> caught by 'ninja test'.
> 
> > +                       virDomainHostdevSubsysPCIBackendTypeToString(backend));
> > +        return -1;
> > +    }
> > +
> > +    if (!virDomainObjIsActive(vm)) {
> > +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> > +                       _("guest unexpectedly quit during hotplug"));
> > +        return -1;
> > +    }
> > +
> 
> This check makes sense in QEMU world, because we unlock the domain
> object (virDomainObj) whilst talking to QEMU on monitor. But in the test
> driver it's needless - the object is locked throughough whole API run.
> 
> > +    VIR_REALLOC_N(vm->def->hostdevs, vm->def->nhostdevs + 1);
> > +
> > +    virDomainAuditHostdev(vm, hostdev, "attach", true);
> 
> This shouldn't be here.
> 
> > +
> > +    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) {
> 
> The ->type member is already of the correct type, no need to type cast it.
> 
> > +        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"),
> 
> We prefer error messages to be on a single line. Again, we have a
> syntax-check rule for that. And in this specific case,
> virPCIDeviceAddressAsString() looks like the way to format PCI address.
> 
> > +                           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;
> > +            }
> > +        }
> > +    }
> 
> Fortunately, we do not implement <interface type="hostdev/> the same way
> as in QEMU driver. In QEMU driver, such <interface/> is also added to
> <hostdev/> (well, vm->def->hostdevs[]), which is not the case in test
> driver. Thus, this is not needed.
> 
> > +
> > +    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);
> > +    }
> 
> This is also not needed. The test driver network does not implement port
> APIs, but firstly, we don't have that aforementioned shadowing of
> <interface/> and <hostdev/> devices.
> 
> > +
> > +    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;
> 
> So if this is called with a device != _HOSTDEV but an alias set, an
> event is emitted? That doesn't feel right.
> 
> > +    }
> > +
> > +    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)
> 
> Nitpick, arguments should merely follow their importance: driver > vm >
> device.
> 
> > +{
> > +    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;
> 
> This variable is initalized to 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) {
> 
> and never set really, rendering this if to be a dead code.
> 
> > +        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 */
> 
> These will have to be 10.0.0 becasue this is going to miss 9.10.0
> release.
> 
> >      .domainCreateXML = testDomainCreateXML, /* 0.1.4 */
> >      .domainCreateXMLWithFiles = testDomainCreateXMLWithFiles, /* 5.7.0 */
> >      .domainLookupByID = testDomainLookupByID, /* 0.1.1 */
> 
> 
> Michal

Thanks a lot!

_______________________________________________
Devel mailing list -- devel@xxxxxxxxxxxxxxxxx
To unsubscribe send an email to devel-leave@xxxxxxxxxxxxxxxxx




[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]

  Powered by Linux