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