[PATCH v4 05/14] qemu: Wire up <memory/> live update

Michal Privoznik mprivozn at redhat.com
Wed Jun 23 10:12:10 UTC 2021


As advertised in one of previous commits, we want to be able to
change 'requested-size' attribute of virtio-mem on the fly. This
commit does exactly that. Changing anything else is checked for
and forbidden.

Once guest has changed the allocation, QEMU emits an event which
we will use to track the allocation. In the next commit.

Signed-off-by: Michal Privoznik <mprivozn at redhat.com>
Reviewed-by: Peter Krempa <pkrempa at redhat.com>
---
 src/conf/domain_conf.c       |  39 +++++++-
 src/conf/domain_conf.h       |   4 +
 src/libvirt_private.syms     |   1 +
 src/qemu/qemu_driver.c       | 174 ++++++++++++++++++++++++++++++++++-
 src/qemu/qemu_hotplug.c      |  18 ++++
 src/qemu/qemu_hotplug.h      |   5 +
 src/qemu/qemu_monitor.c      |  13 +++
 src/qemu/qemu_monitor.h      |   5 +
 src/qemu/qemu_monitor_json.c |  15 +++
 src/qemu/qemu_monitor_json.h |   5 +
 10 files changed, 277 insertions(+), 2 deletions(-)

diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index cac484c9a2..76344b592d 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -16549,6 +16549,42 @@ virDomainMemoryFindInactiveByDef(virDomainDef *def,
 }
 
 
