[PATCH] test_driver: add testUpdateDeviceFlags implementation

John Levon posted 1 patch 2 months, 1 week ago
Failed in applying to current master (apply log)
src/test/test_driver.c | 127 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
[PATCH] test_driver: add testUpdateDeviceFlags implementation
Posted by John Levon 2 months, 1 week ago
Add basic coverage of device update; for now, only support disk updates
until other types are needed or tested.

Signed-off-by: John Levon <john.levon@nutanix.com>
---
 src/test/test_driver.c | 127 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 127 insertions(+)

diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index 40fb8a467d..213fbdbccb 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -41,6 +41,7 @@
 #include "domain_conf.h"
 #include "domain_driver.h"
 #include "domain_event.h"
+#include "domain_postparse.h"
 #include "domain_validate.h"
 #include "network_event.h"
 #include "snapshot_conf.h"
@@ -10237,6 +10238,131 @@ testDomainAttachDevice(virDomainPtr domain, const char *xml)
 }
 
 
+static int
+testDomainUpdateDeviceConfig(virDomainDef *vmdef,
+                             virDomainDeviceDef *dev,
+                             unsigned int parse_flags,
+                             virDomainXMLOption *xmlopt)
+{
+    virDomainDiskDef *newDisk;
+    virDomainDeviceDef oldDev = { .type = dev->type };
+    int pos;
+
+    switch (dev->type) {
+    case VIR_DOMAIN_DEVICE_DISK:
+        newDisk = dev->data.disk;
+        if ((pos = virDomainDiskIndexByName(vmdef, newDisk->dst, false)) < 0) {
+            virReportError(VIR_ERR_INVALID_ARG,
+                           _("target %1$s doesn't exist."), newDisk->dst);
+            return -1;
+        }
+
+        oldDev.data.disk = vmdef->disks[pos];
+        if (virDomainDefCompatibleDevice(vmdef, dev, &oldDev,
+                                         VIR_DOMAIN_DEVICE_ACTION_UPDATE,
+                                         false) < 0)
+            return -1;
+
+        virDomainDiskDefFree(vmdef->disks[pos]);
+        vmdef->disks[pos] = newDisk;
+        dev->data.disk = NULL;
+        break;
+
+    // TODO: support these here once tested.
+    case VIR_DOMAIN_DEVICE_GRAPHICS:
+    case VIR_DOMAIN_DEVICE_NET:
+    case VIR_DOMAIN_DEVICE_MEMORY:
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
+                       _("persistent update of device '%1$s' is not supported by test driver"),
+                       virDomainDeviceTypeToString(dev->type));
+        return -1;
+
+    case VIR_DOMAIN_DEVICE_FS:
+    case VIR_DOMAIN_DEVICE_INPUT:
+    case VIR_DOMAIN_DEVICE_SOUND:
+    case VIR_DOMAIN_DEVICE_VIDEO:
+    case VIR_DOMAIN_DEVICE_WATCHDOG:
+    case VIR_DOMAIN_DEVICE_HUB:
+    case VIR_DOMAIN_DEVICE_SMARTCARD:
+    case VIR_DOMAIN_DEVICE_MEMBALLOON:
+    case VIR_DOMAIN_DEVICE_NVRAM:
+    case VIR_DOMAIN_DEVICE_RNG:
+    case VIR_DOMAIN_DEVICE_SHMEM:
+    case VIR_DOMAIN_DEVICE_LEASE:
+    case VIR_DOMAIN_DEVICE_HOSTDEV:
+    case VIR_DOMAIN_DEVICE_CONTROLLER:
+    case VIR_DOMAIN_DEVICE_REDIRDEV:
+    case VIR_DOMAIN_DEVICE_CHR:
+    case VIR_DOMAIN_DEVICE_NONE:
+    case VIR_DOMAIN_DEVICE_TPM:
+    case VIR_DOMAIN_DEVICE_PANIC:
+    case VIR_DOMAIN_DEVICE_IOMMU:
+    case VIR_DOMAIN_DEVICE_VSOCK:
+    case VIR_DOMAIN_DEVICE_AUDIO:
+    case VIR_DOMAIN_DEVICE_CRYPTO:
+    case VIR_DOMAIN_DEVICE_LAST:
+        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
+                       _("persistent update of device '%1$s' is not supported"),
+                       virDomainDeviceTypeToString(dev->type));
+        return -1;
+    }
+
+    if (virDomainDefPostParse(vmdef, parse_flags, xmlopt, NULL) < 0)
+        return -1;
+
+    return 0;
+}
+
+
+static int
+testDomainUpdateDeviceFlags(virDomainPtr dom,
+                            const char *xml,
+                            unsigned int flags)
+{
+    testDriver *driver = dom->conn->privateData;
+    virDomainObj *vm = NULL;
+    g_autoptr(virDomainDef) vmdef = NULL;
+    g_autoptr(virDomainDeviceDef) dev = NULL;
+    int ret = -1;
+    unsigned int parse_flags = 0;
+
+    virCheckFlags(VIR_DOMAIN_AFFECT_LIVE |
+                  VIR_DOMAIN_AFFECT_CONFIG |
+                  VIR_DOMAIN_DEVICE_MODIFY_FORCE, -1);
+
+    if (!(vm = testDomObjFromDomain(dom)))
+        goto cleanup;
+
+    if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
+        goto cleanup;
+
+    if (virDomainObjUpdateModificationImpact(vm, &flags) < 0)
+        goto endjob;
+
+    if ((flags & VIR_DOMAIN_AFFECT_CONFIG) &&
+        !(flags & VIR_DOMAIN_AFFECT_LIVE))
+        parse_flags |= VIR_DOMAIN_DEF_PARSE_INACTIVE;
+
+    if (!(dev = virDomainDeviceDefParse(xml, vm->def, driver->xmlopt,
+                                        NULL, parse_flags)))
+        goto endjob;
+
+    /* virDomainDefCompatibleDevice call is delayed until we know the
+     * device we're going to update. */
+    if ((ret = testDomainUpdateDeviceConfig(vm->def, dev,
+                                            parse_flags,
+                                            driver->xmlopt)) < 0)
+        goto endjob;
+
+ endjob:
+    virDomainObjEndJob(vm);
+
+ cleanup:
+    virDomainObjEndAPI(&vm);
+    return ret;
+}
+
+
 /* search for a hostdev matching dev and detach it */
 static int
 testDomainDetachPrepHostdev(virDomainObj *vm,
@@ -10456,6 +10582,7 @@ static virHypervisorDriver testHypervisorDriver = {
     .domainAttachDevice = testDomainAttachDevice, /* 10.0.0 */
     .domainAttachDeviceFlags = testDomainAttachDeviceFlags, /* 10.0.0 */
     .domainDetachDeviceAlias = testDomainDetachDeviceAlias, /* 10.0.0 */
+    .domainUpdateDeviceFlags = testDomainUpdateDeviceFlags, /* 10.6.0 */
     .domainCreateXML = testDomainCreateXML, /* 0.1.4 */
     .domainCreateXMLWithFiles = testDomainCreateXMLWithFiles, /* 5.7.0 */
     .domainLookupByID = testDomainLookupByID, /* 0.1.1 */
-- 
2.34.1
Re: [PATCH] test_driver: add testUpdateDeviceFlags implementation
Posted by Michal Prívozník 2 months, 1 week ago
On 7/1/24 22:29, John Levon wrote:
> Add basic coverage of device update; for now, only support disk updates
> until other types are needed or tested.
> 
> Signed-off-by: John Levon <john.levon@nutanix.com>
> ---
>  src/test/test_driver.c | 127 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 127 insertions(+)
> 
> diff --git a/src/test/test_driver.c b/src/test/test_driver.c
> index 40fb8a467d..213fbdbccb 100644
> --- a/src/test/test_driver.c
> +++ b/src/test/test_driver.c
> @@ -41,6 +41,7 @@
>  #include "domain_conf.h"
>  #include "domain_driver.h"
>  #include "domain_event.h"
> +#include "domain_postparse.h"
>  #include "domain_validate.h"
>  #include "network_event.h"
>  #include "snapshot_conf.h"
> @@ -10237,6 +10238,131 @@ testDomainAttachDevice(virDomainPtr domain, const char *xml)
>  }
>  
>  
> +static int
> +testDomainUpdateDeviceConfig(virDomainDef *vmdef,
> +                             virDomainDeviceDef *dev,
> +                             unsigned int parse_flags,
> +                             virDomainXMLOption *xmlopt)
> +{
> +    virDomainDiskDef *newDisk;
> +    virDomainDeviceDef oldDev = { .type = dev->type };
> +    int pos;
> +
> +    switch (dev->type) {
> +    case VIR_DOMAIN_DEVICE_DISK:
> +        newDisk = dev->data.disk;
> +        if ((pos = virDomainDiskIndexByName(vmdef, newDisk->dst, false)) < 0) {
> +            virReportError(VIR_ERR_INVALID_ARG,
> +                           _("target %1$s doesn't exist."), newDisk->dst);
> +            return -1;
> +        }
> +
> +        oldDev.data.disk = vmdef->disks[pos];
> +        if (virDomainDefCompatibleDevice(vmdef, dev, &oldDev,
> +                                         VIR_DOMAIN_DEVICE_ACTION_UPDATE,
> +                                         false) < 0)
> +            return -1;
> +
> +        virDomainDiskDefFree(vmdef->disks[pos]);
> +        vmdef->disks[pos] = newDisk;
> +        dev->data.disk = NULL;
> +        break;
> +
> +    // TODO: support these here once tested.
> +    case VIR_DOMAIN_DEVICE_GRAPHICS:
> +    case VIR_DOMAIN_DEVICE_NET:
> +    case VIR_DOMAIN_DEVICE_MEMORY:
> +        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> +                       _("persistent update of device '%1$s' is not supported by test driver"),
> +                       virDomainDeviceTypeToString(dev->type));
> +        return -1;
> +

It's perfectly okay if ...

> +    case VIR_DOMAIN_DEVICE_FS:
> +    case VIR_DOMAIN_DEVICE_INPUT:
> +    case VIR_DOMAIN_DEVICE_SOUND:
> +    case VIR_DOMAIN_DEVICE_VIDEO:
> +    case VIR_DOMAIN_DEVICE_WATCHDOG:
> +    case VIR_DOMAIN_DEVICE_HUB:
> +    case VIR_DOMAIN_DEVICE_SMARTCARD:
> +    case VIR_DOMAIN_DEVICE_MEMBALLOON:
> +    case VIR_DOMAIN_DEVICE_NVRAM:
> +    case VIR_DOMAIN_DEVICE_RNG:
> +    case VIR_DOMAIN_DEVICE_SHMEM:
> +    case VIR_DOMAIN_DEVICE_LEASE:
> +    case VIR_DOMAIN_DEVICE_HOSTDEV:
> +    case VIR_DOMAIN_DEVICE_CONTROLLER:
> +    case VIR_DOMAIN_DEVICE_REDIRDEV:
> +    case VIR_DOMAIN_DEVICE_CHR:
> +    case VIR_DOMAIN_DEVICE_NONE:
> +    case VIR_DOMAIN_DEVICE_TPM:
> +    case VIR_DOMAIN_DEVICE_PANIC:
> +    case VIR_DOMAIN_DEVICE_IOMMU:
> +    case VIR_DOMAIN_DEVICE_VSOCK:
> +    case VIR_DOMAIN_DEVICE_AUDIO:
> +    case VIR_DOMAIN_DEVICE_CRYPTO:
> +    case VIR_DOMAIN_DEVICE_LAST:
> +        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> +                       _("persistent update of device '%1$s' is not supported"),
> +                       virDomainDeviceTypeToString(dev->type));

.. this error is reported instead.

> +        return -1;
> +    }
> +
> +    if (virDomainDefPostParse(vmdef, parse_flags, xmlopt, NULL) < 0)
> +        return -1;
> +
> +    return 0;
> +}
> +
> +
> +static int
> +testDomainUpdateDeviceFlags(virDomainPtr dom,
> +                            const char *xml,
> +                            unsigned int flags)
> +{
> +    testDriver *driver = dom->conn->privateData;
> +    virDomainObj *vm = NULL;
> +    g_autoptr(virDomainDef) vmdef = NULL;
> +    g_autoptr(virDomainDeviceDef) dev = NULL;
> +    int ret = -1;
> +    unsigned int parse_flags = 0;
> +
> +    virCheckFlags(VIR_DOMAIN_AFFECT_LIVE |
> +                  VIR_DOMAIN_AFFECT_CONFIG |
> +                  VIR_DOMAIN_DEVICE_MODIFY_FORCE, -1);

LIVE is not supported and thus shouldn't be in list of supported flags.
Neither MODIFY_FORCE.

> +
> +    if (!(vm = testDomObjFromDomain(dom)))
> +        goto cleanup;
> +
> +    if (virDomainObjBeginJob(vm, VIR_JOB_MODIFY) < 0)
> +        goto cleanup;
> +
> +    if (virDomainObjUpdateModificationImpact(vm, &flags) < 0)
> +        goto endjob;

After this, flags may contain LIVE and/or CONFIG. We need to recheck
whether LIVE is present and reject it.

> +
> +    if ((flags & VIR_DOMAIN_AFFECT_CONFIG) &&
> +        !(flags & VIR_DOMAIN_AFFECT_LIVE))
> +        parse_flags |= VIR_DOMAIN_DEF_PARSE_INACTIVE;
> +
> +    if (!(dev = virDomainDeviceDefParse(xml, vm->def, driver->xmlopt,
> +                                        NULL, parse_flags)))
> +        goto endjob;
> +
> +    /* virDomainDefCompatibleDevice call is delayed until we know the
> +     * device we're going to update. */
> +    if ((ret = testDomainUpdateDeviceConfig(vm->def, dev,
> +                                            parse_flags,
> +                                            driver->xmlopt)) < 0)
> +        goto endjob;


An event should be emitted to mimic real hypervisor behavior.

I'm squashing in necessary changes and merging.

Reviewed-by: Michal Privoznik <mprivozn@redhat.com>

Michal
Re: [PATCH] test_driver: add testUpdateDeviceFlags implementation
Posted by John Levon 2 months, 1 week ago
On Tue, Jul 02, 2024 at 04:07:48PM +0200, Michal Prívozník wrote:

> > +    // TODO: support these here once tested.
> > +    case VIR_DOMAIN_DEVICE_GRAPHICS:
> > +    case VIR_DOMAIN_DEVICE_NET:
> > +    case VIR_DOMAIN_DEVICE_MEMORY:
> > +        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> > +                       _("persistent update of device '%1$s' is not supported by test driver"),
> > +                       virDomainDeviceTypeToString(dev->type));
> > +        return -1;
> > +
> 
> It's perfectly okay if ...
> 
> > +        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
> > +                       _("persistent update of device '%1$s' is not supported"),
> > +                       virDomainDeviceTypeToString(dev->type));
> 
> .. this error is reported instead.

