On Wed, Apr 17, 2019 at 09:09:21 -0500, Eric Blake wrote:
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 | 198 +++++++++++++++++++++++++++++++++++------
1 file changed, 170 insertions(+), 28 deletions(-)
Again, I don't quite understand the split here. It makes the new code
actually harder to review.
@@ -17695,19 +17713,44 @@
qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint,
static int
qemuDomainBackupPrepare(virQEMUDriverPtr driver, virDomainObjPtr vm,
- virDomainBackupDefPtr def)
+ virDomainBackupDefPtr def,
+ virDomainMomentObjPtr chk)
{
int ret = -1;
size_t i;
+ virDomainCheckpointDefPtr chkdef;
+ chkdef = chk ? virDomainCheckpointObjGetDef(chk) : NULL;
virDomainCheckpointDefPtr chkdef = NULL
if (chk)
chkdef = virDomainCheckpointObjGetDef(chk);
This is way more readable.
+ if (chk && def->ndisks != chkdef->ndisks) {
Riddle for the compiler? Why don't you actually check 'chkdef' instead of
'chk'?
+ 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 &&
see above
+ 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)
[...]
@@ -17779,28 +17822,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;
-
Looks like leftovers form previous patches?
if (!(vm = qemuDomObjFromDomain(domain)))
goto cleanup;
@@ -17827,6 +17864,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)) {
@@ -17864,15 +17912,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)))
@@ -17888,14 +17946,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;
@@ -17961,11 +18043,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;
Declare this inside the for loop with VIR_AUTOPTR to avoid all the
Frees below
+ 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,
Too many ternaries. The for loop is totaly unreadable.
+
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)
This monitor API should better document that it binary-ands all the
bimaps in &arr to 'target'. I had to look into the qemu docs.
+ 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
@@ -17974,24 +18077,61 @@ qemuDomainBackupBegin(virDomainPtr domain, const char
*diskXml,
*/
if (!(json = virJSONValueNewArray()))
goto endmon;
+ if (push)
+ mode = def->incremental ? "incremental" : "full";
You have one full condition, thus expanding the ternary will not hurt.
+ 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,
All the conditions here are quite confusing to read. I think we need to
split the function at least into two so that there's only one dimension
of conditionals depending on the requested operation.