From nobody Sun Feb 8 22:58:13 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1560916244; cv=none; d=zoho.com; s=zohoarc; b=S25vjuRh2aZqFpxcWl3k/stLpy3EC1giBGb7yhQHt0ArhU742ZDNdQgQz68g0tJDpH17y2vWsI/UiU2CU0aTMVrjMW6sBDomW7OYclZD4GB4bIIMWVUnpNYhsskz9NsO5Torl9+pe4ttV3fNsHxKw10VV9q9JC18Kjkq5y9uYG0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1560916244; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=pCdvF2cVJT6DYGtGjAYs5wQcuZ1zwxBViBTb6zJBwtQ=; b=L+LhGhSAH+XMPOan0KRk54gFytbLV0ombo4+IuisrveeDxmynOesrzF0oqWCaVTqHLtEK9Tz+iILXggjIoC6UDoUnqvBs1eLOxrj7gGL98VULYsU3JKhbKaHbS9M5/mrelO8w2+BuE6f6vOn34seyxr4gJLChR/u0cBmZTg3OSk= ARC-Authentication-Results: i=1; mx.zoho.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1560916244339793.8618963382955; Tue, 18 Jun 2019 20:50:44 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 9A681356FF; Wed, 19 Jun 2019 03:50:42 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 739E05C225; Wed, 19 Jun 2019 03:50:42 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 371DC206D4; Wed, 19 Jun 2019 03:50:42 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x5J3m7m6017796 for ; Tue, 18 Jun 2019 23:48:07 -0400 Received: by smtp.corp.redhat.com (Postfix) id CDA167E66A; Wed, 19 Jun 2019 03:48:07 +0000 (UTC) Received: from blue.redhat.com (ovpn-116-44.phx2.redhat.com [10.3.116.44]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7843F7DFF3 for ; Wed, 19 Jun 2019 03:48:07 +0000 (UTC) From: Eric Blake To: libvir-list@redhat.com Date: Tue, 18 Jun 2019 22:47:52 -0500 Message-Id: <20190619034754.2708-12-eblake@redhat.com> In-Reply-To: <20190619034754.2708-1-eblake@redhat.com> References: <20190619034754.2708-1-eblake@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-loop: libvir-list@redhat.com Subject: [libvirt] [PATCH 11/13] backup: qemu: Implement metadata tracking for checkpoint APIs X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.30]); Wed, 19 Jun 2019 03:50:43 +0000 (UTC) Content-Type: text/plain; charset="utf-8" A lot of this work heavily copies from the existing snapshot APIs. The interaction with qemu during create/delete still needs to be implemented, but this takes care of all the libvirt metadata (saving and restoring XML, and tracking the relations between multiple checkpoints). Signed-off-by: Eric Blake --- src/qemu/qemu_block.h | 3 + src/qemu/qemu_conf.h | 2 + src/qemu/qemu_domain.h | 15 + src/qemu/qemu_block.c | 12 + src/qemu/qemu_conf.c | 5 + src/qemu/qemu_domain.c | 134 ++++++++ src/qemu/qemu_driver.c | 730 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 901 insertions(+) diff --git a/src/qemu/qemu_block.h b/src/qemu/qemu_block.h index eab8da8e2c..b280c5d369 100644 --- a/src/qemu/qemu_block.h +++ b/src/qemu/qemu_block.h @@ -128,4 +128,7 @@ qemuBlockSnapshotAddLegacy(virJSONValuePtr actions, virStorageSourcePtr newsrc, bool reuse); +const char * +qemuBlockNodeLookup(virDomainObjPtr vm, const char *disk); + #endif /* LIBVIRT_QEMU_BLOCK_H */ diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 983e74a3cf..1029a042f5 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -29,6 +29,7 @@ # include "capabilities.h" # include "network_conf.h" # include "domain_conf.h" +# include "checkpoint_conf.h" # include "snapshot_conf.h" # include "domain_event.h" # include "virthread.h" @@ -110,6 +111,7 @@ struct _virQEMUDriverConfig { char *cacheDir; char *saveDir; char *snapshotDir; + char *checkpointDir; char *channelTargetDir; char *nvramDir; char *swtpmStorageDir; diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index f92f0dbc27..71266aa1d0 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -724,6 +724,21 @@ int qemuDomainMomentDiscardAll(void *payload, int qemuDomainSnapshotDiscardAllMetadata(virQEMUDriverPtr driver, virDomainObjPtr vm); +int qemuDomainCheckpointWriteMetadata(virDomainObjPtr vm, + virDomainMomentObjPtr checkpoint, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + const char *checkpointDir); + +int qemuDomainCheckpointDiscard(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr chk, + bool update_current, + bool metadata_only); + +int qemuDomainCheckpointDiscardAllMetadata(virQEMUDriverPtr driver, + virDomainObjPtr vm); + void qemuDomainRemoveInactive(virQEMUDriverPtr driver, virDomainObjPtr vm); diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 7d9f7ec3ab..b496b08afa 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -1647,3 +1647,15 @@ qemuBlockStorageGetCopyOnReadProps(virDomainDiskDefP= tr disk) return ret; } + +const char * +qemuBlockNodeLookup(virDomainObjPtr vm, const char *disk) +{ + size_t i; + + for (i =3D 0; i < vm->def->ndisks; i++) { + if (STREQ(vm->def->disks[i]->dst, disk)) + return vm->def->disks[i]->src->nodeformat; + } + return NULL; +} diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index daea11dacb..1312e753db 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -179,6 +179,8 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool priv= ileged) goto error; if (virAsprintf(&cfg->snapshotDir, "%s/snapshot", cfg->libDir) < 0) goto error; + if (virAsprintf(&cfg->checkpointDir, "%s/checkpoint", cfg->libDir)= < 0) + goto error; if (virAsprintf(&cfg->autoDumpPath, "%s/dump", cfg->libDir) < 0) goto error; if (virAsprintf(&cfg->channelTargetDir, @@ -242,6 +244,8 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool priv= ileged) goto error; if (virAsprintf(&cfg->snapshotDir, "%s/qemu/snapshot", cfg->config= BaseDir) < 0) goto error; + if (virAsprintf(&cfg->checkpointDir, "%s/qemu/checkpoint", cfg->co= nfigBaseDir) < 0) + goto error; if (virAsprintf(&cfg->autoDumpPath, "%s/qemu/dump", cfg->configBas= eDir) < 0) goto error; if (virAsprintf(&cfg->channelTargetDir, @@ -354,6 +358,7 @@ static void virQEMUDriverConfigDispose(void *obj) VIR_FREE(cfg->cacheDir); VIR_FREE(cfg->saveDir); VIR_FREE(cfg->snapshotDir); + VIR_FREE(cfg->checkpointDir); VIR_FREE(cfg->channelTargetDir); VIR_FREE(cfg->nvramDir); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index e5c6ef3fda..e128dc169b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -56,6 +56,7 @@ #include "logging/log_manager.h" #include "locking/domain_lock.h" #include "virdomainsnapshotobjlist.h" +#include "virdomaincheckpointobjlist.h" #ifdef MAJOR_IN_MKDEV # include @@ -8567,6 +8568,40 @@ qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, return ret; } +int +qemuDomainCheckpointWriteMetadata(virDomainObjPtr vm, + virDomainMomentObjPtr checkpoint, + virCapsPtr caps, + virDomainXMLOptionPtr xmlopt, + const char *checkpointDir) +{ + unsigned int flags =3D VIR_DOMAIN_CHECKPOINT_FORMAT_SECURE | + VIR_DOMAIN_CHECKPOINT_FORMAT_INTERNAL; + virDomainCheckpointDefPtr def =3D virDomainCheckpointObjGetDef(checkpo= int); + VIR_AUTOFREE(char *) newxml =3D NULL; + VIR_AUTOFREE(char *) chkDir =3D NULL; + VIR_AUTOFREE(char *) chkFile =3D NULL; + + if (virDomainCheckpointGetCurrent(vm->checkpoints) =3D=3D checkpoint) + flags |=3D VIR_DOMAIN_CHECKPOINT_FORMAT_CURRENT; + newxml =3D virDomainCheckpointDefFormat(def, caps, xmlopt, flags); + if (newxml =3D=3D NULL) + return -1; + + if (virAsprintf(&chkDir, "%s/%s", checkpointDir, vm->def->name) < 0) + return -1; + if (virFileMakePath(chkDir) < 0) { + virReportSystemError(errno, _("cannot create checkpoint directory = '%s'"), + chkDir); + return -1; + } + + if (virAsprintf(&chkFile, "%s/%s.xml", chkDir, def->parent.name) < 0) + return -1; + + return virXMLSaveFile(chkFile, NULL, "checkpoint-edit", newxml); +} + /* The domain is expected to be locked and inactive. Return -1 on normal * failure, 1 if we skipped a disk due to try_all. */ static int @@ -8763,12 +8798,100 @@ qemuDomainSnapshotDiscardAllMetadata(virQEMUDriver= Ptr driver, } +/* Discard one checkpoint (or its metadata), without reparenting any child= ren. */ +int +qemuDomainCheckpointDiscard(virQEMUDriverPtr driver, + virDomainObjPtr vm, + virDomainMomentObjPtr chk, + bool update_parent, + bool metadata_only) +{ + char *chkFile =3D NULL; + int ret =3D -1; + virDomainMomentObjPtr parentchk =3D NULL; + virQEMUDriverConfigPtr cfg =3D virQEMUDriverGetConfig(driver); + + if (!metadata_only) { + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot remove checkpoint from inactive domai= n")); + goto cleanup; + } else { + /* TODO: Implement QMP sequence to merge bitmaps */ + // qemuDomainObjPrivatePtr priv; + // priv =3D vm->privateData; + // qemuDomainObjEnterMonitor(driver, vm); + // /* we continue on even in the face of error */ + // qemuMonitorDeleteCheckpoint(priv->mon, chk->def->name); + // ignore_value(qemuDomainObjExitMonitor(driver, vm)); + } + } + + if (virAsprintf(&chkFile, "%s/%s/%s.xml", cfg->checkpointDir, + vm->def->name, chk->def->name) < 0) + goto cleanup; + + if (chk =3D=3D virDomainCheckpointGetCurrent(vm->checkpoints)) { + virDomainCheckpointSetCurrent(vm->checkpoints, NULL); + if (update_parent && chk->def->parent_name) { + parentchk =3D virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent_nam= e); + if (!parentchk) { + VIR_WARN("missing parent checkpoint matching name '%s'", + chk->def->parent_name); + } else { + virDomainCheckpointSetCurrent(vm->checkpoints, parentchk); + if (qemuDomainCheckpointWriteMetadata(vm, parentchk, drive= r->caps, + driver->xmlopt, + cfg->checkpointDir) = < 0) { + VIR_WARN("failed to set parent checkpoint '%s' as curr= ent", + 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); + + ret =3D 0; + + cleanup: + VIR_FREE(chkFile); + virObjectUnref(cfg); + return ret; +} + +int +qemuDomainCheckpointDiscardAllMetadata(virQEMUDriverPtr driver, + virDomainObjPtr vm) +{ + virQEMUMomentRemove rem =3D { + .driver =3D driver, + .vm =3D vm, + .metadata_only =3D true, + .momentDiscard =3D qemuDomainCheckpointDiscard, + }; + + virDomainCheckpointForEach(vm->checkpoints, qemuDomainMomentDiscardAll, + &rem); + virDomainCheckpointObjListRemoveAll(vm->checkpoints); + + return rem.err; +} + + static void qemuDomainRemoveInactiveCommon(virQEMUDriverPtr driver, virDomainObjPtr vm) { virQEMUDriverConfigPtr cfg; VIR_AUTOFREE(char *) snapDir =3D NULL; + VIR_AUTOFREE(char *) chkDir =3D NULL; cfg =3D virQEMUDriverGetConfig(driver); @@ -8783,6 +8906,17 @@ qemuDomainRemoveInactiveCommon(virQEMUDriverPtr driv= er, } else if (rmdir(snapDir) < 0 && errno !=3D ENOENT) { VIR_WARN("unable to remove snapshot directory %s", snapDir); } + /* Remove any checkpoint metadata prior to removing the domain */ + if (qemuDomainCheckpointDiscardAllMetadata(driver, vm) < 0) { + VIR_WARN("unable to remove all checkpoints for domain %s", + vm->def->name); + } else if (virAsprintf(&chkDir, "%s/%s", cfg->checkpointDir, + vm->def->name) < 0) { + VIR_WARN("unable to remove checkpoint directory %s/%s", + cfg->snapshotDir, vm->def->name); + } else if (rmdir(chkDir) < 0 && errno !=3D ENOENT) { + VIR_WARN("unable to remove checkpoint directory %s", chkDir); + } qemuExtDevicesCleanupHost(driver, vm->def); virObjectUnref(cfg); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index b6ff038635..5fa319b656 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -103,6 +103,7 @@ #include "virqemu.h" #include "virdomainsnapshotobjlist.h" #include "virenum.h" +#include "virdomaincheckpointobjlist.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -222,6 +223,40 @@ qemuSnapObjFromSnapshot(virDomainObjPtr vm, return qemuSnapObjFromName(vm, snapshot->name); } +/* 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. */ +static virDomainObjPtr +qemuDomObjFromCheckpoint(virDomainCheckpointPtr checkpoint) +{ + return qemuDomObjFromDomain(checkpoint->domain); +} + + +/* Looks up checkpoint object from VM and name */ +static virDomainMomentObjPtr +qemuCheckObjFromName(virDomainObjPtr vm, + const char *name) +{ + virDomainMomentObjPtr chk =3D NULL; + chk =3D 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 */ +static virDomainMomentObjPtr +qemuCheckObjFromCheckpoint(virDomainObjPtr vm, + virDomainCheckpointPtr checkpoint) +{ + return qemuCheckObjFromName(vm, checkpoint->name); +} + static int qemuAutostartDomain(virDomainObjPtr vm, void *opaque) @@ -525,6 +560,124 @@ qemuDomainSnapshotLoad(virDomainObjPtr vm, } +static int +qemuDomainCheckpointLoad(virDomainObjPtr vm, + void *data) +{ + char *baseDir =3D (char *)data; + char *chkDir =3D NULL; + DIR *dir =3D NULL; + struct dirent *entry; + char *xmlStr; + char *fullpath; + virDomainCheckpointDefPtr def =3D NULL; + virDomainMomentObjPtr chk =3D NULL; + virDomainMomentObjPtr current =3D NULL; + bool cur; + unsigned int flags =3D (VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE | + VIR_DOMAIN_CHECKPOINT_PARSE_INTERNAL); + int ret =3D -1; + virCapsPtr caps =3D NULL; + int direrr; + + virObjectLock(vm); + if (virAsprintf(&chkDir, "%s/%s", baseDir, vm->def->name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to allocate memory for " + "checkpoint directory for domain %s"), + vm->def->name); + goto cleanup; + } + + if (!(caps =3D virQEMUDriverGetCapabilities(qemu_driver, false))) + goto cleanup; + + VIR_INFO("Scanning for checkpoints for domain %s in %s", vm->def->name, + chkDir); + + if (virDirOpenIfExists(&dir, chkDir) <=3D 0) + goto cleanup; + + while ((direrr =3D virDirRead(dir, &entry, NULL)) > 0) { + /* NB: ignoring errors, so one malformed config doesn't + kill the whole process */ + VIR_INFO("Loading checkpoint file '%s'", entry->d_name); + + if (virAsprintf(&fullpath, "%s/%s", chkDir, entry->d_name) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to allocate memory for path")); + continue; + } + + if (virFileReadAll(fullpath, 1024*1024*1, &xmlStr) < 0) { + /* Nothing we can do here, skip this one */ + virReportSystemError(errno, + _("Failed to read checkpoint file %s"), + fullpath); + VIR_FREE(fullpath); + continue; + } + + def =3D virDomainCheckpointDefParseString(xmlStr, caps, + qemu_driver->xmlopt, &cur, + flags); + if (!def || virDomainCheckpointAlignDisks(def) < 0) { + /* Nothing we can do here, skip this one */ + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse checkpoint XML from file '%s= '"), + fullpath); + VIR_FREE(fullpath); + VIR_FREE(xmlStr); + continue; + } + + chk =3D virDomainCheckpointAssignDef(vm->checkpoints, def); + if (chk =3D=3D NULL) { + virObjectUnref(def); + } else if (cur) { + if (current) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Too many snapshots claiming to be curren= t for domain %s"), + vm->def->name); + current =3D chk; + } + + VIR_FREE(fullpath); + VIR_FREE(xmlStr); + } + if (direrr < 0) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to fully read directory %s"), + chkDir); + + virDomainCheckpointSetCurrent(vm->checkpoints, current); + + if (virDomainCheckpointUpdateRelations(vm->checkpoints) < 0) + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Checkpoints have inconsistent relations for doma= in %s"), + vm->def->name); + + /* FIXME: qemu keeps internal track of bitmaps, which form the + * basis for checkpoints; it would be nice if we could update our + * internal state to reflect that information automatically. But + * qemu 3.0 did not have access to this via qemu-img for offline + * images (you have to use QMP commands on a running guest), and + * it also does not track relations which we find + * important in our metadata. + */ + + virResetLastError(); + + ret =3D 0; + cleanup: + VIR_DIR_CLOSE(dir); + VIR_FREE(chkDir); + virObjectUnref(caps); + virObjectUnlock(vm); + return ret; +} + + static int qemuDomainNetsRestart(virDomainObjPtr vm, void *data ATTRIBUTE_UNUSED) @@ -653,6 +806,11 @@ qemuStateInitialize(bool privileged, cfg->snapshotDir); goto error; } + if (virFileMakePath(cfg->checkpointDir) < 0) { + virReportSystemError(errno, _("Failed to create checkpoint dir %s"= ), + cfg->checkpointDir); + goto error; + } if (virFileMakePath(cfg->autoDumpPath) < 0) { virReportSystemError(errno, _("Failed to create dump dir %s"), cfg->autoDumpPath); @@ -760,6 +918,13 @@ qemuStateInitialize(bool privileged, (int)cfg->group); goto error; } + if (chown(cfg->checkpointDir, cfg->user, cfg->group) < 0) { + virReportSystemError(errno, + _("unable to set ownership of '%s' to %d:= %d"), + cfg->checkpointDir, (int)cfg->user, + (int)cfg->group); + goto error; + } if (chown(cfg->autoDumpPath, cfg->user, cfg->group) < 0) { virReportSystemError(errno, _("unable to set ownership of '%s' to %d:= %d"), @@ -893,6 +1058,10 @@ qemuStateInitialize(bool privileged, qemuDomainSnapshotLoad, cfg->snapshotDir); + virDomainObjListForEach(qemu_driver->domains, + qemuDomainCheckpointLoad, + cfg->checkpointDir); + virDomainObjListForEach(qemu_driver->domains, qemuDomainManagedSaveLoad, qemu_driver); @@ -7753,6 +7922,7 @@ qemuDomainUndefineFlags(virDomainPtr dom, if (qemuDomainSnapshotDiscardAllMetadata(driver, vm) < 0) goto endjob; } + /* TODO: Restrict deletion if checkpoints exist? */ name =3D qemuDomainManagedSavePath(driver, vm); if (name =3D=3D NULL) @@ -16756,6 +16926,540 @@ qemuDomainSnapshotDelete(virDomainSnapshotPtr sna= pshot, return ret; } + +/* Called prior to job lock */ +static virDomainCheckpointDefPtr +qemuDomainCheckpointDefParseString(virQEMUDriverPtr driver, virCapsPtr cap= s, + const char *xmlDesc, unsigned int flags) +{ + virDomainCheckpointDefPtr ret =3D NULL; + unsigned int parse_flags =3D 0; + VIR_AUTOUNREF(virDomainCheckpointDefPtr) def =3D NULL; + + if (flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE) + parse_flags |=3D VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + if (!(def =3D virDomainCheckpointDefParseString(xmlDesc, caps, driver-= >xmlopt, + NULL, parse_flags))) + return NULL; + + /* reject checkpoint names containing slashes or starting with dot as + * checkpoint definitions are saved in files named by the checkpoint n= ame */ + if (!(flags & VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA)) { + if (strchr(def->parent.name, '/')) { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid checkpoint name '%s': " + "name can't contain '/'"), + def->parent.name); + return NULL; + } + + if (def->parent.name[0] =3D=3D '.') { + virReportError(VIR_ERR_XML_DETAIL, + _("invalid checkpoint name '%s': " + "name can't start with '.'"), + def->parent.name); + return NULL; + } + } + + VIR_STEAL_PTR(ret, def); + return ret; +} + + +/* Called inside job lock */ +static int +qemuDomainCheckpointPrepare(virQEMUDriverPtr driver, virCapsPtr caps, + virDomainObjPtr vm, + virDomainCheckpointDefPtr def) +{ + int ret =3D -1; + size_t i; + char *xml =3D NULL; + qemuDomainObjPrivatePtr priv =3D vm->privateData; + + /* Easiest way to clone inactive portion of vm->def is via + * conversion in and back out of xml. */ + if (!(xml =3D qemuDomainDefFormatLive(driver, vm->def, priv->origCPU, + true, true)) || + !(def->parent.dom =3D virDomainDefParseString(xml, caps, driver->x= mlopt, NULL, + VIR_DOMAIN_DEF_PARSE_I= NACTIVE | + VIR_DOMAIN_DEF_PARSE_S= KIP_VALIDATE))) + goto cleanup; + + if (virDomainCheckpointAlignDisks(def) < 0 || + qemuBlockNodeNamesDetect(driver, vm, QEMU_ASYNC_JOB_NONE) < 0) + goto cleanup; + + for (i =3D 0; i < def->ndisks; i++) { + virDomainCheckpointDiskDefPtr disk =3D &def->disks[i]; + + if (disk->type !=3D VIR_DOMAIN_CHECKPOINT_TYPE_BITMAP) + continue; + + if (vm->def->disks[i]->src->format > 0 && + vm->def->disks[i]->src->format !=3D 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)); + goto cleanup; + } + } + + ret =3D 0; + + cleanup: + VIR_FREE(xml); + return ret; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D domain->conn->privateData; + virDomainObjPtr vm =3D NULL; + char *xml =3D NULL; + virDomainMomentObjPtr chk =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + virDomainMomentObjPtr current =3D NULL; + bool update_current =3D true; + bool redefine =3D flags & VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE; + unsigned int parse_flags =3D 0; + virDomainMomentObjPtr other =3D NULL; + virQEMUDriverConfigPtr cfg =3D NULL; + virCapsPtr caps =3D NULL; + VIR_AUTOUNREF(virDomainCheckpointDefPtr) def =3D NULL; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE | + VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT | + VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA, NULL); + /* TODO: VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE */ + + if (redefine) + parse_flags |=3D VIR_DOMAIN_CHECKPOINT_PARSE_REDEFINE; + if ((redefine && !(flags & VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT)) || + (flags & VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA)) + update_current =3D false; + + if (!(vm =3D qemuDomObjFromDomain(domain))) + goto cleanup; + + cfg =3D virQEMUDriverGetConfig(driver); + + if (virDomainCheckpointCreateXMLEnsureACL(domain->conn, vm->def, flags= ) < 0) + goto cleanup; + + if (!(caps =3D virQEMUDriverGetCapabilities(driver, false))) + goto cleanup; + + if (qemuProcessAutoDestroyActive(driver, vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is marked for auto destroy")); + goto cleanup; + } + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot create checkpoint for inactive domain")); + goto cleanup; + } + + if (!(def =3D qemuDomainCheckpointDefParseString(driver, caps, xmlDesc, + parse_flags))) + goto cleanup; + + /* We are going to modify the domain below. */ + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (redefine) { + if (virDomainCheckpointRedefinePrep(domain, vm, &def, &chk, + driver->xmlopt, + &update_current) < 0) + goto endjob; + } else if (qemuDomainCheckpointPrepare(driver, caps, vm, def) < 0) { + goto endjob; + } + + if (!chk) { + if (!(chk =3D virDomainCheckpointAssignDef(vm->checkpoints, def))) + goto endjob; + + def =3D NULL; + } + + current =3D virDomainCheckpointGetCurrent(vm->checkpoints); + if (current) { + if (!redefine && + VIR_STRDUP(chk->def->parent_name, current->def->name) < 0) + goto endjob; + if (update_current) { + virDomainCheckpointSetCurrent(vm->checkpoints, NULL); + if (qemuDomainCheckpointWriteMetadata(vm, current, + driver->caps, driver->xm= lopt, + cfg->checkpointDir) < 0) + goto endjob; + } + } + + /* actually do the checkpoint */ + if (redefine) { + /* XXX Should we validate that the redefined checkpoint even + * makes sense, such as checking that qemu-img recognizes the + * checkpoint bitmap name in at least one of the domain's disks? = */ + } else { + /* TODO: issue QMP transaction command */ + } + + /* 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 =3D virGetDomainCheckpoint(domain, chk->def->name); + + endjob: + if (checkpoint && !(flags & VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA))= { + if (update_current) + virDomainCheckpointSetCurrent(vm->checkpoints, chk); + if (qemuDomainCheckpointWriteMetadata(vm, chk, driver->caps, + driver->xmlopt, + cfg->checkpointDir) < 0) { + /* if writing of metadata fails, error out rather than trying + * to silently carry on without completing the checkpoint */ + virObjectUnref(checkpoint); + checkpoint =3D NULL; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unable to save metadata for checkpoint %s"), + chk->def->name); + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + } else { + other =3D virDomainCheckpointFindByName(vm->checkpoints, + chk->def->parent_name); + virDomainMomentSetParent(chk, other); + } + } else if (chk) { + virDomainCheckpointObjListRemove(vm->checkpoints, chk); + } + + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + VIR_FREE(xml); + virObjectUnref(caps); + virObjectUnref(cfg); + return checkpoint; +} + + +static int +qemuDomainListAllCheckpoints(virDomainPtr domain, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + virDomainObjPtr vm =3D NULL; + int n =3D -1; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_LIST_ROOTS | + VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL | + VIR_DOMAIN_CHECKPOINT_FILTERS_ALL, -1); + + if (!(vm =3D qemuDomObjFromDomain(domain))) + return -1; + + if (virDomainListAllCheckpointsEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + n =3D virDomainListCheckpoints(vm->checkpoints, NULL, domain, chks, fl= ags); + + cleanup: + virDomainObjEndAPI(&vm); + return n; +} + + +static int +qemuDomainCheckpointListAllChildren(virDomainCheckpointPtr checkpoint, + virDomainCheckpointPtr **chks, + unsigned int flags) +{ + virDomainObjPtr vm =3D NULL; + virDomainMomentObjPtr chk =3D NULL; + int n =3D -1; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS | + VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL | + VIR_DOMAIN_CHECKPOINT_FILTERS_ALL, -1); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointListAllChildrenEnsureACL(checkpoint->domain->co= nn, + vm->def) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + n =3D virDomainListCheckpoints(vm->checkpoints, chk, checkpoint->domai= n, + chks, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return n; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointLookupByName(virDomainPtr domain, + const char *name, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainMomentObjPtr chk =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + + virCheckFlags(0, NULL); + + if (!(vm =3D qemuDomObjFromDomain(domain))) + return NULL; + + if (virDomainCheckpointLookupByNameEnsureACL(domain->conn, vm->def) < = 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromName(vm, name))) + goto cleanup; + + checkpoint =3D virGetDomainCheckpoint(domain, chk->def->name); + + cleanup: + virDomainObjEndAPI(&vm); + return checkpoint; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointGetParent(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainMomentObjPtr chk =3D NULL; + virDomainCheckpointPtr parent =3D NULL; + + virCheckFlags(0, NULL); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return NULL; + + if (virDomainCheckpointGetParentEnsureACL(checkpoint->domain->conn, vm= ->def) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + if (!chk->def->parent_name) { + virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT, + _("checkpoint '%s' does not have a parent"), + chk->def->name); + goto cleanup; + } + + parent =3D virGetDomainCheckpoint(checkpoint->domain, chk->def->parent= _name); + + cleanup: + virDomainObjEndAPI(&vm); + return parent; +} + + +static virDomainCheckpointPtr +qemuDomainCheckpointCurrent(virDomainPtr domain, + unsigned int flags) +{ + virDomainObjPtr vm; + virDomainCheckpointPtr checkpoint =3D NULL; + const char *name; + + virCheckFlags(0, NULL); + + if (!(vm =3D qemuDomObjFromDomain(domain))) + return NULL; + + if (virDomainCheckpointCurrentEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + name =3D virDomainCheckpointGetCurrentName(vm->checkpoints); + if (!name) { + virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT, "%s", + _("the domain does not have a current checkpoint")); + goto cleanup; + } + + checkpoint =3D virGetDomainCheckpoint(domain, name); + + cleanup: + virDomainObjEndAPI(&vm); + return checkpoint; +} + + +static char * +qemuDomainCheckpointGetXMLDesc(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D checkpoint->domain->conn->privateData; + virDomainObjPtr vm =3D NULL; + char *xml =3D NULL; + virDomainMomentObjPtr chk =3D NULL; + virDomainCheckpointDefPtr chkdef; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_XML_SECURE | + VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN, NULL); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return NULL; + + if (virDomainCheckpointGetXMLDescEnsureACL(checkpoint->domain->conn, v= m->def, flags) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + chkdef =3D virDomainCheckpointObjGetDef(chk); + + xml =3D virDomainCheckpointDefFormat(chkdef, driver->caps, driver->xml= opt, + flags); + + if (flags & VIR_DOMAIN_CHECKPOINT_XML_SIZE) + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + return xml; +} + + +static int +qemuDomainCheckpointIsCurrent(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virDomainObjPtr vm =3D NULL; + int ret =3D -1; + virDomainMomentObjPtr chk =3D NULL; + + virCheckFlags(0, -1); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + if (virDomainCheckpointIsCurrentEnsureACL(checkpoint->domain->conn, vm= ->def) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto cleanup; + + ret =3D chk =3D=3D virDomainCheckpointGetCurrent(vm->checkpoints); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static int +qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, + unsigned int flags) +{ + virQEMUDriverPtr driver =3D checkpoint->domain->conn->privateData; + virDomainObjPtr vm =3D NULL; + int ret =3D -1; + virDomainMomentObjPtr chk =3D NULL; + virQEMUMomentRemove rem; + virQEMUMomentReparent rep; + bool metadata_only =3D !!(flags & VIR_DOMAIN_CHECKPOINT_DELETE_METADAT= A_ONLY); + virQEMUDriverConfigPtr cfg =3D NULL; + + virCheckFlags(VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN | + VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY | + VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY, -1); + + if (!(vm =3D qemuDomObjFromCheckpoint(checkpoint))) + return -1; + + cfg =3D virQEMUDriverGetConfig(driver); + + if (virDomainCheckpointDeleteEnsureACL(checkpoint->domain->conn, vm->d= ef) < 0) + goto cleanup; + + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) + goto cleanup; + + if (!(chk =3D qemuCheckObjFromCheckpoint(vm, checkpoint))) + goto endjob; + + if (flags & (VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN | + VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY)) { + rem.driver =3D driver; + rem.vm =3D vm; + rem.metadata_only =3D metadata_only; + rem.err =3D 0; + rem.current =3D virDomainCheckpointGetCurrent(vm->checkpoints); + rem.found =3D false; + rem.momentDiscard =3D qemuDomainCheckpointDiscard; + virDomainMomentForEachDescendant(chk, qemuDomainMomentDiscardAll, + &rem); + if (rem.err < 0) + goto endjob; + if (rem.found) { + virDomainCheckpointSetCurrent(vm->checkpoints, chk); + if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) { + if (qemuDomainCheckpointWriteMetadata(vm, chk, driver->cap= s, + driver->xmlopt, + cfg->checkpointDir) = < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to set checkpoint '%s' as cur= rent"), + chk->def->name); + virDomainCheckpointSetCurrent(vm->checkpoints, NULL); + goto endjob; + } + } + } + } else if (chk->nchildren) { + rep.dir =3D cfg->checkpointDir; + rep.parent =3D chk->parent; + rep.vm =3D vm; + rep.err =3D 0; + rep.caps =3D driver->caps; + rep.xmlopt =3D driver->xmlopt; + rep.writeMetadata =3D qemuDomainCheckpointWriteMetadata; + virDomainMomentForEachChild(chk, qemuDomainMomentReparentChildren, + &rep); + if (rep.err < 0) + goto endjob; + virDomainMomentMoveChildren(chk, chk->parent); + } + + if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) { + virDomainMomentDropChildren(chk); + ret =3D 0; + } else { + ret =3D qemuDomainCheckpointDiscard(driver, vm, chk, true, metadat= a_only); + } + + endjob: + qemuDomainObjEndJob(driver, vm); + + cleanup: + virDomainObjEndAPI(&vm); + virObjectUnref(cfg); + return ret; +} + static int qemuDomainQemuMonitorCommand(virDomainPtr domain, const char *c= md, char **result, unsigned int flags) { @@ -16866,6 +17570,16 @@ static virDomainPtr qemuDomainQemuAttach(virConnec= tPtr conn, goto cleanup; } + if (qemuProcessAttach(conn, driver, vm, pid, + pidfile, monConfig, monJSON) < 0) { + monConfig =3D NULL; + qemuDomainRemoveInactive(driver, vm); + qemuDomainObjEndJob(driver, vm); + goto cleanup; + } + + monConfig =3D NULL; + if (qemuProcessAttach(conn, driver, vm, pid, pidfile, monConfig, monJSON) < 0) { qemuDomainRemoveInactive(driver, vm); @@ -21712,6 +22426,12 @@ static int qemuDomainRename(virDomainPtr dom, goto endjob; } + if (virDomainListCheckpoints(vm->checkpoints, NULL, dom, NULL, flags) = > 0) { + virReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("cannot rename domain with checkpoints")); + goto endjob; + } + if (virDomainObjListRename(driver->domains, vm, new_name, flags, qemuDomainRenameCallback, driver) < 0) goto endjob; @@ -22532,6 +23252,16 @@ static virHypervisorDriver qemuHypervisorDriver = =3D { .connectBaselineHypervisorCPU =3D qemuConnectBaselineHypervisorCPU, /*= 4.4.0 */ .nodeGetSEVInfo =3D qemuNodeGetSEVInfo, /* 4.5.0 */ .domainGetLaunchSecurityInfo =3D qemuDomainGetLaunchSecurityInfo, /* 4= .5.0 */ + .domainCheckpointCreateXML =3D qemuDomainCheckpointCreateXML, /* 5.5.0= */ + .domainCheckpointGetXMLDesc =3D qemuDomainCheckpointGetXMLDesc, /* 5.5= .0 */ + + .domainListAllCheckpoints =3D qemuDomainListAllCheckpoints, /* 5.5.0 */ + .domainCheckpointListAllChildren =3D qemuDomainCheckpointListAllChildr= en, /* 5.5.0 */ + .domainCheckpointLookupByName =3D qemuDomainCheckpointLookupByName, /*= 5.5.0 */ + .domainCheckpointGetParent =3D qemuDomainCheckpointGetParent, /* 5.5.0= */ + .domainCheckpointCurrent =3D qemuDomainCheckpointCurrent, /* 5.5.0 */ + .domainCheckpointIsCurrent =3D qemuDomainCheckpointIsCurrent, /* 5.5.0= */ + .domainCheckpointDelete =3D qemuDomainCheckpointDelete, /* 5.5.0 */ }; --=20 2.20.1 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list