forked from rpms/libvirt
299 lines
13 KiB
Diff
299 lines
13 KiB
Diff
|
From 30377cd627a919e51cc4bb60a8a57e94e73f016c Mon Sep 17 00:00:00 2001
|
||
|
Message-Id: <30377cd627a919e51cc4bb60a8a57e94e73f016c@dist-git>
|
||
|
From: Peter Krempa <pkrempa@redhat.com>
|
||
|
Date: Tue, 4 Feb 2020 15:08:12 +0100
|
||
|
Subject: [PATCH] qemu: checkpoint: Introduce support for deleting checkpoints
|
||
|
accross snapshots
|
||
|
MIME-Version: 1.0
|
||
|
Content-Type: text/plain; charset=UTF-8
|
||
|
Content-Transfer-Encoding: 8bit
|
||
|
|
||
|
Allow deleting of checkpoints when snapshots were created along. The
|
||
|
code tracks and modifies the checkpoint list so that backups can still
|
||
|
be taken with such a backing chain. This unfortunately requires to
|
||
|
rename few bitmaps (by copying and deleting them) in some cases.
|
||
|
|
||
|
Signed-off-by: Peter Krempa <pkrempa@redhat.com>
|
||
|
Reviewed-by: Ján Tomko <jtomko@redhat.com>
|
||
|
(cherry picked from commit 30bc426071c0f5957af01181c063cd68ade97899)
|
||
|
|
||
|
https://bugzilla.redhat.com/show_bug.cgi?id=1207659
|
||
|
Message-Id: <e9f416811658b2ba03968ae5fca783b7fb7649f7.1580824112.git.pkrempa@redhat.com>
|
||
|
Reviewed-by: Ján Tomko <jtomko@redhat.com>
|
||
|
---
|
||
|
src/qemu/qemu_checkpoint.c | 112 ++++++++++++++++++++++++++++---------
|
||
|
src/qemu/qemu_checkpoint.h | 5 +-
|
||
|
tests/qemublocktest.c | 34 +++++++----
|
||
|
3 files changed, 111 insertions(+), 40 deletions(-)
|
||
|
|
||
|
diff --git a/src/qemu/qemu_checkpoint.c b/src/qemu/qemu_checkpoint.c
|
||
|
index e75cdd0458..55061bbf76 100644
|
||
|
--- a/src/qemu/qemu_checkpoint.c
|
||
|
+++ b/src/qemu/qemu_checkpoint.c
|
||
|
@@ -24,6 +24,7 @@
|
||
|
#include "qemu_capabilities.h"
|
||
|
#include "qemu_monitor.h"
|
||
|
#include "qemu_domain.h"
|
||
|
+#include "qemu_block.h"
|
||
|
|
||
|
#include "virerror.h"
|
||
|
#include "virlog.h"
|
||
|
@@ -150,39 +151,92 @@ qemuCheckpointFindActiveDiskInParent(virDomainObjPtr vm,
|
||
|
|
||
|
int
|
||
|
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
||
|
+ virHashTablePtr blockNamedNodeData,
|
||
|
const char *delbitmap,
|
||
|
const char *parentbitmap,
|
||
|
- bool chkcurrent,
|
||
|
- virJSONValuePtr actions)
|
||
|
+ virJSONValuePtr actions,
|
||
|
+ const char *diskdst)
|
||
|
{
|
||
|
- if (parentbitmap) {
|
||
|
- g_autoptr(virJSONValue) arr = NULL;
|
||
|
+ virStorageSourcePtr n = src;
|
||
|
|
||
|
- if (!(arr = virJSONValueNewArray()))
|
||
|
- return -1;
|
||
|
+ /* find the backing chain entry with bitmap named '@delbitmap' */
|
||
|
+ while (n) {
|
||
|
+ qemuBlockNamedNodeDataBitmapPtr tmp;
|
||
|
|
||
|
- if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(arr,
|
||
|
- src->nodeformat,
|
||
|
- delbitmap) < 0)
|
||
|
- return -1;
|
||
|
+ if ((tmp = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
||
|
+ n, delbitmap))) {
|
||
|
+ break;
|
||
|
+ }
|
||
|
|
||
|
- if (chkcurrent) {
|
||
|
- if (qemuMonitorTransactionBitmapEnable(actions,
|
||
|
- src->nodeformat,
|
||
|
- parentbitmap) < 0)
|
||
|
+ n = n->backingStore;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!n) {
|
||
|
+ virReportError(VIR_ERR_INTERNAL_ERROR,
|
||
|
+ _("bitmap '%s' not found in backing chain of '%s'"),
|
||
|
+ delbitmap, diskdst);
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+
|
||
|
+ while (n) {
|
||
|
+ qemuBlockNamedNodeDataBitmapPtr srcbitmap;
|
||
|
+
|
||
|
+ if (!(srcbitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
||
|
+ n, delbitmap)))
|
||
|
+ break;
|
||
|
+
|
||
|
+ /* For the actual checkpoint deletion we will merge any bitmap into the
|
||
|
+ * bitmap of the parent checkpoint (@parentbitmap) or for any image
|
||
|
+ * where the parent checkpoint bitmap is not present we must rename
|
||
|
+ * the bitmap of the deleted checkpoint into the bitmap of the parent
|
||
|
+ * checkpoint as qemu can't currently take the allocation map and turn
|
||
|
+ * it into a bitmap and thus we wouldn't be able to do a backup. */
|
||
|
+ if (parentbitmap) {
|
||
|
+ qemuBlockNamedNodeDataBitmapPtr dstbitmap;
|
||
|
+ g_autoptr(virJSONValue) arr = NULL;
|
||
|
+
|
||
|
+ dstbitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
||
|
+ n, parentbitmap);
|
||
|
+
|
||
|
+ if (dstbitmap) {
|
||
|
+ if (srcbitmap->recording && !dstbitmap->recording) {
|
||
|
+ if (qemuMonitorTransactionBitmapEnable(actions,
|
||
|
+ n->nodeformat,
|
||
|
+ dstbitmap->name) < 0)
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+
|
||
|
+ } else {
|
||
|
+ if (qemuMonitorTransactionBitmapAdd(actions,
|
||
|
+ n->nodeformat,
|
||
|
+ parentbitmap,
|
||
|
+ true,
|
||
|
+ !srcbitmap->recording,
|
||
|
+ srcbitmap->granularity) < 0)
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!(arr = virJSONValueNewArray()))
|
||
|
+ return -1;
|
||
|
+
|
||
|
+ if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(arr,
|
||
|
+ n->nodeformat,
|
||
|
+ srcbitmap->name) < 0)
|
||
|
+ return -1;
|
||
|
+
|
||
|
+ if (qemuMonitorTransactionBitmapMerge(actions,
|
||
|
+ n->nodeformat,
|
||
|
+ parentbitmap, &arr) < 0)
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
- if (qemuMonitorTransactionBitmapMerge(actions,
|
||
|
- src->nodeformat,
|
||
|
- parentbitmap, &arr) < 0)
|
||
|
+ if (qemuMonitorTransactionBitmapRemove(actions,
|
||
|
+ n->nodeformat,
|
||
|
+ srcbitmap->name) < 0)
|
||
|
return -1;
|
||
|
- }
|
||
|
|
||
|
- if (qemuMonitorTransactionBitmapRemove(actions,
|
||
|
- src->nodeformat,
|
||
|
- delbitmap) < 0)
|
||
|
- return -1;
|
||
|
+ n = n->backingStore;
|
||
|
+ }
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
@@ -191,11 +245,11 @@ qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
||
|
static int
|
||
|
qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
||
|
virDomainCheckpointDefPtr chkdef,
|
||
|
- bool chkcurrent,
|
||
|
virDomainMomentObjPtr parent)
|
||
|
{
|
||
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
||
|
virQEMUDriverPtr driver = priv->driver;
|
||
|
+ g_autoptr(virHashTable) blockNamedNodeData = NULL;
|
||
|
int rc;
|
||
|
g_autoptr(virJSONValue) actions = NULL;
|
||
|
size_t i;
|
||
|
@@ -203,6 +257,11 @@ qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
||
|
if (!(actions = virJSONValueNewArray()))
|
||
|
return -1;
|
||
|
|
||
|
+ qemuDomainObjEnterMonitor(driver, vm);
|
||
|
+ blockNamedNodeData = qemuMonitorBlockGetNamedNodeData(priv->mon);
|
||
|
+ if (qemuDomainObjExitMonitor(priv->driver, vm) < 0 || !blockNamedNodeData)
|
||
|
+ return -1;
|
||
|
+
|
||
|
for (i = 0; i < chkdef->ndisks; i++) {
|
||
|
virDomainCheckpointDiskDef *chkdisk = &chkdef->disks[i];
|
||
|
virDomainDiskDefPtr domdisk = virDomainDiskByTarget(vm->def, chkdisk->name);
|
||
|
@@ -223,8 +282,9 @@ qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
||
|
chkdisk->name)))
|
||
|
parentbitmap = parentchkdisk->name;
|
||
|
|
||
|
- if (qemuCheckpointDiscardDiskBitmaps(domdisk->src, chkdisk->bitmap,
|
||
|
- parentbitmap, chkcurrent, actions) < 0)
|
||
|
+ if (qemuCheckpointDiscardDiskBitmaps(domdisk->src, blockNamedNodeData,
|
||
|
+ chkdisk->bitmap, parentbitmap,
|
||
|
+ actions, domdisk->dst) < 0)
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
@@ -262,7 +322,7 @@ qemuCheckpointDiscard(virQEMUDriverPtr driver,
|
||
|
virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk);
|
||
|
parent = virDomainCheckpointFindByName(vm->checkpoints,
|
||
|
chk->def->parent_name);
|
||
|
- if (qemuCheckpointDiscardBitmaps(vm, chkdef, chkcurrent, parent) < 0)
|
||
|
+ if (qemuCheckpointDiscardBitmaps(vm, chkdef, parent) < 0)
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
diff --git a/src/qemu/qemu_checkpoint.h b/src/qemu/qemu_checkpoint.h
|
||
|
index 85fd453d50..976b1eed0f 100644
|
||
|
--- a/src/qemu/qemu_checkpoint.h
|
||
|
+++ b/src/qemu/qemu_checkpoint.h
|
||
|
@@ -74,7 +74,8 @@ qemuCheckpointRollbackMetadata(virDomainObjPtr vm,
|
||
|
|
||
|
int
|
||
|
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
||
|
+ virHashTablePtr blockNamedNodeData,
|
||
|
const char *delbitmap,
|
||
|
const char *parentbitmap,
|
||
|
- bool chkcurrent,
|
||
|
- virJSONValuePtr actions);
|
||
|
+ virJSONValuePtr actions,
|
||
|
+ const char *diskdst);
|
||
|
diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c
|
||
|
index edaf82053d..e56f813424 100644
|
||
|
--- a/tests/qemublocktest.c
|
||
|
+++ b/tests/qemublocktest.c
|
||
|
@@ -704,6 +704,7 @@ struct testQemuCheckpointDeleteMergeData {
|
||
|
virStorageSourcePtr chain;
|
||
|
const char *deletebitmap;
|
||
|
const char *parentbitmap;
|
||
|
+ const char *nodedatafile;
|
||
|
};
|
||
|
|
||
|
|
||
|
@@ -714,22 +715,30 @@ testQemuCheckpointDeleteMerge(const void *opaque)
|
||
|
g_autofree char *actual = NULL;
|
||
|
g_autofree char *expectpath = NULL;
|
||
|
g_autoptr(virJSONValue) actions = NULL;
|
||
|
- bool currentcheckpoint;
|
||
|
+ g_autoptr(virJSONValue) nodedatajson = NULL;
|
||
|
+ g_autoptr(virHashTable) nodedata = NULL;
|
||
|
|
||
|
expectpath = g_strdup_printf("%s/%s%s-out.json", abs_srcdir,
|
||
|
checkpointDeletePrefix, data->name);
|
||
|
|
||
|
+ if (!(nodedatajson = virTestLoadFileJSON(bitmapDetectPrefix, data->nodedatafile,
|
||
|
+ ".json", NULL)))
|
||
|
+ return -1;
|
||
|
+
|
||
|
+ if (!(nodedata = qemuMonitorJSONBlockGetNamedNodeDataJSON(nodedatajson))) {
|
||
|
+ VIR_TEST_VERBOSE("failed to load nodedata JSON\n");
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
+
|
||
|
if (!(actions = virJSONValueNewArray()))
|
||
|
return -1;
|
||
|
|
||
|
- /* hack to get the 'current' state until the function stops accepting it */
|
||
|
- currentcheckpoint = STREQ("current", data->deletebitmap);
|
||
|
-
|
||
|
if (qemuCheckpointDiscardDiskBitmaps(data->chain,
|
||
|
+ nodedata,
|
||
|
data->deletebitmap,
|
||
|
data->parentbitmap,
|
||
|
- currentcheckpoint,
|
||
|
- actions) < 0) {
|
||
|
+ actions,
|
||
|
+ "testdisk") < 0) {
|
||
|
VIR_TEST_VERBOSE("failed to generate checkpoint delete transaction\n");
|
||
|
return -1;
|
||
|
}
|
||
|
@@ -988,22 +997,23 @@ mymain(void)
|
||
|
TEST_BACKUP_BITMAP_CALCULATE("snapshot-intermediate", bitmapSourceChain, "d", "snapshots");
|
||
|
TEST_BACKUP_BITMAP_CALCULATE("snapshot-deep", bitmapSourceChain, "a", "snapshots");
|
||
|
|
||
|
-#define TEST_CHECKPOINT_DELETE_MERGE(testname, delbmp, parbmp) \
|
||
|
+#define TEST_CHECKPOINT_DELETE_MERGE(testname, delbmp, parbmp, named) \
|
||
|
do { \
|
||
|
checkpointdeletedata.name = testname; \
|
||
|
checkpointdeletedata.chain = bitmapSourceChain; \
|
||
|
checkpointdeletedata.deletebitmap = delbmp; \
|
||
|
checkpointdeletedata.parentbitmap = parbmp; \
|
||
|
+ checkpointdeletedata.nodedatafile = named; \
|
||
|
if (virTestRun("checkpoint delete " testname, \
|
||
|
testQemuCheckpointDeleteMerge, &checkpointdeletedata) < 0) \
|
||
|
ret = -1; \
|
||
|
} while (0)
|
||
|
|
||
|
- TEST_CHECKPOINT_DELETE_MERGE("basic-noparent", "a", NULL);
|
||
|
- TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate1", "b", "a");
|
||
|
- TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate2", "c", "b");
|
||
|
- TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate3", "d", "c");
|
||
|
- TEST_CHECKPOINT_DELETE_MERGE("basic-current", "current", "d");
|
||
|
+ TEST_CHECKPOINT_DELETE_MERGE("basic-noparent", "a", NULL, "basic");
|
||
|
+ TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate1", "b", "a", "basic");
|
||
|
+ TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate2", "c", "b", "basic");
|
||
|
+ TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate3", "d", "c", "basic");
|
||
|
+ TEST_CHECKPOINT_DELETE_MERGE("basic-current", "current", "d", "basic");
|
||
|
|
||
|
cleanup:
|
||
|
virHashFree(diskxmljsondata.schema);
|
||
|
--
|
||
|
2.25.0
|
||
|
|