OK; I just thought it might be useful to separate out "we should add this" from
"we don't support this ever".

> > +    virCheckFlags(VIR_DOMAIN_AFFECT_LIVE |
> > +                  VIR_DOMAIN_AFFECT_CONFIG |
> > +                  VIR_DOMAIN_DEVICE_MODIFY_FORCE, -1);
> 
> LIVE is not supported and thus shouldn't be in list of supported flags.

I was a bit unclear on the test hypervisor case - in a sense everything is both
LIVE *and* CONFIG :)

> I'm squashing in necessary changes and merging.

Thanks!

regards
john
Re: [PATCH] test_driver: add testUpdateDeviceFlags implementation
Posted by Michal Prívozník 2 months ago
On 7/2/24 16:25, John Levon wrote:
> On Tue, Jul 02, 2024 at 04:07:48PM +0200, Michal Prívozník wrote:
> 
>>> +    // TODO: support these here once tested.
>>> +    case VIR_DOMAIN_DEVICE_GRAPHICS:
>>> +    case VIR_DOMAIN_DEVICE_NET:
>>> +    case VIR_DOMAIN_DEVICE_MEMORY:
>>> +        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
>>> +                       _("persistent update of device '%1$s' is not supported by test driver"),
>>> +                       virDomainDeviceTypeToString(dev->type));
>>> +        return -1;
>>> +
>>
>> It's perfectly okay if ...
>>
>>> +        virReportError(VIR_ERR_OPERATION_UNSUPPORTED,
>>> +                       _("persistent update of device '%1$s' is not supported"),
>>> +                       virDomainDeviceTypeToString(dev->type));
>>
>> .. this error is reported instead.
> 
> OK; I just thought it might be useful to separate out "we should add this" from
> "we don't support this ever".

