2019-09-19 14:49:21 +00:00
|
|
|
/*
|
|
|
|
* qemu_checkpoint.c: checkpoint related implementation
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library. If not, see
|
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
|
|
|
#include "qemu_checkpoint.h"
|
|
|
|
#include "qemu_capabilities.h"
|
|
|
|
#include "qemu_monitor.h"
|
|
|
|
#include "qemu_domain.h"
|
2020-01-09 13:19:07 +00:00
|
|
|
#include "qemu_block.h"
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
#include "virerror.h"
|
|
|
|
#include "virlog.h"
|
|
|
|
#include "datatypes.h"
|
|
|
|
#include "viralloc.h"
|
|
|
|
#include "domain_conf.h"
|
|
|
|
#include "libvirt_internal.h"
|
|
|
|
#include "virxml.h"
|
|
|
|
#include "virstring.h"
|
|
|
|
#include "virdomaincheckpointobjlist.h"
|
|
|
|
#include "virdomainsnapshotobjlist.h"
|
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
|
|
|
|
VIR_LOG_INIT("qemu.qemu_checkpoint");
|
|
|
|
|
2020-11-23 11:51:23 +00:00
|
|
|
/**
|
|
|
|
* qemuCheckpointSetCurrent: Set currently active checkpoint
|
|
|
|
*
|
|
|
|
* @vm: domain object
|
|
|
|
* @newcurrent: checkpoint object to set as current/active
|
|
|
|
*
|
|
|
|
* Sets @newcurrent as the 'current' checkpoint of @vm. This helper ensures that
|
|
|
|
* the checkpoint which was 'current' previously is updated.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
qemuCheckpointSetCurrent(virDomainObjPtr vm,
|
|
|
|
virDomainMomentObjPtr newcurrent)
|
|
|
|
{
|
|
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
virQEMUDriverPtr driver = priv->driver;
|
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
|
|
|
virDomainMomentObjPtr oldcurrent = virDomainCheckpointGetCurrent(vm->checkpoints);
|
|
|
|
|
|
|
|
virDomainCheckpointSetCurrent(vm->checkpoints, newcurrent);
|
|
|
|
|
|
|
|
/* we need to write out metadata for the old checkpoint to update the
|
|
|
|
* 'active' property */
|
|
|
|
if (oldcurrent &&
|
|
|
|
oldcurrent != newcurrent) {
|
|
|
|
if (qemuCheckpointWriteMetadata(vm, oldcurrent, driver->xmlopt, cfg->checkpointDir) < 0)
|
|
|
|
VIR_WARN("failed to update old current checkpoint");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-19 14:49:21 +00:00
|
|
|
/* Looks up the domain object from checkpoint and unlocks the
|
|
|
|
* driver. The returned domain object is locked and ref'd and the
|
|
|
|
* caller must call virDomainObjEndAPI() on it. */
|
|
|
|
virDomainObjPtr
|
|
|
|
qemuDomObjFromCheckpoint(virDomainCheckpointPtr checkpoint)
|
|
|
|
{
|
|
|
|
return qemuDomainObjFromDomain(checkpoint->domain);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Looks up checkpoint object from VM and name */
|
|
|
|
virDomainMomentObjPtr
|
|
|
|
qemuCheckpointObjFromName(virDomainObjPtr vm,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr chk = NULL;
|
|
|
|
chk = virDomainCheckpointFindByName(vm->checkpoints, name);
|
|
|
|
if (!chk)
|
|
|
|
virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT,
|
|
|
|
_("no domain checkpoint with matching name '%s'"),
|
|
|
|
name);
|
|
|
|
|
|
|
|
return chk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Looks up checkpoint object from VM and checkpointPtr */
|
|
|
|
virDomainMomentObjPtr
|
|
|
|
qemuCheckpointObjFromCheckpoint(virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointPtr checkpoint)
|
|
|
|
{
|
|
|
|
return qemuCheckpointObjFromName(vm, checkpoint->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-03 06:51:19 +00:00
|
|
|
int
|
2019-09-20 11:47:04 +00:00
|
|
|
qemuCheckpointWriteMetadata(virDomainObjPtr vm,
|
|
|
|
virDomainMomentObjPtr checkpoint,
|
|
|
|
virDomainXMLOptionPtr xmlopt,
|
|
|
|
const char *checkpointDir)
|
|
|
|
{
|
|
|
|
unsigned int flags = VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE;
|
|
|
|
virDomainCheckpointDefPtr def = virDomainCheckpointObjGetDef(checkpoint);
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *newxml = NULL;
|
|
|
|
g_autofree char *chkDir = NULL;
|
|
|
|
g_autofree char *chkFile = NULL;
|
2019-09-20 11:47:04 +00:00
|
|
|
|
2019-11-27 13:10:21 +00:00
|
|
|
newxml = virDomainCheckpointDefFormat(def, xmlopt, flags);
|
2019-09-20 11:47:04 +00:00
|
|
|
if (newxml == NULL)
|
|
|
|
return -1;
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
chkDir = g_strdup_printf("%s/%s", checkpointDir, vm->def->name);
|
2019-09-20 11:47:04 +00:00
|
|
|
if (virFileMakePath(chkDir) < 0) {
|
|
|
|
virReportSystemError(errno, _("cannot create checkpoint directory '%s'"),
|
|
|
|
chkDir);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
chkFile = g_strdup_printf("%s/%s.xml", chkDir, def->parent.name);
|
2019-09-20 11:47:04 +00:00
|
|
|
|
|
|
|
return virXMLSaveFile(chkFile, NULL, "checkpoint-edit", newxml);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-08 09:25:33 +00:00
|
|
|
int
|
|
|
|
qemuCheckpointDiscardDiskBitmaps(virStorageSourcePtr src,
|
2020-10-22 17:04:18 +00:00
|
|
|
GHashTable *blockNamedNodeData,
|
2020-01-08 09:25:33 +00:00
|
|
|
const char *delbitmap,
|
2020-01-09 13:19:07 +00:00
|
|
|
virJSONValuePtr actions,
|
2020-01-09 16:34:29 +00:00
|
|
|
const char *diskdst,
|
|
|
|
GSList **reopenimages)
|
2020-01-08 09:25:33 +00:00
|
|
|
{
|
2020-04-21 06:17:58 +00:00
|
|
|
virStorageSourcePtr n;
|
|
|
|
bool found = false;
|
2020-01-08 09:25:33 +00:00
|
|
|
|
2020-01-09 13:19:07 +00:00
|
|
|
/* find the backing chain entry with bitmap named '@delbitmap' */
|
2020-04-21 06:17:58 +00:00
|
|
|
for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
|
|
qemuBlockNamedNodeDataBitmapPtr bitmapdata;
|
2020-01-09 13:19:07 +00:00
|
|
|
|
2020-04-21 06:17:58 +00:00
|
|
|
if (!(bitmapdata = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData,
|
|
|
|
n, delbitmap)))
|
|
|
|
continue;
|
2020-01-09 13:19:07 +00:00
|
|
|
|
2020-04-21 06:17:58 +00:00
|
|
|
found = true;
|
2020-01-08 09:25:33 +00:00
|
|
|
|
2020-01-09 13:19:07 +00:00
|
|
|
if (qemuMonitorTransactionBitmapRemove(actions,
|
|
|
|
n->nodeformat,
|
2020-04-21 06:17:58 +00:00
|
|
|
bitmapdata->name) < 0)
|
2020-01-08 09:25:33 +00:00
|
|
|
return -1;
|
|
|
|
|
2020-01-09 16:34:29 +00:00
|
|
|
if (n != src)
|
|
|
|
*reopenimages = g_slist_prepend(*reopenimages, n);
|
2020-04-21 06:17:58 +00:00
|
|
|
}
|
2020-01-09 16:34:29 +00:00
|
|
|
|
2020-04-21 06:17:58 +00:00
|
|
|
if (!found) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("bitmap '%s' not found in backing chain of '%s'"),
|
|
|
|
delbitmap, diskdst);
|
|
|
|
return -1;
|
2020-01-09 13:19:07 +00:00
|
|
|
}
|
2020-01-08 09:25:33 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-07 14:15:38 +00:00
|
|
|
static int
|
|
|
|
qemuCheckpointDiscardBitmaps(virDomainObjPtr vm,
|
2020-04-21 06:17:58 +00:00
|
|
|
virDomainCheckpointDefPtr chkdef)
|
2020-01-07 14:15:38 +00:00
|
|
|
{
|
|
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
virQEMUDriverPtr driver = priv->driver;
|
2020-10-22 17:04:18 +00:00
|
|
|
g_autoptr(GHashTable) blockNamedNodeData = NULL;
|
2020-01-09 16:34:29 +00:00
|
|
|
int rc = -1;
|
2020-01-07 14:15:38 +00:00
|
|
|
g_autoptr(virJSONValue) actions = NULL;
|
|
|
|
size_t i;
|
2020-01-09 16:34:29 +00:00
|
|
|
g_autoptr(GSList) reopenimages = NULL;
|
|
|
|
g_autoptr(GSList) relabelimages = NULL;
|
|
|
|
GSList *next;
|
2020-01-07 14:15:38 +00:00
|
|
|
|
2020-01-31 07:18:36 +00:00
|
|
|
actions = virJSONValueNewArray();
|
2020-01-07 14:15:38 +00:00
|
|
|
|
2020-02-26 11:50:53 +00:00
|
|
|
if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_JOB_NONE)))
|
2020-01-09 13:19:07 +00:00
|
|
|
return -1;
|
|
|
|
|
2020-01-07 14:15:38 +00:00
|
|
|
for (i = 0; i < chkdef->ndisks; i++) {
|
2020-01-08 06:55:18 +00:00
|
|
|
virDomainCheckpointDiskDef *chkdisk = &chkdef->disks[i];
|
2020-01-08 07:18:51 +00:00
|
|
|
virDomainDiskDefPtr domdisk = virDomainDiskByTarget(vm->def, chkdisk->name);
|
|
|
|
|
|
|
|
/* domdisk can be missing e.g. when it was unplugged */
|
|
|
|
if (!domdisk)
|
|
|
|
continue;
|
2020-01-07 14:15:38 +00:00
|
|
|
|
2020-01-08 06:55:18 +00:00
|
|
|
if (chkdisk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
|
2020-01-07 14:15:38 +00:00
|
|
|
continue;
|
|
|
|
|
2020-01-09 13:19:07 +00:00
|
|
|
if (qemuCheckpointDiscardDiskBitmaps(domdisk->src, blockNamedNodeData,
|
2020-04-21 06:17:58 +00:00
|
|
|
chkdisk->bitmap,
|
2020-01-09 16:34:29 +00:00
|
|
|
actions, domdisk->dst,
|
|
|
|
&reopenimages) < 0)
|
2020-01-07 14:15:38 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-01-09 16:34:29 +00:00
|
|
|
/* label any non-top images for read-write access */
|
|
|
|
for (next = reopenimages; next; next = next->next) {
|
|
|
|
virStorageSourcePtr src = next->data;
|
|
|
|
|
2020-02-27 10:20:51 +00:00
|
|
|
if (qemuDomainStorageSourceAccessAllow(driver, vm, src,
|
|
|
|
false, false, false) < 0)
|
2020-01-09 16:34:29 +00:00
|
|
|
goto relabel;
|
|
|
|
|
2020-02-13 11:49:14 +00:00
|
|
|
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV_REOPEN) &&
|
|
|
|
qemuBlockReopenReadWrite(vm, src, QEMU_ASYNC_JOB_NONE) < 0)
|
|
|
|
goto relabel;
|
|
|
|
|
2020-01-09 16:34:29 +00:00
|
|
|
relabelimages = g_slist_prepend(relabelimages, src);
|
|
|
|
}
|
|
|
|
|
2020-01-07 14:15:38 +00:00
|
|
|
qemuDomainObjEnterMonitor(driver, vm);
|
|
|
|
rc = qemuMonitorTransaction(priv->mon, &actions);
|
2020-01-09 16:34:29 +00:00
|
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0)
|
2020-01-07 14:15:38 +00:00
|
|
|
return -1;
|
|
|
|
|
2020-01-09 16:34:29 +00:00
|
|
|
relabel:
|
|
|
|
for (next = relabelimages; next; next = next->next) {
|
|
|
|
virStorageSourcePtr src = next->data;
|
|
|
|
|
2020-02-13 11:49:14 +00:00
|
|
|
if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKDEV_REOPEN))
|
|
|
|
ignore_value(qemuBlockReopenReadOnly(vm, src, QEMU_ASYNC_JOB_NONE));
|
|
|
|
|
2020-02-27 10:20:51 +00:00
|
|
|
ignore_value(qemuDomainStorageSourceAccessAllow(driver, vm, src,
|
|
|
|
true, false, false));
|
2020-01-09 16:34:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
2020-01-07 14:15:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-20 11:47:04 +00:00
|
|
|
static int
|
|
|
|
qemuCheckpointDiscard(virQEMUDriverPtr driver,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
virDomainMomentObjPtr chk,
|
|
|
|
bool update_parent,
|
|
|
|
bool metadata_only)
|
|
|
|
{
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
2019-10-15 13:16:31 +00:00
|
|
|
g_autofree char *chkFile = NULL;
|
2020-01-07 14:01:41 +00:00
|
|
|
bool chkcurrent = chk == virDomainCheckpointGetCurrent(vm->checkpoints);
|
2019-09-20 11:47:04 +00:00
|
|
|
|
|
|
|
if (!metadata_only && !virDomainObjIsActive(vm)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("cannot remove checkpoint from inactive domain"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-22 13:26:14 +00:00
|
|
|
chkFile = g_strdup_printf("%s/%s/%s.xml", cfg->checkpointDir, vm->def->name,
|
|
|
|
chk->def->name);
|
2019-09-20 11:47:04 +00:00
|
|
|
|
|
|
|
if (!metadata_only) {
|
|
|
|
virDomainCheckpointDefPtr chkdef = virDomainCheckpointObjGetDef(chk);
|
2020-04-21 06:17:58 +00:00
|
|
|
if (qemuCheckpointDiscardBitmaps(vm, chkdef) < 0)
|
2019-09-20 11:47:04 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-01-07 14:01:41 +00:00
|
|
|
if (chkcurrent) {
|
2020-04-21 06:17:58 +00:00
|
|
|
virDomainMomentObjPtr parent = NULL;
|
|
|
|
|
2019-09-20 11:47:04 +00:00
|
|
|
virDomainCheckpointSetCurrent(vm->checkpoints, NULL);
|
2020-04-21 06:17:58 +00:00
|
|
|
parent = virDomainCheckpointFindByName(vm->checkpoints,
|
|
|
|
chk->def->parent_name);
|
|
|
|
|
2019-09-20 11:47:04 +00:00
|
|
|
if (update_parent && parent) {
|
|
|
|
virDomainCheckpointSetCurrent(vm->checkpoints, parent);
|
2019-11-27 13:19:09 +00:00
|
|
|
if (qemuCheckpointWriteMetadata(vm, parent,
|
2019-09-20 11:47:04 +00:00
|
|
|
driver->xmlopt,
|
|
|
|
cfg->checkpointDir) < 0) {
|
|
|
|
VIR_WARN("failed to set parent checkpoint '%s' as current",
|
|
|
|
chk->def->parent_name);
|
|
|
|
virDomainCheckpointSetCurrent(vm->checkpoints, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unlink(chkFile) < 0)
|
|
|
|
VIR_WARN("Failed to unlink %s", chkFile);
|
|
|
|
if (update_parent)
|
|
|
|
virDomainMomentDropParent(chk);
|
|
|
|
virDomainCheckpointObjListRemove(vm->checkpoints, chk);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuCheckpointDiscardAllMetadata(virQEMUDriverPtr driver,
|
|
|
|
virDomainObjPtr vm)
|
|
|
|
{
|
|
|
|
virQEMUMomentRemove rem = {
|
|
|
|
.driver = driver,
|
|
|
|
.vm = vm,
|
|
|
|
.metadata_only = true,
|
|
|
|
.momentDiscard = qemuCheckpointDiscard,
|
|
|
|
};
|
|
|
|
|
|
|
|
virDomainCheckpointForEach(vm->checkpoints, qemuDomainMomentDiscardAll,
|
|
|
|
&rem);
|
|
|
|
virDomainCheckpointObjListRemoveAll(vm->checkpoints);
|
|
|
|
|
|
|
|
return rem.err;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-19 14:49:21 +00:00
|
|
|
/* Called inside job lock */
|
|
|
|
static int
|
|
|
|
qemuCheckpointPrepare(virQEMUDriverPtr driver,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointDefPtr def)
|
|
|
|
{
|
|
|
|
size_t i;
|
2020-04-03 11:52:58 +00:00
|
|
|
g_autofree char *xml = NULL;
|
2019-09-19 14:49:21 +00:00
|
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
|
|
|
|
/* Easiest way to clone inactive portion of vm->def is via
|
|
|
|
* conversion in and back out of xml. */
|
|
|
|
if (!(xml = qemuDomainDefFormatLive(driver, priv->qemuCaps,
|
|
|
|
vm->def, priv->origCPU,
|
|
|
|
true, true)) ||
|
2019-11-27 12:29:21 +00:00
|
|
|
!(def->parent.dom = virDomainDefParseString(xml, driver->xmlopt,
|
2019-09-19 14:49:21 +00:00
|
|
|
priv->qemuCaps,
|
|
|
|
VIR_DOMAIN_DEF_PARSE_INACTIVE |
|
|
|
|
VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE)))
|
2020-04-03 11:52:58 +00:00
|
|
|
return -1;
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
if (virDomainCheckpointAlignDisks(def) < 0)
|
2020-04-03 11:52:58 +00:00
|
|
|
return -1;
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
|
|
|
virDomainCheckpointDiskDefPtr disk = &def->disks[i];
|
|
|
|
|
|
|
|
if (disk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
|
|
|
|
continue;
|
|
|
|
|
2019-09-18 12:48:57 +00:00
|
|
|
if (STRNEQ(disk->bitmap, def->parent.name)) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("bitmap for disk '%s' must match checkpoint name '%s'"),
|
|
|
|
disk->name, def->parent.name);
|
2020-04-03 11:52:58 +00:00
|
|
|
return -1;
|
2019-09-18 12:48:57 +00:00
|
|
|
}
|
|
|
|
|
2019-09-19 14:49:21 +00:00
|
|
|
if (vm->def->disks[i]->src->format != VIR_STORAGE_FILE_QCOW2) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("checkpoint for disk %s unsupported "
|
|
|
|
"for storage type %s"),
|
|
|
|
disk->name,
|
|
|
|
virStorageFileFormatTypeToString(
|
|
|
|
vm->def->disks[i]->src->format));
|
2020-04-03 11:52:58 +00:00
|
|
|
return -1;
|
2019-09-19 14:49:21 +00:00
|
|
|
}
|
2020-04-17 09:09:58 +00:00
|
|
|
|
|
|
|
if (!qemuDomainDiskBlockJobIsSupported(vm, vm->def->disks[i]))
|
|
|
|
return -1;
|
2019-09-19 14:49:21 +00:00
|
|
|
}
|
|
|
|
|
2020-04-03 11:52:58 +00:00
|
|
|
return 0;
|
2019-09-19 14:49:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuCheckpointAddActions(virDomainObjPtr vm,
|
|
|
|
virJSONValuePtr actions,
|
|
|
|
virDomainCheckpointDefPtr def)
|
|
|
|
{
|
2020-01-08 08:55:18 +00:00
|
|
|
size_t i;
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
for (i = 0; i < def->ndisks; i++) {
|
2020-01-08 06:55:18 +00:00
|
|
|
virDomainCheckpointDiskDef *chkdisk = &def->disks[i];
|
2020-01-08 07:10:35 +00:00
|
|
|
virDomainDiskDefPtr domdisk = virDomainDiskByTarget(vm->def, chkdisk->name);
|
2019-09-19 14:49:21 +00:00
|
|
|
|
2020-01-08 07:10:35 +00:00
|
|
|
/* checkpoint definition validator mandates that the corresponding
|
|
|
|
* domdisk should exist */
|
|
|
|
if (!domdisk ||
|
|
|
|
chkdisk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
|
2019-09-19 14:49:21 +00:00
|
|
|
continue;
|
2020-01-08 07:10:35 +00:00
|
|
|
|
|
|
|
if (qemuMonitorTransactionBitmapAdd(actions, domdisk->src->nodeformat,
|
|
|
|
chkdisk->bitmap, true, false, 0) < 0)
|
2019-09-19 14:49:21 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-04 09:16:02 +00:00
|
|
|
static int
|
|
|
|
qemuCheckpointRedefineValidateBitmaps(virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointDefPtr chkdef)
|
|
|
|
{
|
|
|
|
g_autoptr(GHashTable) blockNamedNodeData = NULL;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
if (virDomainObjCheckActive(vm) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_JOB_NONE)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for (i = 0; i < chkdef->ndisks; i++) {
|
|
|
|
virDomainCheckpointDiskDefPtr chkdisk = chkdef->disks + i;
|
|
|
|
virDomainDiskDefPtr domdisk;
|
|
|
|
|
|
|
|
if (chkdisk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* we tolerate missing disks due to possible detach */
|
|
|
|
if (!(domdisk = virDomainDiskByTarget(vm->def, chkdisk->name)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!qemuBlockBitmapChainIsValid(domdisk->src, chkdef->parent.name,
|
|
|
|
blockNamedNodeData)) {
|
|
|
|
virReportError(VIR_ERR_CHECKPOINT_INCONSISTENT,
|
|
|
|
_("missing or broken bitmap '%s' for disk '%s'"),
|
|
|
|
chkdef->parent.name, domdisk->dst);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-01 13:04:33 +00:00
|
|
|
static virDomainMomentObjPtr
|
2020-12-02 12:35:29 +00:00
|
|
|
qemuCheckpointRedefine(virDomainObjPtr vm,
|
2019-10-01 13:04:33 +00:00
|
|
|
virDomainCheckpointDefPtr *def,
|
2020-11-04 09:16:02 +00:00
|
|
|
bool *update_current,
|
|
|
|
bool validate_bitmaps)
|
2019-10-01 13:04:33 +00:00
|
|
|
{
|
2020-11-04 09:10:56 +00:00
|
|
|
if (virDomainCheckpointRedefinePrep(vm, *def, update_current) < 0)
|
2019-10-01 13:04:33 +00:00
|
|
|
return NULL;
|
|
|
|
|
2020-11-04 09:16:02 +00:00
|
|
|
if (validate_bitmaps &&
|
|
|
|
qemuCheckpointRedefineValidateBitmaps(vm, *def) < 0)
|
|
|
|
return NULL;
|
2019-10-01 13:04:33 +00:00
|
|
|
|
2020-12-02 12:35:29 +00:00
|
|
|
return virDomainCheckpointRedefineCommit(vm, def);
|
2019-10-01 13:04:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuCheckpointCreateCommon(virQEMUDriverPtr driver,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointDefPtr *def,
|
|
|
|
virJSONValuePtr *actions,
|
|
|
|
virDomainMomentObjPtr *chk)
|
|
|
|
{
|
|
|
|
g_autoptr(virJSONValue) tmpactions = NULL;
|
|
|
|
virDomainMomentObjPtr parent;
|
|
|
|
|
2019-11-27 13:19:09 +00:00
|
|
|
if (qemuCheckpointPrepare(driver, vm, *def) < 0)
|
2019-10-01 13:04:33 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if ((parent = virDomainCheckpointGetCurrent(vm->checkpoints)))
|
|
|
|
(*def)->parent.parent_name = g_strdup(parent->def->name);
|
|
|
|
|
2020-01-31 07:18:36 +00:00
|
|
|
tmpactions = virJSONValueNewArray();
|
2019-10-01 13:04:33 +00:00
|
|
|
|
2020-04-17 07:18:49 +00:00
|
|
|
if (qemuCheckpointAddActions(vm, tmpactions, *def) < 0)
|
2019-10-01 13:04:33 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!(*chk = virDomainCheckpointAssignDef(vm->checkpoints, *def)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
*def = NULL;
|
|
|
|
|
|
|
|
*actions = g_steal_pointer(&tmpactions);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-06 14:03:56 +00:00
|
|
|
/**
|
|
|
|
* qemuCheckpointRollbackMetadata:
|
|
|
|
* @vm: domain object
|
|
|
|
* @chk: checkpoint object
|
|
|
|
*
|
|
|
|
* If @chk is not null remove the @chk object from the list of checkpoints of @vm.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
qemuCheckpointRollbackMetadata(virDomainObjPtr vm,
|
|
|
|
virDomainMomentObjPtr chk)
|
|
|
|
{
|
|
|
|
if (!chk)
|
|
|
|
return;
|
|
|
|
|
|
|
|
virDomainCheckpointObjListRemove(vm->checkpoints, chk);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-01 13:04:33 +00:00
|
|
|
static virDomainMomentObjPtr
|
|
|
|
qemuCheckpointCreate(virQEMUDriverPtr driver,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointDefPtr *def)
|
|
|
|
{
|
|
|
|
g_autoptr(virJSONValue) actions = NULL;
|
|
|
|
virDomainMomentObjPtr chk = NULL;
|
|
|
|
int rc;
|
|
|
|
|
2019-11-27 13:19:09 +00:00
|
|
|
if (qemuCheckpointCreateCommon(driver, vm, def, &actions, &chk) < 0)
|
2019-10-01 13:04:33 +00:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
qemuDomainObjEnterMonitor(driver, vm);
|
|
|
|
rc = qemuMonitorTransaction(qemuDomainGetMonitor(vm), &actions);
|
|
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) {
|
2020-01-06 14:03:56 +00:00
|
|
|
qemuCheckpointRollbackMetadata(vm, chk);
|
2019-10-01 13:04:33 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return chk;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-10-03 12:35:58 +00:00
|
|
|
int
|
|
|
|
qemuCheckpointCreateFinalize(virQEMUDriverPtr driver,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
virQEMUDriverConfigPtr cfg,
|
|
|
|
virDomainMomentObjPtr chk,
|
|
|
|
bool update_current)
|
|
|
|
{
|
|
|
|
if (update_current)
|
2020-11-23 11:51:23 +00:00
|
|
|
qemuCheckpointSetCurrent(vm, chk);
|
2019-10-03 12:35:58 +00:00
|
|
|
|
2019-11-27 13:19:09 +00:00
|
|
|
if (qemuCheckpointWriteMetadata(vm, chk,
|
2019-10-03 12:35:58 +00:00
|
|
|
driver->xmlopt,
|
|
|
|
cfg->checkpointDir) < 0) {
|
|
|
|
/* if writing of metadata fails, error out rather than trying
|
|
|
|
* to silently carry on without completing the checkpoint */
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("unable to save metadata for checkpoint %s"),
|
|
|
|
chk->def->name);
|
2020-01-06 14:03:56 +00:00
|
|
|
qemuCheckpointRollbackMetadata(vm, chk);
|
2019-10-03 12:35:58 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
virDomainCheckpointLinkParent(vm->checkpoints, chk);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-19 14:49:21 +00:00
|
|
|
virDomainCheckpointPtr
|
|
|
|
qemuCheckpointCreateXML(virDomainPtr domain,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
const char *xmlDesc,
|
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
virQEMUDriverPtr driver = priv->driver;
|
|
|
|
virDomainMomentObjPtr chk = NULL;
|
|
|
|
virDomainCheckpointPtr checkpoint = NULL;
|
|
|
|
bool update_current = true;
|
|
|
|
bool redefine = flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE;
|
2020-11-09 16:14:53 +00:00
|
|
|
bool validate_bitmaps = flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE_VALIDATE;
|
2019-09-19 14:49:21 +00:00
|
|
|
unsigned int parse_flags = 0;
|
2019-12-08 21:48:58 +00:00
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virDomainCheckpointDef) def = NULL;
|
2019-09-19 14:49:21 +00:00
|
|
|
|
2020-11-04 09:16:02 +00:00
|
|
|
virCheckFlags(VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE |
|
2020-11-09 16:14:53 +00:00
|
|
|
VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE_VALIDATE, NULL);
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
if (redefine) {
|
|
|
|
parse_flags |= VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE;
|
|
|
|
update_current = false;
|
|
|
|
}
|
|
|
|
|
2020-04-01 12:06:44 +00:00
|
|
|
if (!redefine) {
|
|
|
|
if (!virDomainObjIsActive(vm)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("cannot create checkpoint for inactive domain"));
|
|
|
|
return NULL;
|
|
|
|
}
|
2019-09-19 14:49:21 +00:00
|
|
|
|
2020-04-01 12:06:44 +00:00
|
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP)) {
|
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
|
|
|
_("incremental backup is not supported yet"));
|
|
|
|
return NULL;
|
|
|
|
}
|
2019-09-19 14:49:21 +00:00
|
|
|
}
|
|
|
|
|
2019-11-27 13:10:21 +00:00
|
|
|
if (!(def = virDomainCheckpointDefParseString(xmlDesc, driver->xmlopt,
|
2019-09-19 14:49:21 +00:00
|
|
|
priv->qemuCaps, parse_flags)))
|
2019-09-26 11:19:40 +00:00
|
|
|
return NULL;
|
2019-09-19 14:49:21 +00:00
|
|
|
/* Unlike snapshots, the RNG schema already ensured a sane filename. */
|
|
|
|
|
|
|
|
/* We are going to modify the domain below. */
|
|
|
|
if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
|
2019-09-26 11:19:40 +00:00
|
|
|
return NULL;
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
if (redefine) {
|
2020-12-02 12:35:29 +00:00
|
|
|
chk = qemuCheckpointRedefine(vm, &def, &update_current, validate_bitmaps);
|
2019-10-01 13:04:33 +00:00
|
|
|
} else {
|
2019-11-27 13:19:09 +00:00
|
|
|
chk = qemuCheckpointCreate(driver, vm, &def);
|
2019-09-19 14:49:21 +00:00
|
|
|
}
|
|
|
|
|
2019-10-01 13:04:33 +00:00
|
|
|
if (!chk)
|
|
|
|
goto endjob;
|
2019-09-19 14:49:21 +00:00
|
|
|
|
2019-10-03 12:35:58 +00:00
|
|
|
if (qemuCheckpointCreateFinalize(driver, vm, cfg, chk, update_current) < 0)
|
2019-10-01 13:04:33 +00:00
|
|
|
goto endjob;
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
/* If we fail after this point, there's not a whole lot we can do;
|
|
|
|
* we've successfully created the checkpoint, so we have to go
|
|
|
|
* forward the best we can.
|
|
|
|
*/
|
|
|
|
checkpoint = virGetDomainCheckpoint(domain, chk->def->name);
|
|
|
|
|
|
|
|
endjob:
|
|
|
|
qemuDomainObjEndJob(driver, vm);
|
|
|
|
|
|
|
|
return checkpoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-07-01 14:58:37 +00:00
|
|
|
struct qemuCheckpointDiskMap {
|
|
|
|
virDomainCheckpointDiskDefPtr chkdisk;
|
|
|
|
virDomainDiskDefPtr domdisk;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuCheckpointGetXMLDescUpdateSize(virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointDefPtr chkdef)
|
|
|
|
{
|
|
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
virQEMUDriverPtr driver = priv->driver;
|
2020-10-22 17:04:18 +00:00
|
|
|
g_autoptr(GHashTable) blockNamedNodeData = NULL;
|
2020-07-01 14:58:37 +00:00
|
|
|
g_autofree struct qemuCheckpointDiskMap *diskmap = NULL;
|
|
|
|
g_autoptr(virJSONValue) recoveractions = NULL;
|
|
|
|
g_autoptr(virJSONValue) mergeactions = virJSONValueNewArray();
|
|
|
|
g_autoptr(virJSONValue) cleanupactions = virJSONValueNewArray();
|
|
|
|
int rc = 0;
|
|
|
|
size_t ndisks = 0;
|
|
|
|
size_t i;
|
|
|
|
int ret = -1;
|
|
|
|
|
|
|
|
if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virDomainObjCheckActive(vm) < 0)
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_JOB_NONE)))
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
/* enumerate disks relevant for the checkpoint which are also present in the
|
|
|
|
* domain */
|
|
|
|
diskmap = g_new0(struct qemuCheckpointDiskMap, chkdef->ndisks);
|
|
|
|
|
|
|
|
for (i = 0; i < chkdef->ndisks; i++) {
|
|
|
|
virDomainCheckpointDiskDefPtr chkdisk = chkdef->disks + i;
|
|
|
|
virDomainDiskDefPtr domdisk;
|
|
|
|
|
|
|
|
chkdisk->size = 0;
|
|
|
|
chkdisk->sizeValid = false;
|
|
|
|
|
|
|
|
if (chkdisk->type != VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!(domdisk = virDomainDiskByTarget(vm->def, chkdisk->name)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!qemuBlockBitmapChainIsValid(domdisk->src, chkdef->parent.name, blockNamedNodeData))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
diskmap[ndisks].chkdisk = chkdisk;
|
|
|
|
diskmap[ndisks].domdisk = domdisk;
|
|
|
|
ndisks++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ndisks == 0) {
|
|
|
|
ret = 0;
|
|
|
|
goto endjob;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* we need to calculate the merged bitmap to obtain accurate data */
|
|
|
|
for (i = 0; i < ndisks; i++) {
|
|
|
|
virDomainDiskDefPtr domdisk = diskmap[i].domdisk;
|
|
|
|
g_autoptr(virJSONValue) actions = NULL;
|
|
|
|
|
|
|
|
/* possibly delete leftovers from previous cases */
|
|
|
|
if (qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData, domdisk->src,
|
|
|
|
"libvirt-tmp-size-xml")) {
|
|
|
|
if (!recoveractions)
|
|
|
|
recoveractions = virJSONValueNewArray();
|
|
|
|
|
|
|
|
if (qemuMonitorTransactionBitmapRemove(recoveractions,
|
|
|
|
domdisk->src->nodeformat,
|
|
|
|
"libvirt-tmp-size-xml") < 0)
|
|
|
|
goto endjob;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qemuBlockGetBitmapMergeActions(domdisk->src, NULL, domdisk->src,
|
|
|
|
chkdef->parent.name, "libvirt-tmp-size-xml",
|
|
|
|
NULL, &actions, blockNamedNodeData) < 0)
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
if (virJSONValueArrayConcat(mergeactions, actions) < 0)
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
if (qemuMonitorTransactionBitmapRemove(cleanupactions,
|
|
|
|
domdisk->src->nodeformat,
|
|
|
|
"libvirt-tmp-size-xml") < 0)
|
|
|
|
goto endjob;
|
|
|
|
}
|
|
|
|
|
|
|
|
qemuDomainObjEnterMonitor(driver, vm);
|
|
|
|
|
|
|
|
if (rc == 0 && recoveractions)
|
|
|
|
rc = qemuMonitorTransaction(priv->mon, &recoveractions);
|
|
|
|
|
|
|
|
if (rc == 0)
|
|
|
|
rc = qemuMonitorTransaction(priv->mon, &mergeactions);
|
|
|
|
|
|
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
/* now do a final refresh */
|
|
|
|
virHashFree(blockNamedNodeData);
|
|
|
|
if (!(blockNamedNodeData = qemuBlockGetNamedNodeData(vm, QEMU_ASYNC_JOB_NONE)))
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
qemuDomainObjEnterMonitor(driver, vm);
|
|
|
|
|
|
|
|
rc = qemuMonitorTransaction(priv->mon, &cleanupactions);
|
|
|
|
|
|
|
|
if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0)
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
/* update disks */
|
|
|
|
for (i = 0; i < ndisks; i++) {
|
|
|
|
virDomainCheckpointDiskDefPtr chkdisk = diskmap[i].chkdisk;
|
|
|
|
virDomainDiskDefPtr domdisk = diskmap[i].domdisk;
|
|
|
|
qemuBlockNamedNodeDataBitmapPtr bitmap;
|
|
|
|
|
|
|
|
if ((bitmap = qemuBlockNamedNodeDataGetBitmapByName(blockNamedNodeData, domdisk->src,
|
|
|
|
"libvirt-tmp-size-xml"))) {
|
|
|
|
chkdisk->size = bitmap->dirtybytes;
|
|
|
|
chkdisk->sizeValid = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
endjob:
|
|
|
|
qemuDomainObjEndJob(driver, vm);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-19 14:49:21 +00:00
|
|
|
char *
|
|
|
|
qemuCheckpointGetXMLDesc(virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointPtr checkpoint,
|
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
virQEMUDriverPtr driver = priv->driver;
|
|
|
|
virDomainMomentObjPtr chk = NULL;
|
|
|
|
virDomainCheckpointDefPtr chkdef;
|
|
|
|
unsigned int format_flags;
|
|
|
|
|
|
|
|
virCheckFlags(VIR_DOMAIN_CHECKPOINT_XML_SECURE |
|
2020-07-01 14:58:37 +00:00
|
|
|
VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN |
|
|
|
|
VIR_DOMAIN_CHECKPOINT_XML_SIZE, NULL);
|
2019-09-19 14:49:21 +00:00
|
|
|
|
|
|
|
if (!(chk = qemuCheckpointObjFromCheckpoint(vm, checkpoint)))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
chkdef = virDomainCheckpointObjGetDef(chk);
|
|
|
|
|
2020-07-01 14:58:37 +00:00
|
|
|
if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE &&
|
|
|
|
qemuCheckpointGetXMLDescUpdateSize(vm, chkdef) < 0)
|
|
|
|
return NULL;
|
|
|
|
|
2019-09-19 14:49:21 +00:00
|
|
|
format_flags = virDomainCheckpointFormatConvertXMLFlags(flags);
|
2019-11-27 13:10:21 +00:00
|
|
|
return virDomainCheckpointDefFormat(chkdef, driver->xmlopt,
|
2019-09-19 14:49:21 +00:00
|
|
|
format_flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct virQEMUCheckpointReparent {
|
|
|
|
const char *dir;
|
|
|
|
virDomainMomentObjPtr parent;
|
|
|
|
virDomainObjPtr vm;
|
|
|
|
virDomainXMLOptionPtr xmlopt;
|
|
|
|
int err;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuCheckpointReparentChildren(void *payload,
|
2020-10-21 11:31:16 +00:00
|
|
|
const char *name G_GNUC_UNUSED,
|
2019-09-19 14:49:21 +00:00
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
virDomainMomentObjPtr moment = payload;
|
|
|
|
struct virQEMUCheckpointReparent *rep = data;
|
|
|
|
|
|
|
|
if (rep->err < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
VIR_FREE(moment->def->parent_name);
|
|
|
|
|
2019-10-20 11:49:46 +00:00
|
|
|
if (rep->parent->def)
|
|
|
|
moment->def->parent_name = g_strdup(rep->parent->def->name);
|
2019-09-19 14:49:21 +00:00
|
|
|
|
2019-11-27 13:19:09 +00:00
|
|
|
rep->err = qemuCheckpointWriteMetadata(rep->vm, moment,
|
2019-09-20 11:47:04 +00:00
|
|
|
rep->xmlopt, rep->dir);
|
2019-09-19 14:49:21 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
qemuCheckpointDelete(virDomainObjPtr vm,
|
|
|
|
virDomainCheckpointPtr checkpoint,
|
|
|
|
unsigned int flags)
|
|
|
|
{
|
|
|
|
qemuDomainObjPrivatePtr priv = vm->privateData;
|
|
|
|
virQEMUDriverPtr driver = priv->driver;
|
2019-10-15 12:47:50 +00:00
|
|
|
g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
|
2019-09-19 14:49:21 +00:00
|
|
|
int ret = -1;
|
|
|
|
virDomainMomentObjPtr chk = NULL;
|
|
|
|
virQEMUMomentRemove rem;
|
|
|
|
struct virQEMUCheckpointReparent rep;
|
|
|
|
bool metadata_only = !!(flags & VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY);
|
|
|
|
|
|
|
|
virCheckFlags(VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN |
|
|
|
|
VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY |
|
|
|
|
VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY, -1);
|
|
|
|
|
|
|
|
if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!metadata_only) {
|
2020-03-26 14:20:44 +00:00
|
|
|
if (!virDomainObjIsActive(vm)) {
|
2019-09-19 14:49:21 +00:00
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
2020-03-26 14:20:44 +00:00
|
|
|
_("cannot delete checkpoint for inactive domain"));
|
2019-09-19 14:49:21 +00:00
|
|
|
goto endjob;
|
|
|
|
}
|
2019-09-26 11:25:40 +00:00
|
|
|
|
2020-03-26 14:20:44 +00:00
|
|
|
if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP)) {
|
2019-09-26 11:25:40 +00:00
|
|
|
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
|
2020-03-26 14:20:44 +00:00
|
|
|
_("incremental backup is not supported yet"));
|
2019-09-19 14:49:21 +00:00
|
|
|
goto endjob;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(chk = qemuCheckpointObjFromCheckpoint(vm, checkpoint)))
|
|
|
|
goto endjob;
|
|
|
|
|
|
|
|
if (flags & (VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN |
|
|
|
|
VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY)) {
|
|
|
|
rem.driver = driver;
|
|
|
|
rem.vm = vm;
|
|
|
|
rem.metadata_only = metadata_only;
|
|
|
|
rem.err = 0;
|
|
|
|
rem.current = virDomainCheckpointGetCurrent(vm->checkpoints);
|
|
|
|
rem.found = false;
|
2019-09-20 11:47:04 +00:00
|
|
|
rem.momentDiscard = qemuCheckpointDiscard;
|
2019-09-19 14:49:21 +00:00
|
|
|
virDomainMomentForEachDescendant(chk, qemuDomainMomentDiscardAll,
|
|
|
|
&rem);
|
|
|
|
if (rem.err < 0)
|
|
|
|
goto endjob;
|
|
|
|
if (rem.found) {
|
2020-11-23 11:51:23 +00:00
|
|
|
qemuCheckpointSetCurrent(vm, chk);
|
|
|
|
|
2019-09-19 14:49:21 +00:00
|
|
|
if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) {
|
2019-11-27 13:19:09 +00:00
|
|
|
if (qemuCheckpointWriteMetadata(vm, chk,
|
2019-09-20 11:47:04 +00:00
|
|
|
driver->xmlopt,
|
|
|
|
cfg->checkpointDir) < 0) {
|
2019-09-19 14:49:21 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("failed to set checkpoint '%s' as current"),
|
|
|
|
chk->def->name);
|
|
|
|
virDomainCheckpointSetCurrent(vm->checkpoints, NULL);
|
|
|
|
goto endjob;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (chk->nchildren) {
|
|
|
|
rep.dir = cfg->checkpointDir;
|
|
|
|
rep.parent = chk->parent;
|
|
|
|
rep.vm = vm;
|
|
|
|
rep.err = 0;
|
|
|
|
rep.xmlopt = driver->xmlopt;
|
|
|
|
virDomainMomentForEachChild(chk, qemuCheckpointReparentChildren,
|
|
|
|
&rep);
|
|
|
|
if (rep.err < 0)
|
|
|
|
goto endjob;
|
|
|
|
virDomainMomentMoveChildren(chk, chk->parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) {
|
|
|
|
virDomainMomentDropChildren(chk);
|
|
|
|
ret = 0;
|
|
|
|
} else {
|
2019-09-20 11:47:04 +00:00
|
|
|
ret = qemuCheckpointDiscard(driver, vm, chk, true, metadata_only);
|
2019-09-19 14:49:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
endjob:
|
|
|
|
qemuDomainObjEndJob(driver, vm);
|
|
|
|
return ret;
|
|
|
|
}
|