+/**
+ * virDomainMemoryFindByDeviceInfo:
+ * @def: domain defintion
+ * @info: device info to match
+ *
+ * For given domain definition @def find <memory/> device with
+ * matching address and matching device alias (if set in @info,
+ * otherwise ignored).
+ *
+ * Returns: device if found,
+ *          NULL otherwise.
+ */
+virDomainMemoryDef *
+virDomainMemoryFindByDeviceInfo(virDomainDef *def,
+                                virDomainDeviceInfo *info)
+{
+    size_t i;
+
+    for (i = 0; i < def->nmems; i++) {
+        virDomainMemoryDef *tmp = def->mems[i];
+
+        if (!virDomainDeviceInfoAddressIsEqual(&tmp->info, info))
+            continue;
+
+        /* alias, if present */
+        if (info->alias &&
+            STRNEQ_NULLABLE(tmp->info.alias, info->alias))
+            continue;
+
+        return tmp;
+    }
+
+    return NULL;
+}
+
+
 /**
  * virDomainMemoryInsert:
  *
@@ -28515,7 +28551,8 @@ virDomainDefCompatibleDevice(virDomainDef *def,
             return -1;
         }
 
-        if ((virDomainDefGetMemoryTotal(def) + sz) > def->mem.max_memory) {
+        if (action == VIR_DOMAIN_DEVICE_ACTION_ATTACH &&
+            (virDomainDefGetMemoryTotal(def) + sz) > def->mem.max_memory) {
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                            _("Attaching memory device with size '%llu' would "
                              "exceed domain's maxMemory config size '%llu'"),
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index bafa379118..0c43a6ae64 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -3771,6 +3771,10 @@ int virDomainMemoryFindByDef(virDomainDef *def, virDomainMemoryDef *mem)
 int virDomainMemoryFindInactiveByDef(virDomainDef *def,
                                      virDomainMemoryDef *mem)
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT;
+virDomainMemoryDef *
+virDomainMemoryFindByDeviceInfo(virDomainDef *dev,
+                                virDomainDeviceInfo *info)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT;
 
 int virDomainShmemDefInsert(virDomainDef *def, virDomainShmemDef *shmem)
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 19f0d0ddc8..57290b6573 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -503,6 +503,7 @@ virDomainMemballoonModelTypeFromString;
 virDomainMemballoonModelTypeToString;
 virDomainMemoryDefFree;
 virDomainMemoryFindByDef;
+virDomainMemoryFindByDeviceInfo;
 virDomainMemoryFindInactiveByDef;
 virDomainMemoryInsert;
 virDomainMemoryModelTypeToString;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 235f575901..a638c67f1e 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -7113,6 +7113,175 @@ qemuDomainChangeDiskLive(virDomainObj *vm,
     return 0;
 }
 
+
+static bool
+qemuDomainChangeMemoryLiveValidateChange(const virDomainMemoryDef *oldDef,
+                                         const virDomainMemoryDef *newDef)
+{
+    /* The only thing that is allowed to change is 'requestedsize' for
+     * virtio-mem model. Check if user isn't trying to sneak in change for
+     * something else. */
+
+    switch (oldDef->model) {
+    case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
+        break;
+
+    case VIR_DOMAIN_MEMORY_MODEL_NONE:
+    case VIR_DOMAIN_MEMORY_MODEL_DIMM:
+    case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
+    case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
+    case VIR_DOMAIN_MEMORY_MODEL_LAST:
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory of model '%s'"),
+                       virDomainMemoryModelTypeToString(oldDef->model));
+        return false;
+        break;
+    }
+
+    if (oldDef->model != newDef->model) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory model from '%s' to '%s'"),
+                       virDomainMemoryModelTypeToString(oldDef->model),
+                       virDomainMemoryModelTypeToString(newDef->model));
+        return false;
+    }
+
+    if (oldDef->access != newDef->access) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory access from '%s' to '%s'"),
+                       virDomainMemoryAccessTypeToString(oldDef->access),
+                       virDomainMemoryAccessTypeToString(newDef->access));
+        return false;
+    }
+
+    if (oldDef->discard != newDef->discard) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory discard from '%s' to '%s'"),
+                       virTristateBoolTypeToString(oldDef->discard),
+                       virTristateBoolTypeToString(newDef->discard));
+        return false;
+    }
+
+    if (!virBitmapEqual(oldDef->sourceNodes,
+                        newDef->sourceNodes)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("cannot modify memory source nodes"));
+        return false;
+    }
+
+    if (oldDef->pagesize != newDef->pagesize) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory pagesize from '%llu' to '%llu'"),
+                       oldDef->pagesize,
+                       newDef->pagesize);
+        return false;
+    }
+
+    if (STRNEQ_NULLABLE(oldDef->nvdimmPath, newDef->nvdimmPath)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory path from '%s' to '%s'"),
+                       NULLSTR(oldDef->nvdimmPath),
+                       NULLSTR(newDef->nvdimmPath));
+        return false;
+    }
+
+    if (oldDef->alignsize != newDef->alignsize) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory align size from '%llu' to '%llu'"),
+                       oldDef->alignsize, newDef->alignsize);
+        return false;
+    }
+
+    if (oldDef->nvdimmPmem != newDef->nvdimmPmem) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory pmem from '%d' to '%d'"),
+                       oldDef->nvdimmPmem, newDef->nvdimmPmem);
+        return false;
+    }
+
+    if (oldDef->targetNode != newDef->targetNode) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory targetNode from '%d' to '%d'"),
+                       oldDef->targetNode, newDef->targetNode);
+        return false;
+    }
+
+    if (oldDef->size != newDef->size) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory size from '%llu' to '%llu'"),
+                       oldDef->size, newDef->size);
+        return false;
+    }
+
+    if (oldDef->labelsize != newDef->labelsize) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory label size from '%llu' to '%llu'"),
+                       oldDef->labelsize, newDef->labelsize);
+        return false;
+    }
+    if (oldDef->blocksize != newDef->blocksize) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("cannot modify memory block size from '%llu' to '%llu'"),
+                       oldDef->blocksize, newDef->blocksize);
+        return false;
+    }
+
+    /* requestedsize can change */
+
+    if (oldDef->readonly != newDef->readonly) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("cannot modify memory pmem flag"));
+        return false;
+    }
+
+    if ((oldDef->uuid || newDef->uuid) &&
+        !(oldDef->uuid && newDef->uuid &&
+          memcmp(oldDef->uuid, newDef->uuid, VIR_UUID_BUFLEN) == 0)) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("cannot modify memory UUID"));
+        return false;
+    }
+
+    return true;
+}
+
+
+static int
+qemuDomainChangeMemoryLive(virQEMUDriver *driver G_GNUC_UNUSED,
+                           virDomainObj *vm,
+                           virDomainDeviceDef *dev)
+{
+    virDomainDeviceDef oldDev = { .type = VIR_DOMAIN_DEVICE_MEMORY };
+    virDomainMemoryDef *newDef = dev->data.memory;
+    virDomainMemoryDef *oldDef = NULL;
+
+    oldDef = virDomainMemoryFindByDeviceInfo(vm->def, &dev->data.memory->info);
+    if (!oldDef) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                       _("memory '%s' not found"), dev->data.memory->info.alias);
+        return -1;
+    }
+
+    oldDev.data.memory = oldDef;
+
+    if (virDomainDefCompatibleDevice(vm->def, dev, &oldDev,
+                                     VIR_DOMAIN_DEVICE_ACTION_UPDATE,
+                                     true) < 0)
+        return -1;
+
+    if (!qemuDomainChangeMemoryLiveValidateChange(oldDef, newDef))
+        return -1;
+
+    if (qemuDomainChangeMemoryRequestedSize(driver, vm,
+                                            newDef->info.alias,
+                                            newDef->requestedsize) < 0)
+        return -1;
+
+    oldDef->requestedsize = newDef->requestedsize;
+    return 0;
+}
+
+
 static int
 qemuDomainUpdateDeviceLive(virDomainObj *vm,
                            virDomainDeviceDef *dev,
@@ -7154,6 +7323,10 @@ qemuDomainUpdateDeviceLive(virDomainObj *vm,
         ret = qemuDomainChangeNet(driver, vm, dev);
         break;
 
+    case VIR_DOMAIN_DEVICE_MEMORY:
+        ret = qemuDomainChangeMemoryLive(driver, vm, dev);
+        break;
+
     case VIR_DOMAIN_DEVICE_FS:
     case VIR_DOMAIN_DEVICE_INPUT:
     case VIR_DOMAIN_DEVICE_SOUND:
@@ -7169,7 +7342,6 @@ qemuDomainUpdateDeviceLive(virDomainObj *vm,
     case VIR_DOMAIN_DEVICE_HOSTDEV:
     case VIR_DOMAIN_DEVICE_CONTROLLER:
     case VIR_DOMAIN_DEVICE_REDIRDEV:
-    case VIR_DOMAIN_DEVICE_MEMORY:
     case VIR_DOMAIN_DEVICE_CHR:
     case VIR_DOMAIN_DEVICE_NONE:
     case VIR_DOMAIN_DEVICE_TPM:
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index d2a354d026..d9ca4016ff 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -6743,3 +6743,21 @@ qemuDomainSetVcpuInternal(virQEMUDriver *driver,
     virBitmapFree(livevcpus);
     return ret;
 }
+
+
+int
+qemuDomainChangeMemoryRequestedSize(virQEMUDriver *driver,
+                                    virDomainObj *vm,
+                                    const char *alias,
+                                    unsigned long long requestedsize)
+{
+    qemuDomainObjPrivate *priv = vm->privateData;
+    int rc;
+
+    qemuDomainObjEnterMonitor(driver, vm);
+    rc = qemuMonitorChangeMemoryRequestedSize(priv->mon, alias, requestedsize);
+    if (qemuDomainObjExitMonitor(driver, vm) < 0)
+        return -1;
+
+    return rc;
+}
diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h
index b5f7afb076..ae8feb459c 100644
--- a/src/qemu/qemu_hotplug.h
+++ b/src/qemu/qemu_hotplug.h
@@ -166,3 +166,8 @@ int qemuHotplugAttachDBusVMState(virQEMUDriver *driver,
 int qemuHotplugRemoveDBusVMState(virQEMUDriver *driver,
                                  virDomainObj *vm,
                                  qemuDomainAsyncJob asyncJob);
+
+int qemuDomainChangeMemoryRequestedSize(virQEMUDriver *driver,
+                                        virDomainObj *vm,
+                                        const char *alias,
+                                        unsigned long long requestedsize);
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
index 8f35b4240f..be04684585 100644
--- a/src/qemu/qemu_monitor.c
+++ b/src/qemu/qemu_monitor.c
@@ -4743,3 +4743,16 @@ qemuMonitorQueryDirtyRate(qemuMonitor *mon,
 
     return qemuMonitorJSONQueryDirtyRate(mon, info);
 }
+
+
+int
+qemuMonitorChangeMemoryRequestedSize(qemuMonitor *mon,
+                                     const char *alias,
+                                     unsigned long long requestedsize)
+{
+    VIR_DEBUG("alias=%s requestedsize=%llu", alias, requestedsize);
+
+    QEMU_CHECK_MONITOR(mon);
+
+    return qemuMonitorJSONChangeMemoryRequestedSize(mon, alias, requestedsize);
+}
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index 6a25def78b..b5de5711e0 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -1496,3 +1496,8 @@ struct _qemuMonitorDirtyRateInfo {
 int
 qemuMonitorQueryDirtyRate(qemuMonitor *mon,
                           qemuMonitorDirtyRateInfo *info);
+
+int
+qemuMonitorChangeMemoryRequestedSize(qemuMonitor *mon,
+                                     const char *alias,
+                                     unsigned long long requestedsize);
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 223777739d..d3db88a389 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -9404,3 +9404,18 @@ qemuMonitorJSONQueryDirtyRate(qemuMonitor *mon,
 
     return qemuMonitorJSONExtractDirtyRateInfo(data, info);
 }
+
+
+int
+qemuMonitorJSONChangeMemoryRequestedSize(qemuMonitor *mon,
+                                         const char *alias,
+                                         unsigned long long requestedsize)
+{
+    g_autofree char *path = g_strdup_printf("/machine/peripheral/%s", alias);
+    qemuMonitorJSONObjectProperty prop = {
+        .type = QEMU_MONITOR_OBJECT_PROPERTY_ULONG,
+        .val.ul = requestedsize * 1024, /* monitor needs bytes */
+    };
+
+    return qemuMonitorJSONSetObjectProperty(mon, path, "requested-size", &prop);
+}
diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h
index 01a3ba25f1..f91917b671 100644
--- a/src/qemu/qemu_monitor_json.h
+++ b/src/qemu/qemu_monitor_json.h
@@ -706,3 +706,8 @@ qemuMonitorJSONStartDirtyRateCalc(qemuMonitor *mon,
 int
 qemuMonitorJSONQueryDirtyRate(qemuMonitor *mon,
                               qemuMonitorDirtyRateInfo *info);
+
+int
+qemuMonitorJSONChangeMemoryRequestedSize(qemuMonitor *mon,
+                                         const char *alias,
+                                         unsigned long long requestedsize);
-- 
2.31.1




More information about the libvir-list mailing list