I can see us having implementation for virtually all devices.

> 
>>> +    virCheckFlags(VIR_DOMAIN_AFFECT_LIVE |
>>> +                  VIR_DOMAIN_AFFECT_CONFIG |
>>> +                  VIR_DOMAIN_DEVICE_MODIFY_FORCE, -1);
>>
>> LIVE is not supported and thus shouldn't be in list of supported flags.
> 
> I was a bit unclear on the test hypervisor case - in a sense everything is both
> LIVE *and* CONFIG :)

I see what you mean, but no. The fact that libvirt doesn't really store
XMLs on disk for inactive domains doesn't necessarily mean there's no
distinction. The aim of test driver is to mimic real life scenarios as
closely as possible, so that when somebody is developing an app on top
of libvirt they can use the test driver instead of spawning real VMs and
thus test their app quickly. Therefore, the distinction between inactive
and live XMLs must be preserved.

> 
>> I'm squashing in necessary changes and merging.
> 
> Thanks!

You're welcome.

Michal
Re: [PATCH] test_driver: add testUpdateDeviceFlags implementation
Posted by John Levon 2 months ago
On Wed, Jul 03, 2024 at 09:44:47AM +0200, Michal Prívozník wrote:

> >> LIVE is not supported and thus shouldn't be in list of supported flags.
> > 
> > I was a bit unclear on the test hypervisor case - in a sense everything is both
> > LIVE *and* CONFIG :)
> 
> I see what you mean, but no. The fact that libvirt doesn't really store
> XMLs on disk for inactive domains doesn't necessarily mean there's no
> distinction. The aim of test driver is to mimic real life scenarios as
> closely as possible, so that when somebody is developing an app on top
> of libvirt they can use the test driver instead of spawning real VMs and
> thus test their app quickly. Therefore, the distinction between inactive
> and live XMLs must be preserved.

