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(a)redhat.com>
---
src/qemu/qemu_driver.c | 195 +++++++++++++++++++++++++++++++++++------
1 file changed, 167 insertions(+), 28 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 03506460a8..73c9db91b4 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -17238,6 +17238,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->name)) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("disk %s bitmap should match checkpoint name
%s"),
+ disk->name, def->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,
@@ -17996,19 +18014,42 @@ qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint,
static int
qemuDomainBackupPrepare(virQEMUDriverPtr driver, virDomainObjPtr vm,
- virDomainBackupDefPtr def)
+ virDomainBackupDefPtr def,
+ virDomainCheckpointObjPtr chk)
{
int ret = -1;
size_t i;
+ if (chk && def->ndisks != chk->def->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 &&
+ chk->def->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 &&
+ chk->def->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)
@@ -18042,7 +18083,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;
@@ -18080,28 +18121,21 @@ 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;
+ virDomainCheckpointObjPtr chk = NULL;
+ virDomainCheckpointObjPtr other = NULL;
+ virDomainCheckpointObjPtr parent = NULL;
+ 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;
@@ -18128,6 +18162,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->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)) {
@@ -18148,9 +18193,18 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml,
_("qemu binary lacks persistent bitmaps support"));
goto cleanup;
}
- virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
- _("cannot create incremental backups yet"));
- goto cleanup;
+ for (other = vm->current_checkpoint; 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)))
@@ -18166,14 +18220,40 @@ 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;
+ chk->def->current = true;
+ if (vm->current_checkpoint) {
+ parent = vm->current_checkpoint;
+ 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;
+ vm->current_checkpoint = NULL;
+ }
+ }
+
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;
@@ -18239,11 +18319,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 : vm->current_checkpoint; 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
@@ -18252,24 +18353,60 @@ 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, chk->def) <
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;
+ }
+ vm->current_checkpoint = 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)
@@ -18312,7 +18449,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);
}
@@ -18335,6 +18472,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