mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-01 17:35:17 +00:00
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:
parent
d7d97e87af
commit
30bc426071
@ -24,6 +24,7 @@
|
|||||||
#include "qemu_capabilities.h"
|
#include "qemu_capabilities.h"
|
||||||
#include "qemu_monitor.h"
|
#include "qemu_monitor.h"
|
||||||
#include "qemu_domain.h"
|
#include "qemu_domain.h"
|
||||||
|
#include "qemu_block.h"
|
||||||
|
|
||||||
#include "virerror.h"
|
#include "virerror.h"
|
||||||
#include "virlog.h"
|
#include "virlog.h"
|
||||||
@ -150,39 +151,92 @@ qemuCheckpointFindActiveDiskInParent(virDomainObjPtr vm,
|
|||||||
|
|
||||||
int
|
int
|
||||||
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
||||||
|
virHashTablePtr blockNamedNodeData,
|
||||||
const char *delbitmap,
|
const char *delbitmap,
|
||||||
const char *parentbitmap,
|
const char *parentbitmap,
|
||||||
bool chkcurrent,
|
virJSONValuePtr actions,
|
||||||
virJSONValuePtr actions)
|
const char *diskdst)
|
||||||
{
|
{
|
||||||
if (parentbitmap) {
|
virStorageSourcePtr n = src;
|
||||||
g_autoptr(virJSONValue) arr = NULL;
|
|
||||||
|
|
||||||
if (!(arr = virJSONValueNewArray()))
|
/* find the backing chain entry with bitmap named '@delbitmap' */
|
||||||
return -1;
|
while (n) {
|
||||||
|
qemuBlockNamedNodeDataBitmapPtr tmp;
|
||||||
|
|
||||||
if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(arr,
|
if ((tmp = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
||||||
src->nodeformat,
|
n, delbitmap))) {
|
||||||
delbitmap) < 0)
|
break;
|
||||||
return -1;
|
}
|
||||||
|
|
||||||
if (chkcurrent) {
|
n = n->backingStore;
|
||||||
if (qemuMonitorTransactionBitmapEnable(actions,
|
}
|
||||||
src->nodeformat,
|
|
||||||
parentbitmap) < 0)
|
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qemuMonitorTransactionBitmapMerge(actions,
|
if (qemuMonitorTransactionBitmapRemove(actions,
|
||||||
src->nodeformat,
|
n->nodeformat,
|
||||||
parentbitmap, &arr) < 0)
|
srcbitmap->name) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
if (qemuMonitorTransactionBitmapRemove(actions,
|
n = n->backingStore;
|
||||||
src->nodeformat,
|
}
|
||||||
delbitmap) < 0)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -191,11 +245,11 @@ qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
|||||||
static int
|
static int
|
||||||
qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
||||||
virDomainCheckpointDefPtr chkdef,
|
virDomainCheckpointDefPtr chkdef,
|
||||||
bool chkcurrent,
|
|
||||||
virDomainMomentObjPtr parent)
|
virDomainMomentObjPtr parent)
|
||||||
{
|
{
|
||||||
qemuDomainObjPrivatePtr priv = vm->privateData;
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
||||||
virQEMUDriverPtr driver = priv->driver;
|
virQEMUDriverPtr driver = priv->driver;
|
||||||
|
g_autoptr(virHashTable) blockNamedNodeData = NULL;
|
||||||
int rc;
|
int rc;
|
||||||
g_autoptr(virJSONValue) actions = NULL;
|
g_autoptr(virJSONValue) actions = NULL;
|
||||||
size_t i;
|
size_t i;
|
||||||
@ -203,6 +257,11 @@ qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
|||||||
if (!(actions = virJSONValueNewArray()))
|
if (!(actions = virJSONValueNewArray()))
|
||||||
return -1;
|
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++) {
|
for (i = 0; i < chkdef->ndisks; i++) {
|
||||||
virDomainCheckpointDiskDef *chkdisk = &chkdef->disks[i];
|
virDomainCheckpointDiskDef *chkdisk = &chkdef->disks[i];
|
||||||
virDomainDiskDefPtr domdisk = virDomainDiskByTarget(vm->def, chkdisk->name);
|
virDomainDiskDefPtr domdisk = virDomainDiskByTarget(vm->def, chkdisk->name);
|
||||||
@ -223,8 +282,9 @@ qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
|||||||
chkdisk->name)))
|
chkdisk->name)))
|
||||||
parentbitmap = parentchkdisk->name;
|
parentbitmap = parentchkdisk->name;
|
||||||
|
|
||||||
if (qemuCheckpointDiscardDiskBitmaps(domdisk->src, chkdisk->bitmap,
|
if (qemuCheckpointDiscardDiskBitmaps(domdisk->src, blockNamedNodeData,
|
||||||
parentbitmap, chkcurrent, actions) < 0)
|
chkdisk->bitmap, parentbitmap,
|
||||||
|
actions, domdisk->dst) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +322,7 @@ qemuCheckpointDiscard(virQEMUDriverPtr driver,
|
|||||||
virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk);
|
virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk);
|
||||||
parent = virDomainCheckpointFindByName(vm->checkpoints,
|
parent = virDomainCheckpointFindByName(vm->checkpoints,
|
||||||
chk->def->parent_name);
|
chk->def->parent_name);
|
||||||
if (qemuCheckpointDiscardBitmaps(vm, chkdef, chkcurrent, parent) < 0)
|
if (qemuCheckpointDiscardBitmaps(vm, chkdef, parent) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +74,8 @@ qemuCheckpointRollbackMetadata(virDomainObjPtr vm,
|
|||||||
|
|
||||||
int
|
int
|
||||||
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
||||||
|
virHashTablePtr blockNamedNodeData,
|
||||||
const char *delbitmap,
|
const char *delbitmap,
|
||||||
const char *parentbitmap,
|
const char *parentbitmap,
|
||||||
bool chkcurrent,
|
virJSONValuePtr actions,
|
||||||
virJSONValuePtr actions);
|
const char *diskdst);
|
||||||
|
@ -708,6 +708,7 @@ struct testQemuCheckpointDeleteMergeData {
|
|||||||
virStorageSourcePtr chain;
|
virStorageSourcePtr chain;
|
||||||
const char *deletebitmap;
|
const char *deletebitmap;
|
||||||
const char *parentbitmap;
|
const char *parentbitmap;
|
||||||
|
const char *nodedatafile;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -718,22 +719,30 @@ testQemuCheckpointDeleteMerge(const void *opaque)
|
|||||||
g_autofree char *actual = NULL;
|
g_autofree char *actual = NULL;
|
||||||
g_autofree char *expectpath = NULL;
|
g_autofree char *expectpath = NULL;
|
||||||
g_autoptr(virJSONValue) actions = 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,
|
expectpath = g_strdup_printf("%s/%s%s-out.json", abs_srcdir,
|
||||||
checkpointDeletePrefix, data->name);
|
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()))
|
if (!(actions = virJSONValueNewArray()))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* hack to get the 'current' state until the function stops accepting it */
|
|
||||||
currentcheckpoint = STREQ("current", data->deletebitmap);
|
|
||||||
|
|
||||||
if (qemuCheckpointDiscardDiskBitmaps(data->chain,
|
if (qemuCheckpointDiscardDiskBitmaps(data->chain,
|
||||||
|
nodedata,
|
||||||
data->deletebitmap,
|
data->deletebitmap,
|
||||||
data->parentbitmap,
|
data->parentbitmap,
|
||||||
currentcheckpoint,
|
actions,
|
||||||
actions) < 0) {
|
"testdisk") < 0) {
|
||||||
VIR_TEST_VERBOSE("failed to generate checkpoint delete transaction\n");
|
VIR_TEST_VERBOSE("failed to generate checkpoint delete transaction\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -992,22 +1001,23 @@ mymain(void)
|
|||||||
TEST_BACKUP_BITMAP_CALCULATE("snapshot-intermediate", bitmapSourceChain, "d", "snapshots");
|
TEST_BACKUP_BITMAP_CALCULATE("snapshot-intermediate", bitmapSourceChain, "d", "snapshots");
|
||||||
TEST_BACKUP_BITMAP_CALCULATE("snapshot-deep", bitmapSourceChain, "a", "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 { \
|
do { \
|
||||||
checkpointdeletedata.name = testname; \
|
checkpointdeletedata.name = testname; \
|
||||||
checkpointdeletedata.chain = bitmapSourceChain; \
|
checkpointdeletedata.chain = bitmapSourceChain; \
|
||||||
checkpointdeletedata.deletebitmap = delbmp; \
|
checkpointdeletedata.deletebitmap = delbmp; \
|
||||||
checkpointdeletedata.parentbitmap = parbmp; \
|
checkpointdeletedata.parentbitmap = parbmp; \
|
||||||
|
checkpointdeletedata.nodedatafile = named; \
|
||||||
if (virTestRun("checkpoint delete " testname, \
|
if (virTestRun("checkpoint delete " testname, \
|
||||||
testQemuCheckpointDeleteMerge, &checkpointdeletedata) < 0) \
|
testQemuCheckpointDeleteMerge, &checkpointdeletedata) < 0) \
|
||||||
ret = -1; \
|
ret = -1; \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
TEST_CHECKPOINT_DELETE_MERGE("basic-noparent", "a", NULL);
|
TEST_CHECKPOINT_DELETE_MERGE("basic-noparent", "a", NULL, "basic");
|
||||||
TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate1", "b", "a");
|
TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate1", "b", "a", "basic");
|
||||||
TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate2", "c", "b");
|
TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate2", "c", "b", "basic");
|
||||||
TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate3", "d", "c");
|
TEST_CHECKPOINT_DELETE_MERGE("basic-intermediate3", "d", "c", "basic");
|
||||||
TEST_CHECKPOINT_DELETE_MERGE("basic-current", "current", "d");
|
TEST_CHECKPOINT_DELETE_MERGE("basic-current", "current", "d", "basic");
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
virHashFree(diskxmljsondata.schema);
|
virHashFree(diskxmljsondata.schema);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user