[libvirt] [PATCH v4 20/20] backup: implement qemu incremental pull backup

Eric Blake eblake at redhat.com
Wed Feb 6 19:18:18 UTC 2019


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 at redhat.com>
---
 src/qemu/qemu_driver.c | 189 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 159 insertions(+), 30 deletions(-)

diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 267b6e5c4d..0564bab52f 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -17077,6 +17077,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,
@@ -17773,19 +17791,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)
@@ -17820,7 +17861,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;
@@ -17861,23 +17902,15 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml,
     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;

@@ -17904,6 +17937,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;
+    }
+
     if (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL) {
         if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NBD_BITMAP)) {
             virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
@@ -17927,9 +17971,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)))
@@ -17945,14 +17998,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;
@@ -18010,11 +18089,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
@@ -18025,22 +18125,50 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml,
         goto endmon;
     for (i = 0; i < def->ndisks; i++) {
         virDomainBackupDiskDef *disk = &def->disks[i];
-        virStorageSourcePtr src = vm->def->disks[disk->idx]->src;
+        const char *node;

         if (!disk->store)
             continue;
+        node = qemuBlockNodeLookup(vm, disk->name);
         if (qemuMonitorJSONTransactionAdd(json,
                                           "blockdev-backup",
-                                          "s:device", src->nodeformat,
+                                          "s:device", node,
                                           "s:target", disk->store->nodeformat,
                                           "s:sync", "none",
-                                          "s:job-id", disk->store->nodeformat,
+                                          "s:job-id", disk->name,
+                                          NULL) < 0)
+            goto endmon;
+        if (def->incremental && def->type == VIR_DOMAIN_BACKUP_TYPE_PULL &&
+            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)
@@ -18079,10 +18207,9 @@ qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml,
             virDomainBackupDiskDef *disk = &def->disks[i];

             if (job_started &&
-                qemuMonitorBlockJobCancel(priv->mon,
-                                          disk->store->nodeformat) < 0)
+                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, !!def->incremental);
         }
         virSetError(save_err);
@@ -18104,6 +18231,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




More information about the libvir-list mailing list