From 3a204b036f04ebf63c53dca205a856a1aaaa53ff Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Fri, 26 Jul 2019 15:52:08 -0500 Subject: [PATCH] backup: Wire up qemu checkpoint commands over QMP Time to actually issue the QMP transactions that create and delete persistent checkpoints, resolving TODOs intentionally left earlier in the series. For create, we only need one transaction: inside, we visit all disks affected by the checkpoint, and create a new enabled bitmap, as well as disabling the bitmap of the first ancestor checkpoint (if any) that also had a bitmap. For deletion, we need multiple QMP calls: for each disk, if there is an ancestor checkpoint with a bitmap, then the bitmap must be merged (including activating the ancestor bitmap if the leaf node changes), all before deleting the bitmap from the checkpoint being removed. Signed-off-by: Eric Blake --- src/qemu/qemu_domain.c | 69 +++++++++++++++++++++++++++-- src/qemu/qemu_driver.c | 98 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 163 insertions(+), 4 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 4fa1a4ac76..fce307728b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -9375,6 +9375,9 @@ qemuDomainCheckpointDiscard(virQEMUDriverPtr driver, bool metadata_only) { virDomainMomentObjPtr parent = NULL; + virDomainMomentObjPtr moment; + virDomainCheckpointDefPtr parentdef = NULL; + size_t i, j; VIR_AUTOUNREF(virQEMUDriverConfigPtr) cfg = virQEMUDriverGetConfig(driver); VIR_AUTOFREE(char *) chkFile = NULL; @@ -9388,9 +9391,69 @@ qemuDomainCheckpointDiscard(virQEMUDriverPtr driver, vm->def->name, chk->def->name) < 0) return -1; - /* TODO: Implement QMP sequence to merge bitmaps */ - parent = virDomainCheckpointFindByName(vm->checkpoints, - chk->def->parent_name); + if (!metadata_only) { + qemuDomainObjPrivatePtr priv = vm->privateData; + bool success = true; + bool search_parents; + virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk); + + qemuDomainObjEnterMonitor(driver, vm); + parent = virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent_name); + for (i = 0; i < chkdef->ndisks; i++) { + virDomainCheckpointDiskDef *disk = &chkdef->disks[i]; + const char *node; + + if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) + continue; + + node = qemuDomainDiskNodeFormatLookup(vm, disk->name); + /* If any ancestor checkpoint has a bitmap for the same + * disk, then this bitmap must be merged to the + * ancestor. */ + search_parents = true; + for (moment = parent; + search_parents && moment; + moment = virDomainCheckpointFindByName(vm->checkpoints, + parentdef->parent.parent_name)) { + parentdef = virDomainCheckpointObjGetDef(moment); + for (j = 0; j < parentdef->ndisks; j++) { + virDomainCheckpointDiskDef *disk2; + VIR_AUTOPTR(virJSONValue) arr = NULL; + + disk2 = &parentdef->disks[j]; + if (STRNEQ(disk->name, disk2->name) || + disk2->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) + continue; + search_parents = false; + + arr = virJSONValueNewArray(); + if (!arr || + virJSONValueArrayAppendString(arr, disk->bitmap) < 0) { + success = false; + break; + } + if (chk == virDomainCheckpointGetCurrent(vm->checkpoints) && + qemuMonitorEnableBitmap(priv->mon, node, + disk2->bitmap) < 0) { + success = false; + break; + } + if (qemuMonitorMergeBitmaps(priv->mon, node, + disk2->bitmap, &arr) < 0) { + success = false; + break; + } + } + } + if (qemuMonitorDeleteBitmap(priv->mon, node, disk->bitmap) < 0) { + success = false; + break; + } + } + if (qemuDomainObjExitMonitor(driver, vm) < 0 || !success) + return -1; + } if (chk == virDomainCheckpointGetCurrent(vm->checkpoints)) { virDomainCheckpointSetCurrent(vm->checkpoints, NULL); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index eadceddcf0..691e27fd9f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -47,6 +47,7 @@ #include "qemu_hostdev.h" #include "qemu_hotplug.h" #include "qemu_monitor.h" +#include "qemu_monitor_json.h" #include "qemu_process.h" #include "qemu_migration.h" #include "qemu_migration_params.h" @@ -17023,6 +17024,65 @@ qemuDomainCheckpointPrepare(virQEMUDriverPtr driver, virCapsPtr caps, return ret; } +static int +qemuDomainCheckpointAddActions(virDomainObjPtr vm, + virJSONValuePtr actions, + virDomainMomentObjPtr old_current, + virDomainCheckpointDefPtr def) +{ + size_t i, j; + virDomainCheckpointDefPtr olddef; + virDomainMomentObjPtr parent; + bool search_parents; + + for (i = 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDef *disk = &def->disks[i]; + const char *node; + + if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) + continue; + node = qemuDomainDiskNodeFormatLookup(vm, disk->name); + if (qemuMonitorJSONTransactionAdd(actions, + "block-dirty-bitmap-add", + "s:node", node, + "s:name", disk->bitmap, + "b:persistent", true, + NULL) < 0) + return -1; + + /* We only want one active bitmap for a disk along the + * checkpoint chain, then later differential backups will + * merge the bitmaps (only one active) between the bounding + * checkpoint and the leaf checkpoint. If the same disks are + * involved in each checkpoint, this search terminates in one + * iteration; but it is also possible to have to search + * further than the immediate parent to find another + * checkpoint with a bitmap on the same disk. */ + search_parents = true; + for (parent = old_current; search_parents && parent; + parent = virDomainCheckpointFindByName(vm->checkpoints, + olddef->parent.parent_name)) { + olddef = virDomainCheckpointObjGetDef(parent); + for (j = 0; j < olddef->ndisks; j++) { + virDomainCheckpointDiskDef *disk2; + + disk2 = &olddef->disks[j]; + if (STRNEQ(disk->name, disk2->name) || + disk2->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) + continue; + if (qemuMonitorJSONTransactionAdd(actions, + "block-dirty-bitmap-disable", + "s:node", node, + "s:name", disk2->bitmap, + NULL) < 0) + return -1; + search_parents = false; + break; + } + } + } + return 0; +} static virDomainCheckpointPtr qemuDomainCheckpointCreateXML(virDomainPtr domain, @@ -17040,6 +17100,9 @@ qemuDomainCheckpointCreateXML(virDomainPtr domain, virDomainMomentObjPtr other = NULL; virQEMUDriverConfigPtr cfg = NULL; virCapsPtr caps = NULL; + qemuDomainObjPrivatePtr priv; + virJSONValuePtr actions = NULL; + int ret; VIR_AUTOUNREF(virDomainCheckpointDefPtr) def = NULL; virCheckFlags(VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE, NULL); @@ -17059,11 +17122,18 @@ qemuDomainCheckpointCreateXML(virDomainPtr domain, goto cleanup; } + priv = vm->privateData; cfg = virQEMUDriverGetConfig(driver); if (virDomainCheckpointCreateXMLEnsureACL(domain->conn, vm->def, flags) < 0) goto cleanup; + if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BITMAP_MERGE)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("qemu binary lacks persistent bitmaps support")); + goto cleanup; + } + if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; @@ -17124,7 +17194,15 @@ qemuDomainCheckpointCreateXML(virDomainPtr domain, * makes sense, such as checking that qemu-img recognizes the * checkpoint bitmap name in at least one of the domain's disks? */ } else { - /* TODO: issue QMP transaction command */ + if (!(actions = virJSONValueNewArray())) + goto endjob; + if (qemuDomainCheckpointAddActions(vm, actions, other, + virDomainCheckpointObjGetDef(chk)) < 0) + goto endjob; + qemuDomainObjEnterMonitor(driver, vm); + ret = qemuMonitorTransaction(priv->mon, &actions); + if (qemuDomainObjExitMonitor(driver, vm) < 0 || ret < 0) + goto endjob; } /* If we fail after this point, there's not a whole lot we can do; @@ -17158,6 +17236,7 @@ qemuDomainCheckpointCreateXML(virDomainPtr domain, qemuDomainObjEndJob(driver, vm); cleanup: + virJSONValueFree(actions); virDomainObjEndAPI(&vm); VIR_FREE(xml); virObjectUnref(caps); @@ -17329,6 +17408,7 @@ qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, { virQEMUDriverPtr driver = checkpoint->domain->conn->privateData; virDomainObjPtr vm = NULL; + qemuDomainObjPrivatePtr priv; int ret = -1; virDomainMomentObjPtr chk = NULL; virQEMUMomentRemove rem; @@ -17351,6 +17431,22 @@ qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; + priv = vm->privateData; + if (!metadata_only) { + /* Until qemu-img supports offline bitmap deletion, we are stuck + * with requiring a running guest */ + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot delete checkpoint for inactive domain")); + goto endjob; + } + if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BITMAP_MERGE)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("qemu binary lacks persistent bitmaps support")); + goto endjob; + } + } + if (!(chk = qemuCheckpointObjFromCheckpoint(vm, checkpoint))) goto endjob; -- GitLab