[libvirt] [PATCH 0/6] snapshot work - don't strand snapshot metadata

Maps pretty well to my RFC email trail, as amended with followups: https://www.redhat.com/archives/libvir-list/2011-August/msg00452.html The qemu_driver diffstat is deceptively large; there's some code motion thrown in on 3/6. This series requires acks on the earlier one: https://www.redhat.com/archives/libvir-list/2011-August/msg00493.html Eric Blake (6): snapshot: identify which snapshots have metadata snapshot: prevent stranding snapshot data on domain destruction snapshot: refactor some qemu code snapshot: cache qemu-img location snapshot: support new undefine flags in qemu snapshot: teach virsh about new undefine flags include/libvirt/libvirt.h.in | 29 +++- src/esx/esx_driver.c | 17 ++- src/libvirt.c | 58 +++++- src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 336 +++++++++++++++++++++-------------- src/vbox/vbox_tmpl.c | 19 ++- tools/virsh.c | 400 +++++++++++++++++++++++++++++++++++++----- tools/virsh.pod | 36 ++++- 8 files changed, 691 insertions(+), 205 deletions(-) -- 1.7.4.4

To make it easier to know when undefine will fail because of existing snapshot metadata, we need to know how many snapshots have metadata. * include/libvirt/libvirt.h.in (VIR_DOMAIN_SNAPSHOT_NUM_METADATA): New flag. * src/libvirt.c (virDomainSnapshotNum): Document it. * src/esx/esx_driver.c (esxDomainSnapshotNum): Implement it. * src/vbox/vbox_tmpl.c (vboxDomainSnapshotNum): Likewise. * src/qemu/qemu_driver.c (qemuDomainSnapshotNum): Likewise. --- include/libvirt/libvirt.h.in | 4 ++++ src/esx/esx_driver.c | 6 +++++- src/libvirt.c | 11 ++++++++--- src/qemu/qemu_driver.c | 6 +++++- src/vbox/vbox_tmpl.c | 8 +++++++- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index eae0a10..c672145 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2551,6 +2551,10 @@ virDomainSnapshotPtr virDomainSnapshotCreateXML(virDomainPtr domain, char *virDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, unsigned int flags); +typedef enum { + VIR_DOMAIN_SNAPSHOT_NUM_METADATA = (1 << 0), /* Snapshots which have metadata */ +} virDomainSnapshotNumFlags; + /* Return the number of snapshots for this domain */ int virDomainSnapshotNum(virDomainPtr domain, unsigned int flags); diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index beeafbd..837b774 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4329,12 +4329,16 @@ esxDomainSnapshotNum(virDomainPtr domain, unsigned int flags) esxPrivate *priv = domain->conn->privateData; esxVI_VirtualMachineSnapshotTree *rootSnapshotTreeList = NULL; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_NUM_METADATA, -1); if (esxVI_EnsureSession(priv->primary) < 0) { return -1; } + /* ESX snapshots do not require libvirt to maintain any metadata. */ + if (flags & VIR_DOMAIN_SNAPSHOT_NUM_METADATA) + return 0; + if (esxVI_LookupRootSnapshotTreeList(priv->primary, domain->uuid, &rootSnapshotTreeList) < 0) { return -1; diff --git a/src/libvirt.c b/src/libvirt.c index 8ee9e96..3e031b6 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15526,11 +15526,16 @@ error: /** * virDomainSnapshotNum: * @domain: a domain object - * @flags: unused flag parameters; callers should pass 0 + * @flags: bitwise-or of supported virDomainSnapshotNumFlags + * + * Provides the number of domain snapshots for this domain. * - * Provides the number of domain snapshots for this domain.. + * If @flags includes VIR_DOMAIN_SNAPSHOT_NUM_METADATA, then the result is + * the number of snapshots that also include metadata that would prevent + * the removal of the last reference to a domain; this value will either + * be 0 or the same value as if @flags had been 0. * - * Returns the number of domain snapshost found or -1 in case of error. + * Returns the number of domain snapshots found or -1 in case of error. */ int virDomainSnapshotNum(virDomainPtr domain, unsigned int flags) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index c5b3965..ffea714 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8577,7 +8577,7 @@ static int qemuDomainSnapshotNum(virDomainPtr domain, virDomainObjPtr vm = NULL; int n = -1; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_NUM_METADATA, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, domain->uuid); @@ -8589,6 +8589,10 @@ static int qemuDomainSnapshotNum(virDomainPtr domain, goto cleanup; } + /* All qemu snapshots have libvirt metadata, so + * VIR_DOMAIN_SNAPSHOT_NUM_METADATA makes no difference to our + * answer. */ + n = virDomainSnapshotObjListNum(&vm->snapshots); cleanup: diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 8de2bae..fa19aaa 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -5861,7 +5861,7 @@ vboxDomainSnapshotNum(virDomainPtr dom, nsresult rc; PRUint32 snapshotCount; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_NUM_METADATA, -1); vboxIIDFromUUID(&iid, dom->uuid); rc = VBOX_OBJECT_GET_MACHINE(iid.value, &machine); @@ -5871,6 +5871,12 @@ vboxDomainSnapshotNum(virDomainPtr dom, goto cleanup; } + /* VBox snapshots do not require libvirt to maintain any metadata. */ + if (flags & VIR_DOMAIN_SNAPSHOT_NUM_METADATA) { + ret = 0; + goto cleanup; + } + rc = machine->vtbl->GetSnapshotCount(machine, &snapshotCount); if (NS_FAILED(rc)) { vboxError(VIR_ERR_INTERNAL_ERROR, -- 1.7.4.4

Just as leaving managed save metadata behind can cause problems when creating a new domain that happens to collide with the name of the just-deleted domain, the same is true of leaving any snapshot metadata behind. For safety sake, extend the semantic change of commit b26a9fa9 to also cover snapshot metadata as a reason to reject losing the last reference to a domain (undefine on an inactive, or shutdown/destroy on a transient). The caller must first take care of snapshots, possible via the existing virDomainSnapshotDelete. This also documents some new flags that hypervisors can choose to support to shortcuts taking care of the metadata as part of the shutdown process; however, nontrivial driver support for these flags will be deferred to future patches. Note that ESX and VBox can never be transient; therefore, they do not have to affect shutdown/destroy (the persistent domain still remains); likewise they never store snapshot metadata, so one of the two flags is trivial. The bulk of the nontrivial work remaining is thus in the qemu driver. * include/libvirt/libvirt.h.in (VIR_DOMAIN_UNDEFINE_SNAPSHOTS) (VIR_DOMAIN_DESTROY_SNAPSHOTS): New flags. * src/libvirt.c (virDomainUndefine, virDomainUndefineFlags) (virDomainDestroy, virDomainDestroyFlags, virDomainShutdown): Document new limitations and flags. * src/esx/esx_driver.c (esxDomainUndefineFlags): Enforce the limitations. * src/vbox/vbox_tmpl.c (vboxDomainUndefineFlags): Likewise. * src/qemu/qemu_driver.c (qemuDomainUndefineFlags) (qemuDomainShutdown, qemuDomainDestroyFlags): Likewise. --- include/libvirt/libvirt.h.in | 25 ++++++++++++++++++--- src/esx/esx_driver.c | 11 ++++++++- src/libvirt.c | 47 +++++++++++++++++++++++++++++++++++------ src/qemu/qemu_driver.c | 26 +++++++++++++++++++++++ src/vbox/vbox_tmpl.c | 11 ++++++++- 5 files changed, 105 insertions(+), 15 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index c672145..e6509ec 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -919,10 +919,20 @@ virConnectPtr virDomainGetConnect (virDomainPtr domain); * Domain creation and destruction */ -/* - * typedef enum { - * } virDomainDestroyFlagsValues; + +/* Counterparts to virDomainUndefineFlagsValues, but note that running + * domains have no managed save data, so no flag is provided for that. */ +typedef enum { + /* VIR_DOMAIN_DESTROY_MANAGED_SAVE = (1 << 0), */ /* Reserved */ + VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA = (1 << 1), /* If last use of domain, + then also remove any + snapshot metadata */ + VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL = (1 << 2), /* If last use of domain, + then also remove any + snapshot data */ +} virDomainDestroyFlagsValues; + virDomainPtr virDomainCreateXML (virConnectPtr conn, const char *xmlDesc, unsigned int flags); @@ -1240,7 +1250,14 @@ virDomainPtr virDomainDefineXML (virConnectPtr conn, int virDomainUndefine (virDomainPtr domain); typedef enum { - VIR_DOMAIN_UNDEFINE_MANAGED_SAVE = (1 << 0), + VIR_DOMAIN_UNDEFINE_MANAGED_SAVE = (1 << 0), /* Also remove any + managed save */ + VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA = (1 << 1), /* If last use of domain, + then also remove any + snapshot metadata */ + VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL = (1 << 2), /* If last use of domain, + then also remove any + snapshot data */ /* Future undefine control flags should come here. */ } virDomainUndefineFlagsValues; diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index 837b774..c71b1b3 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -1950,7 +1950,9 @@ esxDomainDestroyFlags(virDomainPtr domain, esxVI_TaskInfoState taskInfoState; char *taskInfoErrorMessage = NULL; - virCheckFlags(0, -1); + /* No transient domains, so these flags are trivially ignored. */ + virCheckFlags(VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA | + VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL, -1); if (priv->vCenter != NULL) { ctx = priv->vCenter; @@ -3309,7 +3311,9 @@ esxDomainUndefineFlags(virDomainPtr domain, esxVI_String *propertyNameList = NULL; esxVI_VirtualMachinePowerState powerState; - virCheckFlags(0, -1); + /* No managed save, so we explicitly reject + * VIR_DOMAIN_UNDEFINE_MANAGED_SAVE. */ + virCheckFlags(VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA, -1); if (priv->vCenter != NULL) { ctx = priv->vCenter; @@ -3337,6 +3341,9 @@ esxDomainUndefineFlags(virDomainPtr domain, goto cleanup; } + /* ESX snapshots maintain no metadata, so we can trivially ignore + * that flag. XXX Wire up VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL. */ + if (esxVI_UnregisterVM(ctx, virtualMachine->obj) < 0) { goto cleanup; } diff --git a/src/libvirt.c b/src/libvirt.c index 3e031b6..deb95b2 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -2039,6 +2039,10 @@ error: * does not free the associated virDomainPtr object. * This function may require privileged access * + * If the domain is transient and has any snapshot metadata (see + * virDomainSnapshotNum()), then the destroy will fail. See + * virDomainDestroyFlags() for more control. + * * Returns 0 in case of success and -1 in case of failure. */ int @@ -2088,7 +2092,20 @@ error: * This function may require privileged access. * * Calling this function with no @flags set (equal to zero) - * is equivalent to calling virDomainDestroy. + * is equivalent to calling virDomainDestroy. Using virDomainShutdown() + * may produce cleaner results for the guest's disks, but depends on guest + * support. + * + * If the domain is transient and has any snapshot metadata (see + * virDomainSnapshotNum()), then including + * VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA in @flags will also remove + * that metadata, including VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL will + * remove the metadata and snapshot data contents, and omitting both + * of these flags will cause the destroy process to fail. + * VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA and + * VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL have no effect if the domain is + * persistent, and VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA has no effect + * if libvirt does not maintain snapshot metadata. * * Returns 0 in case of success and -1 in case of failure. */ @@ -2856,12 +2873,16 @@ error: * virDomainShutdown: * @domain: a domain object * - * Shutdown a domain, the domain object is still usable there after but + * Shutdown a domain, the domain object is still usable thereafter but * the domain OS is being stopped. Note that the guest OS may ignore the - * request. + * request. For guests that react to a shutdown request, the differences + * from virDomainDestroy() are that the guests disk storage will be in a + * stable state rather than having the (virtual) power cord pulled, and + * this command returns as soon as the shutdown request is issued rather + * than blocking until the guest is no longer running. * - * TODO: should we add an option for reboot, knowing it may not be doable - * in the general case ? + * If the domain is transient and has any snapshot metadata (see + * virDomainSnapshotNum()), then the shutdown will fail. * * Returns 0 in case of success and -1 in case of failure. */ @@ -6808,8 +6829,9 @@ error: * the domain configuration is removed. * * If the domain has a managed save image (see - * virDomainHasManagedSaveImage()), then the undefine will fail. See - * virDomainUndefineFlags() for more control. + * virDomainHasManagedSaveImage()), or if the domain is active and has any + * snapshot metadata (see virDomainSnapshotNum()), then the undefine will + * fail. See virDomainUndefineFlags() for more control. * * Returns 0 in case of success, -1 in case of error */ @@ -6860,6 +6882,17 @@ error: * then including VIR_DOMAIN_UNDEFINE_MANAGED_SAVE in @flags will also remove * that file, and omitting the flag will cause the undefine process to fail. * + * If the domain is inactive and has any snapshot metadata (see + * virDomainSnapshotNum()), then including + * VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA in @flags will also remove + * that metadata, including VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL will + * remove the metadata and snapshot data contents, and omitting both + * of these flags will cause the undefine process to fail. + * VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA and + * VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL have no effect if the domain is + * active, and VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA has no effect if + * libvirt does not maintain snapshot metadata. + * * Returns 0 in case of success, -1 in case of error */ int diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ffea714..678ac1b 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1466,6 +1466,7 @@ static int qemuDomainShutdown(virDomainPtr dom) { virDomainObjPtr vm; int ret = -1; qemuDomainObjPrivatePtr priv; + int nsnapshots; qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -1479,6 +1480,14 @@ static int qemuDomainShutdown(virDomainPtr dom) { goto cleanup; } + if (!vm->persistent && + (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete transient domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; @@ -1574,6 +1583,7 @@ qemuDomainDestroyFlags(virDomainPtr dom, int ret = -1; virDomainEventPtr event = NULL; qemuDomainObjPrivatePtr priv; + int nsnapshots; virCheckFlags(0, -1); @@ -1587,6 +1597,14 @@ qemuDomainDestroyFlags(virDomainPtr dom, goto cleanup; } + if (!vm->persistent && + (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete transient domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + priv = vm->privateData; priv->fakeReboot = false; @@ -4706,6 +4724,7 @@ qemuDomainUndefineFlags(virDomainPtr dom, virDomainEventPtr event = NULL; char *name = NULL; int ret = -1; + int nsnapshots; virCheckFlags(VIR_DOMAIN_UNDEFINE_MANAGED_SAVE, -1); @@ -4720,10 +4739,17 @@ qemuDomainUndefineFlags(virDomainPtr dom, goto cleanup; } + /* XXX We should allow undefine to convert running persistent into + * transient domain, rather than reject things here. */ if (virDomainObjIsActive(vm)) { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot delete active domain")); goto cleanup; + } else if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete inactive domain with %d snapshots"), + nsnapshots); + goto cleanup; } if (!vm->persistent) { diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index fa19aaa..91a5423 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -1701,7 +1701,9 @@ vboxDomainDestroyFlags(virDomainPtr dom, PRBool isAccessible = PR_FALSE; nsresult rc; - virCheckFlags(0, -1); + /* No transient domains, so these flags are trivially ignored. */ + virCheckFlags(VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA | + VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL, -1); vboxIIDFromUUID(&iid, dom->uuid); rc = VBOX_OBJECT_GET_MACHINE(iid.value, &machine); @@ -4982,10 +4984,15 @@ vboxDomainUndefineFlags(virDomainPtr dom, unsigned int flags) #if VBOX_API_VERSION >= 4000 vboxArray media = VBOX_ARRAY_INITIALIZER; #endif - virCheckFlags(0, -1); + /* No managed save, so we explicitly reject + * VIR_DOMAIN_UNDEFINE_MANAGED_SAVE. */ + virCheckFlags(VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA, -1); vboxIIDFromUUID(&iid, dom->uuid); + /* VBox snapshots maintain no metadata, so we can trivially ignore + * that flag. XXX Wire up VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL. */ + #if VBOX_API_VERSION < 4000 /* Block for checking if HDD's are attched to VM. * considering just IDE bus for now. Also skipped -- 1.7.4.4

Prepare for code sharing. No semantic change. * src/qemu/qemu_driver.c (qemuFindQemuImgBinary) (qemuDomainSnapshotDiscard): Float up. (qemuDomainSnapshotDiscardDescendant): Likewise, and rename... (qemuDomainSnapshotDiscardAll): ...for generic use. (qemuDomainSnapshotDelete): Update caller. --- src/qemu/qemu_driver.c | 253 ++++++++++++++++++++++++----------------------- 1 files changed, 129 insertions(+), 124 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 678ac1b..d9e88fe 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1574,6 +1574,134 @@ cleanup: } +struct snap_remove { + struct qemud_driver *driver; + virDomainObjPtr vm; + bool metadata_only; + int err; +}; + +/* Locate an appropriate 'qemu-img' binary. */ +static char * +qemuFindQemuImgBinary(void) +{ + char *ret; + + ret = virFindFileInPath("kvm-img"); + if (ret == NULL) + ret = virFindFileInPath("qemu-img"); + if (ret == NULL) + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find kvm-img or qemu-img")); + + return ret; +} + +/* Discard one snapshot (or its metadata), without reparenting any children. */ +static int +qemuDomainSnapshotDiscard(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap, + bool metadata_only) +{ + const char *qemuimgarg[] = { NULL, "snapshot", "-d", NULL, NULL, NULL }; + char *snapFile = NULL; + int ret = -1; + int i; + qemuDomainObjPrivatePtr priv; + virDomainSnapshotObjPtr parentsnap; + + if (!metadata_only) { + if (!virDomainObjIsActive(vm)) { + qemuimgarg[0] = qemuFindQemuImgBinary(); + if (qemuimgarg[0] == NULL) + /* qemuFindQemuImgBinary set the error */ + goto cleanup; + + qemuimgarg[3] = snap->def->name; + + for (i = 0; i < vm->def->ndisks; i++) { + /* FIXME: we also need to handle LVM here */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + /* we continue on even in the face of error, since other + * disks in this VM may have this snapshot in place + */ + continue; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) { + /* we continue on even in the face of error, since other + * disks in this VM may have this snapshot in place + */ + continue; + } + } + } + } else { + priv = vm->privateData; + qemuDomainObjEnterMonitorWithDriver(driver, vm); + /* we continue on even in the face of error */ + qemuMonitorDeleteSnapshot(priv->mon, snap->def->name); + qemuDomainObjExitMonitorWithDriver(driver, vm); + } + } + + if (snap == vm->current_snapshot) { + if (snap->def->parent) { + parentsnap = virDomainSnapshotFindByName(&vm->snapshots, + snap->def->parent); + if (!parentsnap) { + qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, + _("no domain snapshot parent with matching name '%s'"), + snap->def->parent); + goto cleanup; + } + + /* Now we set the new current_snapshot for the domain */ + vm->current_snapshot = parentsnap; + } else { + vm->current_snapshot = NULL; + } + } + + if (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, + vm->def->name, snap->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + unlink(snapFile); + + virDomainSnapshotObjListRemove(&vm->snapshots, snap); + + ret = 0; + +cleanup: + VIR_FREE(snapFile); + VIR_FREE(qemuimgarg[0]); + + return ret; +} + +/* Hash iterator callback to discard multiple snapshots. */ +static void +qemuDomainSnapshotDiscardAll(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr snap = payload; + struct snap_remove *curr = data; + int err; + + err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap, + curr->metadata_only); + if (err && !curr->err) + curr->err = err; +} + static int qemuDomainDestroyFlags(virDomainPtr dom, unsigned int flags) @@ -8272,20 +8400,6 @@ cleanup: return ret; } -static char *qemuFindQemuImgBinary(void) -{ - char *ret; - - ret = virFindFileInPath("kvm-img"); - if (ret == NULL) - ret = virFindFileInPath("qemu-img"); - if (ret == NULL) - qemuReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("unable to find kvm-img or qemu-img")); - - return ret; -} - static int qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, virDomainSnapshotObjPtr snapshot, char *snapshotDir) @@ -8951,115 +9065,6 @@ cleanup: return ret; } -static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, - virDomainObjPtr vm, - virDomainSnapshotObjPtr snap, - bool metadata_only) -{ - const char *qemuimgarg[] = { NULL, "snapshot", "-d", NULL, NULL, NULL }; - char *snapFile = NULL; - int ret = -1; - int i; - qemuDomainObjPrivatePtr priv; - virDomainSnapshotObjPtr parentsnap; - - if (!metadata_only) { - if (!virDomainObjIsActive(vm)) { - qemuimgarg[0] = qemuFindQemuImgBinary(); - if (qemuimgarg[0] == NULL) - /* qemuFindQemuImgBinary set the error */ - goto cleanup; - - qemuimgarg[3] = snap->def->name; - - for (i = 0; i < vm->def->ndisks; i++) { - /* FIXME: we also need to handle LVM here */ - if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { - if (!vm->def->disks[i]->driverType || - STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { - /* we continue on even in the face of error, since other - * disks in this VM may have this snapshot in place - */ - continue; - } - - qemuimgarg[4] = vm->def->disks[i]->src; - - if (virRun(qemuimgarg, NULL) < 0) { - /* we continue on even in the face of error, since other - * disks in this VM may have this snapshot in place - */ - continue; - } - } - } - } else { - priv = vm->privateData; - qemuDomainObjEnterMonitorWithDriver(driver, vm); - /* we continue on even in the face of error */ - qemuMonitorDeleteSnapshot(priv->mon, snap->def->name); - qemuDomainObjExitMonitorWithDriver(driver, vm); - } - } - - if (snap == vm->current_snapshot) { - if (snap->def->parent) { - parentsnap = virDomainSnapshotFindByName(&vm->snapshots, - snap->def->parent); - if (!parentsnap) { - qemuReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, - _("no domain snapshot parent with matching name '%s'"), - snap->def->parent); - goto cleanup; - } - - /* Now we set the new current_snapshot for the domain */ - vm->current_snapshot = parentsnap; - } else { - vm->current_snapshot = NULL; - } - } - - if (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, - vm->def->name, snap->def->name) < 0) { - virReportOOMError(); - goto cleanup; - } - unlink(snapFile); - - virDomainSnapshotObjListRemove(&vm->snapshots, snap); - - ret = 0; - -cleanup: - VIR_FREE(snapFile); - VIR_FREE(qemuimgarg[0]); - - return ret; -} - -struct snap_remove { - struct qemud_driver *driver; - virDomainObjPtr vm; - bool metadata_only; - int err; -}; - -static void -qemuDomainSnapshotDiscardDescendant(void *payload, - const void *name ATTRIBUTE_UNUSED, - void *data) -{ - virDomainSnapshotObjPtr snap = payload; - struct snap_remove *curr = data; - int err; - - err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap, - curr->metadata_only); - if (err && !curr->err) - curr->err = err; -} - struct snap_reparent { struct qemud_driver *driver; virDomainSnapshotObjPtr snap; @@ -9139,7 +9144,7 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, rem.err = 0; virDomainSnapshotForEachDescendant(&vm->snapshots, snap, - qemuDomainSnapshotDiscardDescendant, + qemuDomainSnapshotDiscardAll, &rem); if (rem.err < 0) goto endjob; -- 1.7.4.4

As more clients start to want to know this information, doing a PATH stat walk and malloc for every client adds up. * src/qemu/qemu_conf.h (qemud_driver): Add member. * src/qemu/qemu_driver.c (qemudShutdown): Cleanup. (qemuFindQemuImgBinary): Add an argument, and cache result. (qemuDomainSnapshotDiscard, qemuDomainSnapshotCreateInactive) (qemuDomainSnapshotRevertInactive, qemuDomainSnapshotCreateXML) (qemuDomainRevertToSnapshot): Update callers. --- src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 42 +++++++++++++++++++++--------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 0a60d32..5469a63 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -82,6 +82,7 @@ struct qemud_driver { char *cacheDir; char *saveDir; char *snapshotDir; + char *qemuImgBinary; unsigned int vncAutoUnixSocket : 1; unsigned int vncTLS : 1; unsigned int vncTLSx509verify : 1; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d9e88fe..08d5a27 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -762,6 +762,7 @@ qemudShutdown(void) { VIR_FREE(qemu_driver->cacheDir); VIR_FREE(qemu_driver->saveDir); VIR_FREE(qemu_driver->snapshotDir); + VIR_FREE(qemu_driver->qemuImgBinary); VIR_FREE(qemu_driver->autoDumpPath); VIR_FREE(qemu_driver->vncTLSx509certdir); VIR_FREE(qemu_driver->vncListen); @@ -1582,19 +1583,19 @@ struct snap_remove { }; /* Locate an appropriate 'qemu-img' binary. */ -static char * -qemuFindQemuImgBinary(void) +static const char * +qemuFindQemuImgBinary(struct qemud_driver *driver) { - char *ret; - - ret = virFindFileInPath("kvm-img"); - if (ret == NULL) - ret = virFindFileInPath("qemu-img"); - if (ret == NULL) - qemuReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("unable to find kvm-img or qemu-img")); + if (!driver->qemuImgBinary) { + driver->qemuImgBinary = virFindFileInPath("kvm-img"); + if (!driver->qemuImgBinary) + driver->qemuImgBinary = virFindFileInPath("qemu-img"); + if (!driver->qemuImgBinary) + qemuReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find kvm-img or qemu-img")); + } - return ret; + return driver->qemuImgBinary; } /* Discard one snapshot (or its metadata), without reparenting any children. */ @@ -1613,7 +1614,7 @@ qemuDomainSnapshotDiscard(struct qemud_driver *driver, if (!metadata_only) { if (!virDomainObjIsActive(vm)) { - qemuimgarg[0] = qemuFindQemuImgBinary(); + qemuimgarg[0] = qemuFindQemuImgBinary(driver); if (qemuimgarg[0] == NULL) /* qemuFindQemuImgBinary set the error */ goto cleanup; @@ -1681,7 +1682,6 @@ qemuDomainSnapshotDiscard(struct qemud_driver *driver, cleanup: VIR_FREE(snapFile); - VIR_FREE(qemuimgarg[0]); return ret; } @@ -8505,14 +8505,15 @@ static int qemuDomainSnapshotIsAllowed(virDomainObjPtr vm) /* The domain is expected to be locked and inactive. */ static int -qemuDomainSnapshotCreateInactive(virDomainObjPtr vm, +qemuDomainSnapshotCreateInactive(struct qemud_driver *driver, + virDomainObjPtr vm, virDomainSnapshotObjPtr snap) { const char *qemuimgarg[] = { NULL, "snapshot", "-c", NULL, NULL, NULL }; int ret = -1; int i; - qemuimgarg[0] = qemuFindQemuImgBinary(); + qemuimgarg[0] = qemuFindQemuImgBinary(driver); if (qemuimgarg[0] == NULL) { /* qemuFindQemuImgBinary set the error */ goto cleanup; @@ -8545,7 +8546,6 @@ qemuDomainSnapshotCreateInactive(virDomainObjPtr vm, ret = 0; cleanup: - VIR_FREE(qemuimgarg[0]); return ret; } @@ -8643,7 +8643,7 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, /* actually do the snapshot */ if (!virDomainObjIsActive(vm)) { - if (qemuDomainSnapshotCreateInactive(vm, snap) < 0) + if (qemuDomainSnapshotCreateInactive(driver, vm, snap) < 0) goto cleanup; } else { if (qemuDomainSnapshotCreateActive(domain->conn, driver, @@ -8880,14 +8880,15 @@ cleanup: /* The domain is expected to be locked and inactive. */ static int -qemuDomainSnapshotRevertInactive(virDomainObjPtr vm, +qemuDomainSnapshotRevertInactive(struct qemud_driver *driver, + virDomainObjPtr vm, virDomainSnapshotObjPtr snap) { const char *qemuimgarg[] = { NULL, "snapshot", "-a", NULL, NULL, NULL }; int ret = -1; int i; - qemuimgarg[0] = qemuFindQemuImgBinary(); + qemuimgarg[0] = qemuFindQemuImgBinary(driver); if (qemuimgarg[0] == NULL) { /* qemuFindQemuImgBinary set the error */ goto cleanup; @@ -8920,7 +8921,6 @@ qemuDomainSnapshotRevertInactive(virDomainObjPtr vm, ret = 0; cleanup: - VIR_FREE(qemuimgarg[0]); return ret; } @@ -9045,7 +9045,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, } } - if (qemuDomainSnapshotRevertInactive(vm, snap) < 0) + if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) goto endjob; } -- 1.7.4.4

A nice benefit of deleting all snapshots at undefine time is that you don't have to do any reparenting or subtree identification - since everything goes, this is an O(n) process whereas using multiple virDomainSnapshotDelete calls would be O(n^2) or worse. * src/qemu/qemu_driver.c (qemuDomainDestroyFlags) (qemuDomainUndefineFlags): Honor new flags. --- src/qemu/qemu_driver.c | 51 ++++++++++++++++++++++++++++++++++++++--------- 1 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 08d5a27..bf82df4 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1713,7 +1713,8 @@ qemuDomainDestroyFlags(virDomainPtr dom, qemuDomainObjPrivatePtr priv; int nsnapshots; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA | + VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -1727,10 +1728,24 @@ qemuDomainDestroyFlags(virDomainPtr dom, if (!vm->persistent && (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - _("cannot delete transient domain with %d snapshots"), - nsnapshots); - goto cleanup; + struct snap_remove rem; + + if ((flags & (VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA | + VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL)) == 0) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete transient domain with %d " + "snapshots"), + nsnapshots); + goto cleanup; + } + + rem.driver = driver; + rem.vm = vm; + rem.metadata_only = !(flags & VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL); + rem.err = 0; + virHashForEach(vm->snapshots.objs, qemuDomainSnapshotDiscardAll, &rem); + if (rem.err < 0) + goto cleanup; } priv = vm->privateData; @@ -4854,7 +4869,9 @@ qemuDomainUndefineFlags(virDomainPtr dom, int ret = -1; int nsnapshots; - virCheckFlags(VIR_DOMAIN_UNDEFINE_MANAGED_SAVE, -1); + virCheckFlags(VIR_DOMAIN_UNDEFINE_MANAGED_SAVE | + VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA | + VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -4874,10 +4891,24 @@ qemuDomainUndefineFlags(virDomainPtr dom, "%s", _("cannot delete active domain")); goto cleanup; } else if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - _("cannot delete inactive domain with %d snapshots"), - nsnapshots); - goto cleanup; + struct snap_remove rem; + + if ((flags & (VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA | + VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL)) == 0) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete inactive domain with %d " + "snapshots"), + nsnapshots); + goto cleanup; + } + + rem.driver = driver; + rem.vm = vm; + rem.metadata_only = !(flags & VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL); + rem.err = 0; + virHashForEach(vm->snapshots.objs, qemuDomainSnapshotDiscardAll, &rem); + if (rem.err < 0) + goto cleanup; } if (!vm->persistent) { -- 1.7.4.4

Similar to 'undefine --managed-save' (commit 83e849c1), we must assume that the old API is unsafe, and emulate it ourselves. Additionally, we have the wrinkle that while virDomainUndefineFlags and managed save cleanup were introduced in 0.9.4, it wasn't until 0.9.5 that snapshots block undefine of a domain. Simulate as much as possible with older servers, but the --snapshots-metadata support requires a server >= 0.9.5. Same story for virDomainDestroyFlags, and virDomainShutdownFlags doesn't exist yet. Oh well. * tools/virsh.c (cmdUndefine, cmdDestroy, cmdShutdown): Add --snapshots-full and --snapshots-metadata flags. (vshRemoveAllSnapshots): New helper method. * tools/virsh.pod (undefine, destroy, shutdown): Document them. --- tools/virsh.c | 400 ++++++++++++++++++++++++++++++++++++++++++++++++------- tools/virsh.pod | 36 +++++- 2 files changed, 386 insertions(+), 50 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index c094911..7275e4c 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1410,6 +1410,59 @@ cmdDefine(vshControl *ctl, const vshCmd *cmd) return ret; } +/* Helper for undefine, shutdown, and destroy */ +static int +vshRemoveAllSnapshots(virDomainPtr dom, int nsnapshots, bool full) +{ + int ret = -1; + char **names; + int actual; + int i; + virDomainSnapshotPtr snapshot = NULL; + int flags = 0; + + /* It's likely that if virDomainUndefineFlags was unsupported to + * get us here in the first place, then + * VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY will also be + * unsupported. But better to hear that from the driver. + */ + if (!full) + flags |= VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY; + + if (VIR_ALLOC_N(names, nsnapshots) < 0) + goto cleanup; + + actual = virDomainSnapshotListNames(dom, names, nsnapshots, 0); + if (actual < 0) + goto cleanup; + + /* Sadly, this is an O(n) loop over a function call that is also a + * minimum of O(n), for a complexity of O(n^2), whereas newer + * servers that support the delete in the undefine action are + * O(n). Oh well. */ + for (i = 0; i < actual; i++) { + if (snapshot) + virDomainSnapshotFree(snapshot); + + snapshot = virDomainSnapshotLookupByName(dom, names[i], 0); + if (snapshot == NULL) + continue; + + if (virDomainSnapshotDelete(snapshot, flags) < 0) + goto cleanup; + } + + ret = 0; + +cleanup: + if (snapshot) + virDomainSnapshotFree(snapshot); + for (i = 0; i < actual; i++) + VIR_FREE(names[i]); + VIR_FREE(names); + return ret; +} + /* * "undefine" command */ @@ -1422,6 +1475,10 @@ static const vshCmdInfo info_undefine[] = { static const vshCmdOptDef opts_undefine[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name or uuid")}, {"managed-save", VSH_OT_BOOL, 0, N_("remove domain managed state file")}, + {"snapshots-metadata", VSH_OT_BOOL, 0, + N_("remove all domain snapshot metadata, if inactive")}, + {"snapshots-full", VSH_OT_BOOL, 0, + N_("remove all domain snapshot contents, if inactive")}, {NULL, 0, 0, NULL} }; @@ -1429,18 +1486,36 @@ static bool cmdUndefine(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom; - bool ret = true; + bool ret = false; const char *name = NULL; + /* Flags to attempt. */ int flags = 0; - int managed_save = vshCommandOptBool(cmd, "managed-save"); + /* User-requested actions. */ + bool managed_save = vshCommandOptBool(cmd, "managed-save"); + bool snapshots_metadata = vshCommandOptBool(cmd, "snapshots-metadata"); + bool snapshots_full = vshCommandOptBool(cmd, "snapshots-full"); + /* Positive if these items exist. */ int has_managed_save = 0; + int has_snapshots_metadata = 0; + int has_snapshots = 0; + /* True if undefine will not strand data, even on older servers. */ + bool managed_save_safe = false; + bool snapshots_safe = false; int rc = -1; + int running; - if (managed_save) + if (managed_save) { flags |= VIR_DOMAIN_UNDEFINE_MANAGED_SAVE; - - if (!managed_save) - flags = -1; + managed_save_safe = true; + } + if (snapshots_metadata) { + flags |= VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA; + snapshots_safe = true; + } + if (snapshots_full) { + flags |= VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL; + snapshots_safe = true; + } if (!vshConnectionUsability(ctl, ctl->conn)) return false; @@ -1448,61 +1523,124 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, &name))) return false; - has_managed_save = virDomainHasManagedSaveImage(dom, 0); - if (has_managed_save < 0) { - if (last_error->code != VIR_ERR_NO_SUPPORT) { - virshReportError(ctl); - virDomainFree(dom); - return false; - } else { + /* Do some flag manipulation. The goal here is to disable bits + * from flags to reduce the likelihood of a server rejecting + * unknown flag bits, as well as to track conditions which are + * safe by default for the given hypervisor and server version. */ + running = virDomainIsActive(dom); + if (running < 0) { + virshReportError(ctl); + goto cleanup; + } + if (!running) { + /* Undefine with snapshots only fails for inactive domains, + * and managed save only exists on inactive domains; if + * running, then we don't want to remove anything. */ + has_managed_save = virDomainHasManagedSaveImage(dom, 0); + if (has_managed_save < 0) { + if (last_error->code != VIR_ERR_NO_SUPPORT) { + virshReportError(ctl); + goto cleanup; + } virFreeError(last_error); last_error = NULL; - } - } - - if (flags == -1) { - if (has_managed_save == 1) { - vshError(ctl, - _("Refusing to undefine while domain managed save " - "image exists")); - virDomainFree(dom); - return false; + has_managed_save = 0; } - rc = virDomainUndefine(dom); - } else { - rc = virDomainUndefineFlags(dom, flags); - - /* It might fail when virDomainUndefineFlags is not - * supported on older libvirt, try to undefine the - * domain with combo virDomainManagedSaveRemove and - * virDomainUndefine. - */ - if (rc < 0) { + has_snapshots = virDomainSnapshotNum(dom, 0); + if (has_snapshots < 0) { if (last_error->code != VIR_ERR_NO_SUPPORT) { virshReportError(ctl); - goto end; - } else { + goto cleanup; + } + virFreeError(last_error); + last_error = NULL; + has_snapshots = 0; + } + if (has_snapshots) { + has_snapshots_metadata + = virDomainSnapshotNum(dom, VIR_DOMAIN_SNAPSHOT_NUM_METADATA); + if (has_snapshots_metadata < 0) { + /* The server did not know the new flag, assume that all + snapshots have metadata. */ virFreeError(last_error); last_error = NULL; + has_snapshots_metadata = has_snapshots; + } else { + /* The server knew the new flag, all aspects of + * undefineFlags are safe. */ + managed_save_safe = snapshots_safe = true; } + } + } + if (!has_managed_save) { + flags &= ~VIR_DOMAIN_UNDEFINE_MANAGED_SAVE; + managed_save_safe = true; + } + if (has_snapshots == 0) { + flags &= ~VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL; + snapshots_safe = true; + } + if (has_snapshots_metadata == 0) { + flags &= ~VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA; + snapshots_safe = true; + } - if ((has_managed_save == 1) && - virDomainManagedSaveRemove(dom, 0) < 0) - goto end; + /* Generally we want to try the new API first. However, while + * virDomainUndefineFlags was introduced at the same time as + * VIR_DOMAIN_UNDEFINE_MANAGED_SAVE in 0.9.4, the + * VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA flag was not present + * until 0.9.5; skip to piecewise emulation if we couldn't prove + * above that the new API is safe. */ + if (managed_save_safe && snapshots_safe) { + rc = virDomainUndefineFlags(dom, flags); + if (rc == 0 || (last_error->code != VIR_ERR_NO_SUPPORT && + last_error->code != VIR_ERR_INVALID_ARG)) + goto out; + virFreeError(last_error); + last_error = NULL; + } + + /* The new API is unsupported or unsafe; fall back to doing things + * piecewise. */ + if (has_managed_save) { + if (!managed_save) { + vshError(ctl, "%s", + _("Refusing to undefine while domain managed save " + "image exists")); + goto cleanup; + } + if (virDomainManagedSaveRemove(dom, 0) < 0) { + virshReportError(ctl); + goto cleanup; + } + } - rc = virDomainUndefine(dom); + if (has_snapshots) { + if (has_snapshots_metadata && + !(snapshots_metadata || snapshots_full)) { + vshError(ctl, + _("Refusing to undefine while %d snapshots exist"), + has_snapshots); + goto cleanup; } + + if ((snapshots_metadata || snapshots_full) && + vshRemoveAllSnapshots(dom, has_snapshots, snapshots_full) < 0) + goto cleanup; } -end: + rc = virDomainUndefine(dom); + +out: if (rc == 0) { vshPrint(ctl, _("Domain %s has been undefined\n"), name); + ret = true; } else { vshError(ctl, _("Failed to undefine domain %s"), name); - ret = false; } +cleanup: virDomainFree(dom); return ret; } @@ -2488,6 +2626,10 @@ static const vshCmdInfo info_shutdown[] = { static const vshCmdOptDef opts_shutdown[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"snapshots-metadata", VSH_OT_BOOL, 0, + N_("remove all domain snapshot metadata, if transient")}, + {"snapshots-full", VSH_OT_BOOL, 0, + N_("remove all domain snapshot contents, if transient")}, {NULL, 0, 0, NULL} }; @@ -2495,8 +2637,15 @@ static bool cmdShutdown(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom; - bool ret = true; + bool ret = false; const char *name; + /* User-requested actions. */ + bool snapshots_metadata = vshCommandOptBool(cmd, "snapshots-metadata"); + bool snapshots_full = vshCommandOptBool(cmd, "snapshots-full"); + /* Positive if these items exist. */ + int has_snapshots_metadata = 0; + int has_snapshots = 0; + int persistent; if (!vshConnectionUsability(ctl, ctl->conn)) return false; @@ -2504,13 +2653,67 @@ cmdShutdown(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, &name))) return false; + /* Do some flag manipulation. The goal here is to track + * conditions which are safe by default for the given hyperviser + * and server version. */ + persistent = virDomainIsPersistent(dom); + if (persistent < 0) { + virshReportError(ctl); + goto cleanup; + } + if (!persistent) { + /* Snapshot safety is only needed for transient domains. */ + has_snapshots = virDomainSnapshotNum(dom, 0); + if (has_snapshots < 0) { + if (last_error->code != VIR_ERR_NO_SUPPORT) { + virshReportError(ctl); + goto cleanup; + } + virFreeError(last_error); + last_error = NULL; + has_snapshots = 0; + } + if (has_snapshots) { + has_snapshots_metadata + = virDomainSnapshotNum(dom, VIR_DOMAIN_SNAPSHOT_NUM_METADATA); + if (has_snapshots_metadata < 0) { + /* The server did not know the new flag, assume that all + snapshots have metadata. */ + virFreeError(last_error); + last_error = NULL; + has_snapshots_metadata = has_snapshots; + } + } + } + + /* XXX Once virDomainShutdownFlags is added, use it here. Since + * snapshot shutdown safety was introduced before the new API, we + * don't have to go through quite as many contortions as + * cmdDestroy to check for snapshot safety. */ + + /* Fall back to doing things piecewise. */ + if (has_snapshots) { + if (has_snapshots_metadata && + !(snapshots_metadata || snapshots_full)) { + vshError(ctl, + _("Refusing to shutdown while %d snapshots exist"), + has_snapshots); + goto cleanup; + } + + if ((snapshots_metadata || snapshots_full) && + vshRemoveAllSnapshots(dom, has_snapshots, snapshots_full) < 0) + goto cleanup; + } + if (virDomainShutdown(dom) == 0) { vshPrint(ctl, _("Domain %s is being shutdown\n"), name); + ret = true; } else { vshError(ctl, _("Failed to shutdown domain %s"), name); - ret = false; } +cleanup: virDomainFree(dom); return ret; } @@ -2565,6 +2768,10 @@ static const vshCmdInfo info_destroy[] = { static const vshCmdOptDef opts_destroy[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"snapshots-metadata", VSH_OT_BOOL, 0, + N_("remove all domain snapshot metadata, if transient")}, + {"snapshots-full", VSH_OT_BOOL, 0, + N_("remove all domain snapshot contents, if transient")}, {NULL, 0, 0, NULL} }; @@ -2572,8 +2779,29 @@ static bool cmdDestroy(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom; - bool ret = true; + bool ret = false; const char *name; + /* Flags to attempt. */ + int flags = 0; + /* User-requested actions. */ + bool snapshots_metadata = vshCommandOptBool(cmd, "snapshots-metadata"); + bool snapshots_full = vshCommandOptBool(cmd, "snapshots-full"); + /* Positive if these items exist. */ + int has_snapshots_metadata = 0; + int has_snapshots = 0; + /* True if undefine will not strand data, even on older servers. */ + bool snapshots_safe = false; + int rc = -1; + int persistent; + + if (snapshots_metadata) { + flags |= VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA; + snapshots_safe = true; + } + if (snapshots_full) { + flags |= VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL; + snapshots_safe = true; + } if (!vshConnectionUsability(ctl, ctl->conn)) return false; @@ -2581,13 +2809,93 @@ cmdDestroy(vshControl *ctl, const vshCmd *cmd) if (!(dom = vshCommandOptDomain(ctl, cmd, &name))) return false; - if (virDomainDestroy(dom) == 0) { + /* Do some flag manipulation. The goal here is to disable bits + * from flags to reduce the likelihood of a server rejecting + * unknown flag bits, as well as to track conditions which are + * safe by default for the given hypervisor and server version. */ + persistent = virDomainIsPersistent(dom); + if (persistent < 0) { + virshReportError(ctl); + goto cleanup; + } + if (!persistent) { + /* Snapshot safety is only needed for transient domains. */ + has_snapshots = virDomainSnapshotNum(dom, 0); + if (has_snapshots < 0) { + if (last_error->code != VIR_ERR_NO_SUPPORT) { + virshReportError(ctl); + goto cleanup; + } + virFreeError(last_error); + last_error = NULL; + has_snapshots = 0; + } + if (has_snapshots) { + has_snapshots_metadata + = virDomainSnapshotNum(dom, VIR_DOMAIN_SNAPSHOT_NUM_METADATA); + if (has_snapshots_metadata < 0) { + /* The server did not know the new flag, assume that all + snapshots have metadata. */ + virFreeError(last_error); + last_error = NULL; + has_snapshots_metadata = has_snapshots; + } else { + /* The server knew the new flag, all aspects of + * destroyFlags are safe. */ + snapshots_safe = true; + } + } + } + if (has_snapshots == 0) { + flags &= ~VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL; + snapshots_safe = true; + } + if (has_snapshots_metadata == 0) { + flags &= ~VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA; + snapshots_safe = true; + } + + /* Generally we want to try the new API first. However, while + * virDomainDestroyFlags was introduced in 0.9.4, the + * VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA flag was not present + * until 0.9.5; skip to piecewise emulation if we couldn't prove + * above that the new API is safe. */ + if (snapshots_safe) { + rc = virDomainDestroyFlags(dom, flags); + if (rc == 0 || (last_error->code != VIR_ERR_NO_SUPPORT && + last_error->code != VIR_ERR_INVALID_ARG)) + goto out; + virFreeError(last_error); + last_error = NULL; + } + + /* The new API is unsupported or unsafe; fall back to doing things + * piecewise. */ + if (has_snapshots) { + if (has_snapshots_metadata && + !(snapshots_metadata || snapshots_full)) { + vshError(ctl, + _("Refusing to destroy while %d snapshots exist"), + has_snapshots); + goto cleanup; + } + + if ((snapshots_metadata || snapshots_full) && + vshRemoveAllSnapshots(dom, has_snapshots, snapshots_full) < 0) + goto cleanup; + } + + rc = virDomainDestroy(dom); + +out: + if (rc == 0) { vshPrint(ctl, _("Domain %s destroyed\n"), name); + ret = true; } else { vshError(ctl, _("Failed to destroy domain %s"), name); - ret = false; } +cleanup: virDomainFree(dom); return ret; } diff --git a/tools/virsh.pod b/tools/virsh.pod index 11a13fd..c27e99d 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -405,7 +405,7 @@ B<Example> Define a domain from an XML <file>. The domain definition is registered but not started. -=item B<destroy> I<domain-id> +=item B<destroy> I<domain-id> [I<--snapshots-full>] [I<--snapshots-metadata>] Immediately terminate the domain domain-id. This doesn't give the domain OS any chance to react, and it's the equivalent of ripping the power @@ -414,6 +414,15 @@ the B<shutdown> command instead. However, this does not delete any storage volumes used by the guest, and if the domain is persistent, it can be restarted later. +The I<--snapshots-full> and I<--snapshots-metadata> flags guarantee +that any snapshots (see the B<snapshot-list> command) are also cleaned +up when stopping a transient domain. Without either flag, attempts +to destroy a transient domain with snapshot metadata will fail. The +I<--snapshots-full> flag works with older servers, but loses all +snapshot contents; the I<--snapshots-metadata> flag only removes +metadata that can later be recreated, but requires newer servers. If +the domain is persistent, these flags are ignored. + =item B<domblkstat> I<domain> I<block-device> Get device block stats for a running domain. @@ -867,7 +876,7 @@ The I<--maximum> flag controls the maximum number of virtual cpus that can be hot-plugged the next time the domain is booted. As such, it must only be used with the I<--config> flag, and not with the I<--live> flag. -=item B<shutdown> I<domain-id> +=item B<shutdown> I<domain-id> [I<--snapshots-full>] [I<--snapshots-metadata>] Gracefully shuts down a domain. This coordinates with the domain OS to perform graceful shutdown, so there is no guarantee that it will @@ -877,6 +886,15 @@ services must be shutdown in the domain. The exact behavior of a domain when it shuts down is set by the I<on_shutdown> parameter in the domain's XML definition. +The I<--snapshots-full> and I<--snapshots-metadata> flags guarantee +that any snapshots (see the B<snapshot-list> command) are also cleaned +up when shutting down a transient domain. Without either flag, attempts +to shutdown a transient domain with snapshot metadata will fail. The +I<--snapshots-full> flag works with older servers, but loses all +snapshot contents; the I<--snapshots-metadata> flag only removes +metadata that can later be recreated, but requires newer servers. If +the domain is persistent, these flags are ignored. + =item B<start> I<domain-name> [I<--console>] [I<--paused>] [I<--autodestroy>] [I<--bypass-cache>] @@ -907,16 +925,26 @@ hypervisor. Output the device used for the TTY console of the domain. If the information is not available the processes will provide an exit code of 1. -=item B<undefine> I<domain-id> [I<--managed-save>] +=item B<undefine> I<domain-id> [I<--managed-save>] [I<--snapshots-full>] +[I<--snapshots-metadata] Undefine a domain. If the domain is running, this converts it to a transient domain, without stopping it. If the domain is inactive, the domain configuration is removed. -The I<--managed-save> flag guarantees that any managed save image(see +The I<--managed-save> flag guarantees that any managed save image (see the B<managedsave> command) is also cleaned up. Without the flag, attempts to undefine a domain with a managed save image will fail. +The I<--snapshots-full> and I<--snapshots-metadata> flags guarantee +that any snapshots (see the B<snapshot-list> command) are also cleaned +up when undefining an inactive domain. Without either flag, attempts +to undefine an inactive domain with snapshot metadata will fail. The +I<--snapshots-full> flag works with older servers, but loses all +snapshot contents; the I<--snapshots-metadata> flag only removes +metadata that can later be recreated, but requires newer servers. If +the domain is active, these flags are ignored. + NOTE: For an inactive domain, the domain name or UUID must be used as the I<domain-id>. -- 1.7.4.4

Migration is another case of stranding metadata. And since snapshot metadata is arbitrarily large, there's no way to shoehorn it into the migration cookie of migration v3. A future patch will make it possible to manually recreate the snapshot metadata on the destination. But even that is limited, since if we delete the snapshot metadata prior to migration, then we won't know the name of the current snapshot to pass along; and if we delete the snapshot metadata after migration and use the v3 migration cookie to pass along the name of the current snapshot, then we need a way to bypass the fact that this patch refuses migration with snapshot metadata present. So eventually, we may have to introduce migration protocol v4 that allows feature negotiation and an arbitrary number of handshake exchanges, so as to pass as many rpc calls as needed to transfer all the snapshot xml hierarchy. But all of that is thoughts for the future; for now, the best course of action is to quit early, rather than get into a funky state of stale metadata. * src/qemu/qemu_driver.c (qemudDomainMigratePerform) (qemuDomainMigrateBegin3, qemuDomainMigratePerform3): Add restriction. --- src/qemu/qemu_driver.c | 24 ++++++++++++++++++++++++ 1 files changed, 24 insertions(+), 0 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index bf82df4..7802e08 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7659,6 +7659,7 @@ qemudDomainMigratePerform (virDomainPtr dom, virDomainObjPtr vm; int ret = -1; const char *dconnuri = NULL; + int nsnapshots; virCheckFlags(QEMU_MIGRATION_FLAGS, -1); @@ -7679,6 +7680,13 @@ qemudDomainMigratePerform (virDomainPtr dom, goto cleanup; } + if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot migrate domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + if (flags & VIR_MIGRATE_PEER2PEER) { dconnuri = uri; uri = NULL; @@ -7755,6 +7763,7 @@ qemuDomainMigrateBegin3(virDomainPtr domain, struct qemud_driver *driver = domain->conn->privateData; virDomainObjPtr vm; char *xml = NULL; + int nsnapshots; virCheckFlags(QEMU_MIGRATION_FLAGS, NULL); @@ -7768,6 +7777,13 @@ qemuDomainMigrateBegin3(virDomainPtr domain, goto cleanup; } + if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot migrate domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + if ((flags & VIR_MIGRATE_CHANGE_PROTECTION)) { if (qemuMigrationJobStart(driver, vm, QEMU_ASYNC_JOB_MIGRATION_OUT) < 0) goto cleanup; @@ -7929,6 +7945,7 @@ qemuDomainMigratePerform3(virDomainPtr dom, struct qemud_driver *driver = dom->conn->privateData; virDomainObjPtr vm; int ret = -1; + int nsnapshots; virCheckFlags(QEMU_MIGRATION_FLAGS, -1); @@ -7942,6 +7959,13 @@ qemuDomainMigratePerform3(virDomainPtr dom, goto cleanup; } + if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot migrate domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + ret = qemuMigrationPerform(driver, dom->conn, vm, xmlin, dconnuri, uri, cookiein, cookieinlen, cookieout, cookieoutlen, -- 1.7.4.4
participants (1)
-
Eric Blake