I get the rationale, but now we're inconsistent with our previous contribution:

  10247 static int
  10248 testDomainAttachDeviceLiveAndConfig(virDomainObj *vm,
  10249                                     testDriver *driver,
  10250                                     const char *xml,
  10251                                     unsigned int flags)
  10252 {
  10253     g_autoptr(virDomainDeviceDef) devLive = NULL;
  10254     unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE |
  10255                                VIR_DOMAIN_DEF_PARSE_ABI_UPDATE;
  10256
  10257     virCheckFlags(VIR_DOMAIN_AFFECT_LIVE, -1);

Given that the test driver does not and cannot have a persisted configuration
(there is only the in-process ->vmdef), I'm confused why you made the new code
be:

10399     virCheckFlags(VIR_DOMAIN_AFFECT_CONFIG, -1);

in testDomainUpdateDeviceFlags().

For our application we actually do want to pass the LIVE flag - we never want to
modify any persistent configuration. We'd prefer not to have to carry a patch on
top for this.

regards
john
Re: [PATCH] test_driver: add testUpdateDeviceFlags implementation
Posted by Michal Prívozník 2 months ago
On 7/3/24 18:29, John Levon wrote:
> On Wed, Jul 03, 2024 at 09:44:47AM +0200, Michal Prívozník wrote:
> 
>>>> LIVE is not supported and thus shouldn't be in list of supported flags.
>>>
>>> I was a bit unclear on the test hypervisor case - in a sense everything is both
>>> LIVE *and* CONFIG :)
>>
>> I see what you mean, but no. The fact that libvirt doesn't really store
>> XMLs on disk for inactive domains doesn't necessarily mean there's no
>> distinction. The aim of test driver is to mimic real life scenarios as
>> closely as possible, so that when somebody is developing an app on top
>> of libvirt they can use the test driver instead of spawning real VMs and
>> thus test their app quickly. Therefore, the distinction between inactive
>> and live XMLs must be preserved.
> 
> I get the rationale, but now we're inconsistent with our previous contribution:
> 
>   10247 static int
>   10248 testDomainAttachDeviceLiveAndConfig(virDomainObj *vm,
>   10249                                     testDriver *driver,
>   10250                                     const char *xml,
>   10251                                     unsigned int flags)
>   10252 {
>   10253     g_autoptr(virDomainDeviceDef) devLive = NULL;
>   10254     unsigned int parse_flags = VIR_DOMAIN_DEF_PARSE_INACTIVE |
>   10255                                VIR_DOMAIN_DEF_PARSE_ABI_UPDATE;
>   10256
>   10257     virCheckFlags(VIR_DOMAIN_AFFECT_LIVE, -1);
> 
> Given that the test driver does not and cannot have a persisted configuration
> (there is only the in-process ->vmdef), I'm confused why you made the new code
> be:
> 
> 10399     virCheckFlags(VIR_DOMAIN_AFFECT_CONFIG, -1);
> 
> in testDomainUpdateDeviceFlags().

The fact that domain XML is not stored on a disk doesn't necessarily
mean it can't have inactive XML. Those are two different things. Just
consider:

virsh -c test:///default
Welcome to virsh, the virtualization interactive terminal.

Type:  'help' for help with commands
       'quit' to quit

virsh # domstate test
running

virsh # destroy test
Domain 'test' destroyed

virsh # domstate test
shut off


And in fact, inactive (sometimes called also config) XML can differ from
that of a running domain. Hence '--inactive' flag to 'dumpxml'. And
since the testDomainUpdateDeviceFlags() is made to error out on
VIR_DOMAIN_AFFECT_LIVE flag it makes no sense to accept the flag.
Therefore it's not listed in virCheckFlags().

There's one caveat - if the API is called with flags=0 then it means
"affect current state of the domain". flgas is then updated in
virDomainObjUpdateModificationImpact() and thus _LIVE needs to be
checked again.

> 
> For our application we actually do want to pass the LIVE flag - we never want to
> modify any persistent configuration. We'd prefer not to have to carry a patch on
> top for this.

Ah, I thought you're interested only in _CONFIG since that's what your
original patch implemented. But implementing live update should be
pretty trivial. It's a test driver so "hypervisor" will allow just any
change. Do you want to post a patch for that or should I?

Michal
Re: [PATCH] test_driver: add testUpdateDeviceFlags implementation
Posted by John Levon 2 months ago
On Thu, Jul 04, 2024 at 08:24:30AM +0200, Michal Prívozník wrote:

> The fact that domain XML is not stored on a disk doesn't necessarily
> mean it can't have inactive XML. Those are two different things.

I spent a bit of time playing, and the behaviour was more implemented than I
thought for the non-LIVE case; I think I was missing how the vmdefs are
retrieved based upon the flags.

> > For our application we actually do want to pass the LIVE flag - we never want to
> > modify any persistent configuration. We'd prefer not to have to carry a patch on
> > top for this.
> 
> Ah, I thought you're interested only in _CONFIG since that's what your
> original patch implemented.

I took a look at my original patch with fresh eyes, and see it was a bit
confused in fact. It does implement LIVE (vm->def) (and I tested that), but
picked the wrong function name from the qemu driver sources:

10427     if ((ret = testDomainUpdateDeviceConfig(vm->def, dev,
                                           ^^^^^^ ^^^^^^^
10428                                             parse_flags,
10429                                             driver->xmlopt)) < 0)

Obviously that makes no sense!

> But implementing live update should be
> pretty trivial. It's a test driver so "hypervisor" will allow just any
> change. Do you want to post a patch for that or should I?

Now I have a better idea of what was wrong, let me have another pass.

thanks
john