[libvirt PATCH 23/30] qemu_snapshot: prepare data for external snapshot deletion
Peter Krempa
pkrempa at redhat.com
Tue Dec 13 15:47:08 UTC 2022
On Thu, Dec 08, 2022 at 14:30:59 +0100, Pavel Hrdina wrote:
> In order to save some CPU cycles we will collect all the necessary data
> to delete external snapshot before we even start. They will be later
> used by code that deletes the snapshots and updates metadata when
> needed.
>
> With external snapshots we need data that libvirt gets from running QEMU
> process so if the VM is not running we need to start paused QEMU process
> for the snapshot deletion and kill at afterwards.
>
> Signed-off-by: Pavel Hrdina <phrdina at redhat.com>
> ---
> src/qemu/qemu_snapshot.c | 163 +++++++++++++++++++++++++++++++++++++++
> 1 file changed, 163 insertions(+)
>
> diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c
> index 97df448363..882224b0a7 100644
> --- a/src/qemu/qemu_snapshot.c
> +++ b/src/qemu/qemu_snapshot.c
> @@ -2232,6 +2232,134 @@ qemuSnapshotRevert(virDomainObj *vm,
> }
>
>
> +typedef struct _qemuSnapshotDeleteExternalData {
> + virDomainMomentObj *parentSnap;
> + virDomainSnapshotDiskDef *snapDisk;
> + virDomainDiskDef *domDisk;
> + virDomainDiskDef *parentDomDisk;
> + virStorageSource *diskSrc;
> + virStorageSource *parentDiskSrc;
> + virStorageSource *prevDiskSrc;
> + qemuBlockJobData *job;
Please annotate which 'source' belongs to which 'disk def' and such.
You can also theoretically reorder them.
> +} qemuSnapshotDeleteExternalData;
> +
> +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuSnapshotDeleteExternalData, g_free);
> +
> +
> +/**
> + * qemuSnapshotFindParentSnapForDisk:
> + * @snap: snapshot object that is about to be deleted
> + * @snapDisk: snapshot disk definition
> + *
> + * Find parent snapshot object that contains snapshot of the @snapDisk.
> + * It may not be the next snapshot in the snapshot tree as the disk in
> + * question may be skipped by using VIR_DOMAIN_SNAPSHOT_LOCATION_NO.
> + *
> + * Returns virDomainMomentObj* on success or NULL if there is no parent
> + * snapshot containing the @snapDisk.
> + * */
> +static virDomainMomentObj*
> +qemuSnapshotFindParentSnapForDisk(virDomainMomentObj *snap,
> + virDomainSnapshotDiskDef *snapDisk)
> +{
> + virDomainMomentObj *parentSnap = snap->parent;
> +
> + while (parentSnap) {
> + ssize_t i;
> + virDomainSnapshotDef *parentSnapdef = virDomainSnapshotObjGetDef(parentSnap);
> +
> + if (!parentSnapdef)
> + break;
> +
> + for (i = 0; i < parentSnapdef->ndisks; i++) {
> + virDomainSnapshotDiskDef *parentSnapDisk = &(parentSnapdef->disks[i]);
> +
> + if (parentSnapDisk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_NO &&
> + STREQ(snapDisk->name, parentSnapDisk->name)) {
> + return parentSnap;
> + }
> + }
> +
> + parentSnap = parentSnap->parent;
> + }
> +
> + return NULL;
> +}
> +
> +
> +static GSList*
> +qemuSnapshotDeleteExternalPrepare(virDomainObj *vm,
> + virDomainMomentObj *snap)
> +{
> + ssize_t i;
> + virDomainSnapshotDef *snapdef = virDomainSnapshotObjGetDef(snap);
> + g_autoslist(qemuSnapshotDeleteExternalData) ret = NULL;
> +
> + for (i = 0; i < snapdef->ndisks; i++) {
> + g_autofree qemuSnapshotDeleteExternalData *data = NULL;
> + virDomainSnapshotDiskDef *snapDisk = &(snapdef->disks[i]);
> +
> + if (snapDisk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NO)
> + continue;
> +
> + data = g_new0(qemuSnapshotDeleteExternalData, 1);
> + data->snapDisk = snapDisk;
> +
> + data->domDisk = qemuDomainDiskByName(vm->def, snapDisk->name);
> + if (!data->domDisk)
> + return NULL;
> +
> + data->diskSrc = virStorageSourceChainLookup(data->domDisk->src, NULL,
> + data->snapDisk->src->path,
So here you are looking up based on 'src->path' which works properly
really only for file-based/local storage. For anything else it will
break.
Since you have a virStorageSource to compare to you should probably
create a helper which walks the backing chain and uses
virStorageSourceIsSameLocation internally instead virStorageSourceChainLookup
which should really be used only for user-provided data as the third
argument (for backwards compatibility with old-style API args).
> + NULL, &data->prevDiskSrc);
> + if (!data->diskSrc)
> + return NULL;
> +
> + if (!virStorageSourceIsSameLocation(data->diskSrc, data->snapDisk->src)) {
> + virReportError(VIR_ERR_OPERATION_FAILED, "%s",
> + _("VM disk source and snapshot disk source are not the same"));
> + return NULL;
> + }
> +
> + data->parentDomDisk = virDomainDiskByTarget(snapdef->parent.dom,
> + data->snapDisk->name);
> + if (!data->parentDomDisk) {
> + virReportError(VIR_ERR_OPERATION_FAILED,
> + _("failed to find disk '%s' in snapshot VM XML"),
> + snapDisk->name);
> + return NULL;
> + }
> +
> + if (virDomainObjIsActive(vm)) {
> + data->parentDiskSrc = data->diskSrc->backingStore;
> + if (!data->parentDiskSrc) {
Note that active definitions contain also a "terminator"
virStorageSource put into the 'backingStore' object, which is a valid
pointer. You probably want to use 'virStorageSourceIsBacking' here.
> + virReportError(VIR_ERR_OPERATION_FAILED, "%s",
> + _("failed to find parent disk source in backing chain"));
> + return NULL;
> + }
> +
> + if (!virStorageSourceIsSameLocation(data->parentDiskSrc, data->parentDomDisk->src)) {
> + virReportError(VIR_ERR_OPERATION_FAILED, "%s",
> + _("snapshot VM disk source and parent disk source are not the same"));
> + return NULL;
> + }
> + }
> +
> + data->parentSnap = qemuSnapshotFindParentSnapForDisk(snap, data->snapDisk);
> +
> + if (data->parentSnap && !virDomainSnapshotIsExternal(data->parentSnap)) {
> + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
> + _("deleting external snapshot that has internal snapshot as parent not supported"));
> + return NULL;
> + }
> +
> + ret = g_slist_append(ret, g_steal_pointer(&data));
The usual way to work with singly-linked lists is to use the prepend
operation ...
> + }
> +
... and reverse it before returning.
> + return g_steal_pointer(&ret);
> +}
> +
> +
> typedef struct _virQEMUMomentReparent virQEMUMomentReparent;
> struct _virQEMUMomentReparent {
> const char *dir;
> @@ -2565,6 +2693,10 @@ qemuSnapshotDelete(virDomainObj *vm,
> int ret = -1;
> virDomainMomentObj *snap = NULL;
> bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY);
> + bool stop_qemu = false;
> + qemuDomainObjPrivate *priv = vm->privateData;
> + virQEMUDriver *driver = priv->driver;
> + g_autoslist(qemuSnapshotDeleteExternalData) externalData = NULL;
>
> virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY |
> @@ -2582,6 +2714,32 @@ qemuSnapshotDelete(virDomainObj *vm,
> if (!metadata_only) {
> if (qemuSnapshotDeleteValidate(vm, snap, flags) < 0)
> goto endjob;
> +
> + if (virDomainSnapshotIsExternal(snap) &&
> + !(flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN |
> + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY))) {
> +
> + externalData = qemuSnapshotDeleteExternalPrepare(vm, snap);
> +
> + if (!virDomainObjIsActive(vm)) {
> + if (qemuProcessStart(NULL, driver, vm, NULL, VIR_ASYNC_JOB_SNAPSHOT,
> + NULL, -1, NULL, NULL,
> + VIR_NETDEV_VPORT_PROFILE_OP_CREATE,
> + VIR_QEMU_PROCESS_START_PAUSED) < 0) {
> + goto endjob;
> + }
> +
> + stop_qemu = true;
> +
> + /* Call the prepare again as some data require that the VM is
> + * running to get everything we need. */
> + g_slist_free_full(externalData, g_free);
> + externalData = qemuSnapshotDeleteExternalPrepare(vm, snap);
Preferrably don't reuse 'externalData'. You can e.g. instead steal the
value into a locally scoped temporary autofreed variable and then
re-create it.
> + }
> +
> + if (!externalData)
> + goto endjob;
> + }
If you deal with the lookup properly you can add:
Reviewed-by: Peter Krempa <pkrempa at redhat.com>
More information about the libvir-list
mailing list