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 <phrd...@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 <pkre...@redhat.com>