qemu: checkpoint: Introduce support for deleting checkpoints accross snapshots

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>
This commit is contained in:
Peter Krempa 2020-01-09 14:19:07 +01:00
parent d7d97e87af
commit 30bc426071
3 changed files with 111 additions and 40 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -708,6 +708,7 @@ struct testQemuCheckpointDeleteMergeData {
virStorageSourcePtr chain;
const char *deletebitmap;
const char *parentbitmap;
const char *nodedatafile;
};
@ -718,22 +719,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;
}
@ -992,22 +1001,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);