Complete wiring up incremental backup, by adding in support for creating a checkpoint at the same time as a backup (make the transaction have a few more steps) as well as exposing the dirty bitmap for a prior backup over NBD (requires creating a temporary bitmap, merging all appropriate bitmaps in, then exposing that bitmap over NBD). Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> --- src/qemu/qemu_driver.c | 197 +++++++++++++++++++++++++++++++++++------ 1 file changed, 169 insertions(+), 28 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 9c2bc8175a..238de51dc5 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -17068,6 +17068,24 @@ qemuDomainCheckpointPrepare(virQEMUDriverPtr driver, virCapsPtr caps, if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) continue; + /* We want to name temporary bitmap after disk name during + * incremental backup, which is not possible if that is a + * persistent bitmap name. We can also make life easier by + * enforcing bitmap names match checkpoint name, although this + * is not technically necessary. */ + if (STREQ(disk->name, disk->bitmap)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("checkpoint for disk %s must have distinct bitmap name"), + disk->name); + goto cleanup; + } + if (STRNEQ(disk->bitmap, def->common.name)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk %s bitmap should match checkpoint name %s"), + disk->name, def->common.name); + goto cleanup; + } + if (vm->def->disks[i]->src->format > 0 && vm->def->disks[i]->src->format != VIR_STORAGE_FILE_QCOW2) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, @@ -17724,19 +17742,43 @@ qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, static int qemuDomainBackupPrepare(virQEMUDriverPtr driver, virDomainObjPtr vm, - virDomainBackupDefPtr def) + virDomainBackupDefPtr def, + virDomainMomentObjPtr chk) { int ret = -1; size_t i; + virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk); + if (chk && def->ndisks != chkdef->ndisks) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("inconsistency between backup and checkpoint disks")); + goto cleanup; + } if (qemuBlockNodeNamesDetect(driver, vm, QEMU_ASYNC_JOB_NONE) < 0) goto cleanup; for (i = 0; i < def->ndisks; i++) { virDomainBackupDiskDef *disk = &def->disks[i]; virStorageSourcePtr src = vm->def->disks[disk->idx]->src; - if (!disk->store) + /* For now, insist that atomic checkpoint affect same disks as + * those being backed up. */ + if (!disk->store) { + if (chk && + chkdef->disks[i].type != VIR_DOMAIN_CHECKPOINT_TYPE_NONE) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("disk %s requested checkpoint without backup"), + disk->name); + goto cleanup; + } continue; + } + if (chk && + chkdef->disks[i].type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, + _("disk %s requested backup without checkpoint"), + disk->name); + goto cleanup; + } if (virAsprintf(&disk->store->nodeformat, "tmp-%s", disk->name) < 0) goto cleanup; if (!disk->store->format) @@ -17770,7 +17812,7 @@ qemuDomainBackupDiskCleanup(virQEMUDriverPtr driver, virDomainObjPtr vm, * shortly be calling nbd-server-stop. */ } if (incremental && disk->state >= VIR_DOMAIN_BACKUP_DISK_STATE_BITMAP && - qemuMonitorDeleteBitmap(priv->mon, node, disk->store->nodeformat) < 0) { + qemuMonitorDeleteBitmap(priv->mon, node, disk->name) < 0) { VIR_WARN("Unable to remove temp bitmap for disk %s after backup", disk->name); ret = -1; @@ -17808,28 +17850,22 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, bool job_started = false; bool nbd_running = false; bool push; + const char *mode; size_t i; struct timeval tv; char *suffix = NULL; virCommandPtr cmd = NULL; const char *qemuImgPath; + virDomainCheckpointDefPtr chkdef = NULL; + virDomainMomentObjPtr chk = NULL; + virDomainMomentObjPtr other = NULL; + virDomainMomentObjPtr parent = NULL; + virDomainMomentObjPtr current; + virJSONValuePtr arr = NULL; virCheckFlags(VIR_DOMAIN_BACKUP_BEGIN_NO_METADATA, -1); /* TODO: VIR_DOMAIN_BACKUP_BEGIN_QUIESCE */ - // FIXME: Support non-null checkpointXML for incremental - what - // code can be shared with CheckpointCreateXML, then add to transaction - // to create new checkpoint at same time as starting blockdev-backup - if (checkpointXml) { - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("cannot create incremental backups yet")); - return -1; - } - // if (chk) VIR_STRDUP(suffix, chk->name); - gettimeofday(&tv, NULL); - if (virAsprintf(&suffix, "%lld", (long long)tv.tv_sec) < 0) - goto cleanup; - if (!(vm = qemuDomObjFromDomain(domain))) goto cleanup; @@ -17856,6 +17892,17 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, if (!(def = virDomainBackupDefParseString(diskXml, driver->xmlopt, 0))) goto cleanup; + if (checkpointXml) { + if (!(chkdef = qemuDomainCheckpointDefParseString(driver, caps, + checkpointXml, 0)) || + VIR_STRDUP(suffix, chkdef->common.name) < 0) + goto cleanup; + } else { + gettimeofday(&tv, NULL); + if (virAsprintf(&suffix, "%lld", (long long)tv.tv_sec) < 0) + goto cleanup; + } + push = def->type == VIR_DOMAIN_BACKUP_TYPE_PUSH; if (!push) { if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NBD_BITMAP)) { @@ -17870,15 +17917,25 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, goto cleanup; } } + current = virDomainCheckpointGetCurrent(vm->checkpoints); if (def->incremental) { if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BITMAP_MERGE)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("qemu binary lacks persistent bitmaps support")); goto cleanup; } - virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", - _("cannot create incremental backups yet")); - goto cleanup; + for (other = current; other; + other = other->def->parent ? + virDomainCheckpointFindByName(vm->checkpoints, + other->def->parent) : NULL) + if (STREQ(other->def->name, def->incremental)) + break; + if (!other) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("could not locate checkpoint '%s' for incremental backup"), + def->incremental); + goto cleanup; + } } if (!(qemuImgPath = qemuFindQemuImgBinary(driver))) @@ -17894,14 +17951,38 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, goto endjob; } + if (chkdef) { + if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BITMAP_MERGE)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("qemu binary lacks persistent bitmaps support")); + goto endjob; + } + + if (qemuDomainCheckpointPrepare(driver, caps, vm, chkdef) < 0) + goto endjob; + if (!(chk = virDomainCheckpointAssignDef(vm->checkpoints, chkdef))) + goto endjob; + chkdef = NULL; + if (current) { + parent = current; + if (VIR_STRDUP(chk->def->parent, parent->def->name) < 0) + goto endjob; + if (qemuDomainCheckpointWriteMetadata(vm, parent, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) + goto endjob; + } + } + if (virDomainBackupAlignDisks(def, vm->def, suffix) < 0 || - qemuDomainBackupPrepare(driver, vm, def) < 0) + qemuDomainBackupPrepare(driver, vm, def, chk) < 0) goto endjob; /* actually start the checkpoint. 2x2 array of push/pull, full/incr, plus additional tweak if checkpoint requested */ qemuDomainObjEnterMonitor(driver, vm); - /* - push/pull: blockdev-add per <disk> */ + /* - push/pull: blockdev-add per <disk> + - incr: bitmap-add of tmp, bitmap-merge per <disk> */ for (i = 0; i < def->ndisks; i++) { virDomainBackupDiskDef *disk = &def->disks[i]; virJSONValuePtr file; @@ -17967,11 +18048,32 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, goto endmon; json = NULL; disk->state = VIR_DOMAIN_BACKUP_DISK_STATE_READY; + + if (def->incremental) { + if (!(arr = virJSONValueNewArray())) + goto endmon; + if (qemuMonitorAddBitmap(priv->mon, node, disk->name, false) < 0) { + virJSONValueFree(arr); + goto endmon; + } + disk->state = VIR_DOMAIN_BACKUP_DISK_STATE_BITMAP; + for (other = parent ? parent : current; other; + other = other->def->parent ? + virDomainCheckpointFindByName(vm->checkpoints, + other->def->parent) : NULL) { + if (virJSONValueArrayAppendString(arr, other->def->name) < 0) { + virJSONValueFree(arr); + goto endmon; + } + if (STREQ(other->def->name, def->incremental)) + break; + } + if (qemuMonitorMergeBitmaps(priv->mon, node, disk->name, &arr) < 0) + goto endmon; + } } - /* TODO: - - incr: bitmap-add of tmp, bitmap-merge per <disk> - - transaction, containing: + /* - transaction, containing: - push+full: blockdev-backup sync:full - push+incr: blockdev-backup sync:incremental bitmap:tmp - pull+full: blockdev-backup sync:none @@ -17980,24 +18082,61 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, */ if (!(json = virJSONValueNewArray())) goto endmon; + if (push) + mode = def->incremental ? "incremental" : "full"; + else + mode = "none"; for (i = 0; i < def->ndisks; i++) { virDomainBackupDiskDef *disk = &def->disks[i]; - virStorageSourcePtr src = vm->def->disks[disk->idx]->src; + const char *node; + const char *push_bitmap = NULL; if (!disk->store) continue; + node = qemuBlockNodeLookup(vm, disk->name); + if (push && def->incremental) + push_bitmap = disk->name; if (qemuMonitorJSONTransactionAdd(json, "blockdev-backup", - "s:device", src->nodeformat, + "s:device", node, "s:target", disk->store->nodeformat, - "s:sync", push ? "full" : "none", + "s:sync", mode, + "S:bitmap", push_bitmap, "s:job-id", disk->name, NULL) < 0) goto endmon; + if (def->incremental && !push && + qemuMonitorJSONTransactionAdd(json, + "block-dirty-bitmap-disable", + "s:node", node, + "s:name", disk->name, + NULL) < 0) + goto endmon; } + if (chk && qemuDomainCheckpointAddActions(vm, json, parent, + virDomainCheckpointObjGetDef(chk)) < 0) + goto endmon; if (qemuMonitorTransaction(priv->mon, &json) < 0) goto endmon; job_started = true; + if (chk) { + if (qemuDomainCheckpointWriteMetadata(vm, chk, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to save metadata for checkpoint %s"), + chk->def->name); + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + goto endmon; + } + virDomainCheckpointSetCurrent(vm->checkpoints, chk); + other = virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent); + chk->parent = other; + other->nchildren++; + chk->sibling = other->first_child; + other->first_child = chk; + } /* - pull: nbd-server-start with <server> from user (or autogenerate server) @@ -18040,7 +18179,7 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, if (job_started && qemuMonitorBlockJobCancel(priv->mon, disk->name) < 0) VIR_WARN("Unable to stop backup job %s on vm %s after failure", - disk->store->nodeformat, vm->def->name); + disk->name, vm->def->name); qemuDomainBackupDiskCleanup(driver, vm, disk, push, !!def->incremental, false); } @@ -18063,6 +18202,8 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml, qemuDomainObjEndJob(driver, vm); cleanup: + virJSONValueFree(arr); + virDomainCheckpointDefFree(chkdef); virCommandFree(cmd); VIR_FREE(suffix); virJSONValueFree(json); -- 2.20.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list