[libvirt] [PATCHv3 00/43] cleaned up snapshot series

As promised, here is my v3, which includes all the self-replies I was scattering throughout v2: https://www.redhat.com/archives/libvir-list/2011-August/msg00620.html Also available here: git fetch git://repo.or.cz/libvirt/ericb.git snapshot or browse online at: http://repo.or.cz/w/libvirt/ericb.git/shortlog/refs/heads/snapshot I've tested that this can create external disk snapshots with SELinux running using the qemu snapshot_blkdev monitor command, then deleting the metadata for those snapshots. Other things from my RFC still remain to be coded: https://www.redhat.com/archives/libvir-list/2011-August/msg00361.html support for offline external disk snapshots support for more flags (such as starting an offline snapshot on revert) support for revert and delete of disk snapshots new apis for easier manipulation of snapshot hierarchies Eric Blake (43): snapshot: better event when reverting qemu to paused snapshot snapshot: improve reverting to qemu paused snapshots snapshot: properly revert qemu to offline snapshots snapshot: don't leak resources on qemu snapshot failure snapshot: only pass snapshot to qemu command line when reverting snapshot: track current snapshot across restarts snapshot: allow deletion of just snapshot metadata snapshot: avoid crash when deleting qemu snapshots snapshot: simplify acting on just children snapshot: let qemu discard only snapshot metadata snapshot: identify which snapshots have metadata snapshot: identify qemu snapshot roots 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 snapshot: prevent migration from stranding snapshot data snapshot: refactor domain xml output snapshot: allow full domain xml in snapshot snapshot: correctly escape generated xml snapshot: update rng to support full domain in xml snapshot: store qemu domain details in xml snapshot: additions to domain xml for disks snapshot: reject transient disks where code is not ready snapshot: reflect recent options in virsh snapshot: introduce new deletion flag snapshot: expose new delete flag in virsh snapshot: allow halting after snapshot snapshot: refactor virsh snapshot creation snapshot: expose halt-after-creation in virsh snapshot: wire up new qemu monitor command snapshot: support extra state in snapshots snapshot: add <disks> to snapshot xml snapshot: also support disks by path snapshot: add virsh domblklist command snapshot: add flag for requesting disk snapshot snapshot: reject unimplemented disk snapshot features snapshot: make it possible to audit external snapshot snapshot: wire up disk-only flag to snapshot-create snapshot: wire up live qemu disk snapshots snapshot: refactor qemu file opening snapshot: use SELinux and lock manager with external snapshots docs/formatdomain.html.in | 40 +- docs/formatsnapshot.html.in | 253 ++- docs/schemas/Makefile.am | 1 + docs/schemas/domain.rng | 2555 +------------------- docs/schemas/{domain.rng => domaincommon.rng} | 32 +- docs/schemas/domainsnapshot.rng | 89 +- include/libvirt/libvirt.h.in | 62 +- src/conf/domain_audit.c | 12 +- src/conf/domain_audit.h | 4 +- src/conf/domain_conf.c | 861 ++++++-- src/conf/domain_conf.h | 75 +- src/esx/esx_driver.c | 41 +- src/libvirt.c | 132 +- src/libvirt_private.syms | 8 + src/libxl/libxl_conf.c | 5 + src/libxl/libxl_driver.c | 11 +- src/qemu/qemu_command.c | 12 +- src/qemu/qemu_conf.h | 1 + src/qemu/qemu_driver.c | 1611 +++++++++---- src/qemu/qemu_hotplug.c | 18 +- src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_monitor.c | 24 + src/qemu/qemu_monitor.h | 4 + src/qemu/qemu_monitor_json.c | 33 + src/qemu/qemu_monitor_json.h | 4 + src/qemu/qemu_monitor_text.c | 40 + src/qemu/qemu_monitor_text.h | 4 + src/qemu/qemu_process.c | 21 +- src/qemu/qemu_process.h | 1 + src/uml/uml_driver.c | 56 +- src/vbox/vbox_tmpl.c | 46 +- src/xen/xend_internal.c | 12 +- src/xenxs/xen_sxpr.c | 5 + src/xenxs/xen_xm.c | 5 + tests/domainsnapshotxml2xmlin/disk_snapshot.xml | 16 + tests/domainsnapshotxml2xmlout/disk_snapshot.xml | 77 + tests/domainsnapshotxml2xmlout/full_domain.xml | 35 + .../qemuxml2argv-disk-snapshot.args | 7 + .../qemuxml2argv-disk-snapshot.xml | 39 + .../qemuxml2argv-disk-transient.xml | 27 + tests/qemuxml2argvtest.c | 2 + tools/virsh.c | 755 +++++- tools/virsh.pod | 111 +- 43 files changed, 3616 insertions(+), 3533 deletions(-) copy docs/schemas/{domain.rng => domaincommon.rng} (98%) create mode 100644 tests/domainsnapshotxml2xmlin/disk_snapshot.xml create mode 100644 tests/domainsnapshotxml2xmlout/disk_snapshot.xml create mode 100644 tests/domainsnapshotxml2xmlout/full_domain.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-transient.xml -- 1.7.4.4

When reverting a running domain to a paused snapshot, the event that fires should mention that the domain is suspended. * include/libvirt/libvirt.h.in (VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT): New sub-event. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Use it. --- include/libvirt/libvirt.h.in | 3 ++- src/qemu/qemu_driver.c | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index aa29fb6..a625479 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2010,7 +2010,7 @@ typedef enum { VIR_DOMAIN_EVENT_STARTED_BOOTED = 0, /* Normal startup from boot */ VIR_DOMAIN_EVENT_STARTED_MIGRATED = 1, /* Incoming migration from another host */ VIR_DOMAIN_EVENT_STARTED_RESTORED = 2, /* Restored from a state file */ - VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT = 3, /* Restored from snapshot */ + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT = 3, /* Restored from running snapshot */ } virDomainEventStartedDetailType; /** @@ -2023,6 +2023,7 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED = 1, /* Suspended for offline migration */ VIR_DOMAIN_EVENT_SUSPENDED_IOERROR = 2, /* Suspended due to a disk I/O error */ VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG = 3, /* Suspended due to a watchdog firing */ + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 4, /* Restored from paused snapshot */ } virDomainEventSuspendedDetailType; /** diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 57ad3d1..12f8179 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8808,14 +8808,16 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, QEMU_ASYNC_JOB_NONE); if (rc < 0) goto endjob; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); } else { virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_FROM_SNAPSHOT); + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); } - - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); } else { /* qemu is a little funny with running guests and the restoration * of snapshots. If the snapshot was taken online, -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:18AM -0600, Eric Blake wrote:
When reverting a running domain to a paused snapshot, the event that fires should mention that the domain is suspended.
* include/libvirt/libvirt.h.in (VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT): New sub-event. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Use it. --- include/libvirt/libvirt.h.in | 3 ++- src/qemu/qemu_driver.c | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index aa29fb6..a625479 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2010,7 +2010,7 @@ typedef enum { VIR_DOMAIN_EVENT_STARTED_BOOTED = 0, /* Normal startup from boot */ VIR_DOMAIN_EVENT_STARTED_MIGRATED = 1, /* Incoming migration from another host */ VIR_DOMAIN_EVENT_STARTED_RESTORED = 2, /* Restored from a state file */ - VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT = 3, /* Restored from snapshot */ + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT = 3, /* Restored from running snapshot */ } virDomainEventStartedDetailType;
/** @@ -2023,6 +2023,7 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED = 1, /* Suspended for offline migration */ VIR_DOMAIN_EVENT_SUSPENDED_IOERROR = 2, /* Suspended due to a disk I/O error */ VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG = 3, /* Suspended due to a watchdog firing */ + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 4, /* Restored from paused snapshot */ } virDomainEventSuspendedDetailType;
/** diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 57ad3d1..12f8179 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8808,14 +8808,16 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, QEMU_ASYNC_JOB_NONE); if (rc < 0) goto endjob; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); } else { virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_FROM_SNAPSHOT); + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); } - - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); } else { /* qemu is a little funny with running guests and the restoration * of snapshots. If the snapshot was taken online,
Same comment about lifecycle as for previous posting of this patch Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

There are two classes of management apps that track events - one that only cares about on/off (and only needs to track EVENT_STARTED and EVENT_STOPPED), and one that cares about paused/running (also tracks EVENT_SUSPENDED/EVENT_RESUMED). To keep both classes happy, any transition that can go from inactive to paused must emit two back-to-back events - one for started and one for suspended (since later resuming of the domain will only send RESUMED, but the first class isn't tracking that). This also fixes a bug where virDomainCreateWithFlags with the VIR_DOMAIN_START_PAUSED flag failed to start paused when restoring from a managed save image. * include/libvirt/libvirt.h.in (VIR_DOMAIN_EVENT_SUSPENDED_RESTORED) (VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT) (VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT): New sub-events. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Use them. (qemuDomainSaveImageStartVM): Likewise, and add parameter. (qemudDomainCreate, qemuDomainObjStart): Send suspended event when starting paused. (qemuDomainObjRestore): Add parameter. (qemuDomainObjStart, qemuDomainRestoreFlags): Update callers. * examples/domain-events/events-c/event-test.c (eventDetailToString): Map new detail strings. --- I think this addresses Dan's findings about events related to state transitions. It turned a 13-line v3 into a 109-line patch. I'll repost my entire snapshot series as v4 once I finish rebasing on top of this (since I know it introduces some merge conflicts with later patches in the series). examples/domain-events/events-c/event-test.c | 37 +++++++++++++-- include/libvirt/libvirt.h.in | 6 ++- src/qemu/qemu_driver.c | 66 +++++++++++++++++++++----- 3 files changed, 90 insertions(+), 19 deletions(-) diff --git a/examples/domain-events/events-c/event-test.c b/examples/domain-events/events-c/event-test.c index 4766a0d..6a3ed26 100644 --- a/examples/domain-events/events-c/event-test.c +++ b/examples/domain-events/events-c/event-test.c @@ -87,19 +87,45 @@ static const char *eventDetailToString(int event, int detail) { case VIR_DOMAIN_EVENT_STARTED_RESTORED: ret = "Restored"; break; + case VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT: + ret = "Snapshot"; + break; } break; case VIR_DOMAIN_EVENT_SUSPENDED: - if (detail == VIR_DOMAIN_EVENT_SUSPENDED_PAUSED) + switch (detail) { + case VIR_DOMAIN_EVENT_SUSPENDED_PAUSED: ret = "Paused"; - else if (detail == VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED) + break; + case VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED: ret = "Migrated"; + break; + case VIR_DOMAIN_EVENT_SUSPENDED_IOERROR: + ret = "I/O Error"; + break; + case VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG: + ret = "Watchdog"; + break; + case VIR_DOMAIN_EVENT_SUSPENDED_RESTORED: + ret = "Restored"; + break; + case VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT: + ret = "Snapshot"; + break; + } break; case VIR_DOMAIN_EVENT_RESUMED: - if (detail == VIR_DOMAIN_EVENT_RESUMED_UNPAUSED) + switch (detail) { + case VIR_DOMAIN_EVENT_RESUMED_UNPAUSED: ret = "Unpaused"; - else if (detail == VIR_DOMAIN_EVENT_RESUMED_MIGRATED) + break; + case VIR_DOMAIN_EVENT_RESUMED_MIGRATED: ret = "Migrated"; + break; + case VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT: + ret = "Snapshot"; + break; + } break; case VIR_DOMAIN_EVENT_STOPPED: switch (detail) { @@ -121,6 +147,9 @@ static const char *eventDetailToString(int event, int detail) { case VIR_DOMAIN_EVENT_STOPPED_FAILED: ret = "Failed"; break; + case VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT: + ret = "Snapshot"; + break; } break; } diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 53a2f7d..edd197c 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -116,8 +116,7 @@ typedef enum { VIR_DOMAIN_PAUSED_DUMP = 4, /* paused for offline core dump */ VIR_DOMAIN_PAUSED_IOERROR = 5, /* paused due to a disk I/O error */ VIR_DOMAIN_PAUSED_WATCHDOG = 6, /* paused due to a watchdog event */ - VIR_DOMAIN_PAUSED_FROM_SNAPSHOT = 7, /* restored from a snapshot which was - * taken while domain was paused */ + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT = 7, /* paused after restoring from snapshot */ } virDomainPausedReason; typedef enum { @@ -2024,6 +2023,8 @@ typedef enum { VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED = 1, /* Suspended for offline migration */ VIR_DOMAIN_EVENT_SUSPENDED_IOERROR = 2, /* Suspended due to a disk I/O error */ VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG = 3, /* Suspended due to a watchdog firing */ + VIR_DOMAIN_EVENT_SUSPENDED_RESTORED = 4, /* Restored from paused state file */ + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT = 5, /* Restored from paused snapshot */ } virDomainEventSuspendedDetailType; /** @@ -2034,6 +2035,7 @@ typedef enum { typedef enum { VIR_DOMAIN_EVENT_RESUMED_UNPAUSED = 0, /* Normal resume due to admin unpause */ VIR_DOMAIN_EVENT_RESUMED_MIGRATED = 1, /* Resumed for completion of migration */ + VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT = 2, /* Resumed from snapshot */ } virDomainEventResumedDetailType; /** diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 737eec8..6becd31 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1268,6 +1268,7 @@ static virDomainPtr qemudDomainCreate(virConnectPtr conn, const char *xml, virDomainObjPtr vm = NULL; virDomainPtr dom = NULL; virDomainEventPtr event = NULL; + virDomainEventPtr event2 = NULL; virCheckFlags(VIR_DOMAIN_START_PAUSED | VIR_DOMAIN_START_AUTODESTROY, NULL); @@ -1315,6 +1316,16 @@ static virDomainPtr qemudDomainCreate(virConnectPtr conn, const char *xml, event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_BOOTED); + if (event && (flags & VIR_DOMAIN_START_PAUSED)) { + /* There are two classes of event-watching clients - those + * that only care about on/off (and must see a started event + * no matter what, but don't care about suspend events), and + * those that also care about running/paused. To satisfy both + * client types, we have to send two events. */ + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); + } virDomainAuditStart(vm, "booted", true); dom = virGetDomain(conn, vm->def->name, vm->def->uuid); @@ -1328,8 +1339,11 @@ cleanup: virDomainDefFree(def); if (vm) virDomainObjUnlock(vm); - if (event) + if (event) { qemuDomainEventQueue(driver, event); + if (event2) + qemuDomainEventQueue(driver, event2); + } qemuDriverUnlock(driver); return dom; } @@ -3890,7 +3904,8 @@ qemuDomainSaveImageStartVM(virConnectPtr conn, virDomainObjPtr vm, int *fd, const struct qemud_save_header *header, - const char *path) + const char *path, + bool start_paused) { int ret = -1; virDomainEventPtr event; @@ -3961,8 +3976,8 @@ qemuDomainSaveImageStartVM(virConnectPtr conn, qemuDomainEventQueue(driver, event); - /* If it was running before, resume it now. */ - if (header->was_running) { + /* If it was running before, resume it now unless caller requested pause. */ + if (header->was_running && !start_paused) { if (qemuProcessStartCPUs(driver, vm, conn, VIR_DOMAIN_RUNNING_RESTORED, QEMU_ASYNC_JOB_NONE) < 0) { @@ -3975,6 +3990,14 @@ qemuDomainSaveImageStartVM(virConnectPtr conn, VIR_WARN("Failed to save status on vm %s", vm->def->name); goto out; } + } else { + int detail = (start_paused ? VIR_DOMAIN_EVENT_SUSPENDED_PAUSED : + VIR_DOMAIN_EVENT_SUSPENDED_RESTORED); + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + if (event) + qemuDomainEventQueue(driver, event); } ret = 0; @@ -4026,7 +4049,8 @@ qemuDomainRestoreFlags(virConnectPtr conn, if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; - ret = qemuDomainSaveImageStartVM(conn, driver, vm, &fd, &header, path); + ret = qemuDomainSaveImageStartVM(conn, driver, vm, &fd, &header, path, + false); if (virFileDirectFdClose(directFd) < 0) VIR_WARN("Failed to close %s", path); @@ -4151,6 +4175,7 @@ qemuDomainObjRestore(virConnectPtr conn, struct qemud_driver *driver, virDomainObjPtr vm, const char *path, + bool start_paused, bool bypass_cache) { virDomainDefPtr def = NULL; @@ -4181,7 +4206,8 @@ qemuDomainObjRestore(virConnectPtr conn, virDomainObjAssignDef(vm, def, true); def = NULL; - ret = qemuDomainSaveImageStartVM(conn, driver, vm, &fd, &header, path); + ret = qemuDomainSaveImageStartVM(conn, driver, vm, &fd, &header, path, + start_paused); if (virFileDirectFdClose(directFd) < 0) VIR_WARN("Failed to close %s", path); @@ -4459,7 +4485,7 @@ qemuDomainObjStart(virConnectPtr conn, if (virFileExists(managed_save)) { ret = qemuDomainObjRestore(conn, driver, vm, managed_save, - bypass_cache); + start_paused, bypass_cache); if ((ret == 0) && (unlink(managed_save) < 0)) VIR_WARN("Failed to remove the managed state %s", managed_save); @@ -4475,8 +4501,16 @@ qemuDomainObjStart(virConnectPtr conn, virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_BOOTED); - if (event) + if (event) { qemuDomainEventQueue(driver, event); + if (start_paused) { + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_PAUSED); + if (event) + qemuDomainEventQueue(driver, event); + } + } } cleanup: @@ -8744,6 +8778,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, virDomainSnapshotObjPtr snap = NULL; char uuidstr[VIR_UUID_STRING_BUFLEN]; virDomainEventPtr event = NULL; + virDomainEventPtr event2 = NULL; qemuDomainObjPrivatePtr priv; int rc; @@ -8800,6 +8835,9 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); if (snap->def->state == VIR_DOMAIN_PAUSED) { /* qemu unconditionally starts the domain running again after * loadvm, so let's pause it to keep consistency @@ -8810,14 +8848,13 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, QEMU_ASYNC_JOB_NONE); if (rc < 0) goto endjob; + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); } else { virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_FROM_SNAPSHOT); } - - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); } else { /* qemu is a little funny with running guests and the restoration * of snapshots. If the snapshot was taken online, @@ -8860,8 +8897,11 @@ cleanup: } else if (snap) { snap->def->current = false; } - if (event) + if (event) { qemuDomainEventQueue(driver, event); + if (event2) + qemuDomainEventQueue(driver, event2); + } if (vm) virDomainObjUnlock(vm); qemuDriverUnlock(driver); -- 1.7.4.4

While it is nice that snapshots and saved images remember whether the domain was running or paused, sometimes the restoration phase wants to guarantee a particular state (paused to allow hot-plugging, or running without needing to call resume). This introduces new flags to allow the control, and a later patch will implement the flags for qemu. * include/libvirt/libvirt.h.in (VIR_DOMAIN_SAVE_RUNNING) (VIR_DOMAIN_SAVE_PAUSED, VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) (VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED): New flags. * src/libvirt.c (virDomainSaveFlags, virDomainRestoreFlags) (virDomainManagedSave, virDomainSaveImageDefineXML) (virDomainRevertToSnapshot): Document their use, and enforce mutual exclusion. --- include/libvirt/libvirt.h.in | 12 +++++- src/libvirt.c | 89 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index edd197c..c271e5e 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -955,11 +955,14 @@ int virDomainResume (virDomainPtr domain); /** * virDomainSaveRestoreFlags: - * Flags for use in virDomainSaveFlags(), virDomainManagedSave(), and - * virDomainRestoreFlags(). + * Flags for use in virDomainSaveFlags(), virDomainManagedSave(), + * virDomainRestoreFlags(), and virDomainSaveImageDefineXML(). Not all + * flags apply to all these functions. */ typedef enum { VIR_DOMAIN_SAVE_BYPASS_CACHE = 1 << 0, /* Avoid file system cache pollution */ + VIR_DOMAIN_SAVE_RUNNING = 1 << 1, /* Favor running over paused */ + VIR_DOMAIN_SAVE_PAUSED = 1 << 2, /* Favor paused over running */ } virDomainSaveRestoreFlags; int virDomainSave (virDomainPtr domain, @@ -2572,6 +2575,11 @@ int virDomainHasCurrentSnapshot(virDomainPtr domain, unsigned int flags); virDomainSnapshotPtr virDomainSnapshotCurrent(virDomainPtr domain, unsigned int flags); +typedef enum { + VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING = 1 << 0, /* Run after revert */ + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED = 1 << 1, /* Pause after revert */ +} virDomainSnapshotRevertFlags; + /* Revert the domain to a point-in-time snapshot. The * state of the guest after this call will be the state * of the guest when the snapshot in question was taken diff --git a/src/libvirt.c b/src/libvirt.c index 711580e..ab0e3e8 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -2310,7 +2310,7 @@ error: * * This method will suspend a domain and save its memory contents to * a file on disk. After the call, if successful, the domain is not - * listed as running anymore (this may be a problem). + * listed as running anymore (this ends the life of a transient domain). * Use virDomainRestore() to restore a domain after saving. * * See virDomainSaveFlags() for more control. Also, a save file can @@ -2379,7 +2379,7 @@ error: * * This method will suspend a domain and save its memory contents to * a file on disk. After the call, if successful, the domain is not - * listed as running anymore (this may be a problem). + * listed as running anymore (this ends the life of a transient domain). * Use virDomainRestore() to restore a domain after saving. * * If the hypervisor supports it, @dxml can be used to alter @@ -2394,6 +2394,12 @@ error: * fail if it cannot do so for the given system; this can allow less * pressure on file system cache, but also risks slowing saves to NFS. * + * Normally, the saved state file will remember whether the domain was + * running or paused, and restore defaults to the same state. + * Specifying VIR_DOMAIN_SAVE_RUNNING or VIR_DOMAIN_SAVE_PAUSED in + * @flags will override what state gets saved into the file. These + * two flags are mutually exclusive. + * * A save file can be inspected or modified slightly with * virDomainSaveImageGetXMLDesc() and virDomainSaveImageDefineXML(). * @@ -2425,6 +2431,12 @@ virDomainSaveFlags(virDomainPtr domain, const char *to, goto error; } + if ((flags & VIR_DOMAIN_SAVE_RUNNING) && (flags & VIR_DOMAIN_SAVE_PAUSED)) { + virLibDomainError(VIR_ERR_INVALID_ARG, + _("running and paused flags are mutually exclusive")); + goto error; + } + if (conn->driver->domainSaveFlags) { int ret; char *absolute_to; @@ -2532,6 +2544,12 @@ error: * fail if it cannot do so for the given system; this can allow less * pressure on file system cache, but also risks slowing saves to NFS. * + * Normally, the saved state file will remember whether the domain was + * running or paused, and restore defaults to the same state. + * Specifying VIR_DOMAIN_SAVE_RUNNING or VIR_DOMAIN_SAVE_PAUSED in + * @flags will override the default read from the file. These two + * flags are mutually exclusive. + * * Returns 0 in case of success and -1 in case of failure. */ int @@ -2557,6 +2575,12 @@ virDomainRestoreFlags(virConnectPtr conn, const char *from, const char *dxml, goto error; } + if ((flags & VIR_DOMAIN_SAVE_RUNNING) && (flags & VIR_DOMAIN_SAVE_PAUSED)) { + virLibDomainError(VIR_ERR_INVALID_ARG, + _("running and paused flags are mutually exclusive")); + goto error; + } + if (conn->driver->domainRestoreFlags) { int ret; char *absolute_from; @@ -2661,7 +2685,7 @@ error: * @conn: pointer to the hypervisor connection * @file: path to saved state file * @dxml: XML config for adjusting guest xml used on restore - * @flags: 0 for now + * @flags: bitwise-OR of virDomainSaveRestoreFlags * * This updates the definition of a domain stored in a saved state * file. @file must be a file created previously by virDomainSave() @@ -2673,6 +2697,13 @@ error: * disk device, to match renaming done as part of backing up the disk * device while the domain is stopped. * + * Normally, the saved state file will remember whether the domain was + * running or paused, and restore defaults to the same state. + * Specifying VIR_DOMAIN_SAVE_RUNNING or VIR_DOMAIN_SAVE_PAUSED in + * @flags will override the default saved into the file; omitting both + * leaves the file's default unchanged. These two flags are mutually + * exclusive. + * * Returns 0 in case of success and -1 in case of failure. */ int @@ -2698,6 +2729,12 @@ virDomainSaveImageDefineXML(virConnectPtr conn, const char *file, goto error; } + if ((flags & VIR_DOMAIN_SAVE_RUNNING) && (flags & VIR_DOMAIN_SAVE_PAUSED)) { + virLibDomainError(VIR_ERR_INVALID_ARG, + _("running and paused flags are mutually exclusive")); + goto error; + } + if (conn->driver->domainSaveImageDefineXML) { int ret; char *absolute_file; @@ -7018,7 +7055,9 @@ error: * @domain: pointer to a defined domain * * Launch a defined domain. If the call succeeds the domain moves from the - * defined to the running domains pools. + * defined to the running domains pools. The domain will be paused only + * if restoring from managed state created from a paused domain. For more + * control, see virDomainCreateWithFlags(). * * Returns 0 in case of success, -1 in case of error */ @@ -7064,9 +7103,12 @@ error: * Launch a defined domain. If the call succeeds the domain moves from the * defined to the running domains pools. * - * If the VIR_DOMAIN_START_PAUSED flag is set, the guest domain - * will be started, but its CPUs will remain paused. The CPUs - * can later be manually started using virDomainResume. + * If the VIR_DOMAIN_START_PAUSED flag is set, or if the guest domain + * has a managed save image that requested paused state (see + * virDomainManagedSave()) the guest domain will be started, but its + * CPUs will remain paused. The CPUs can later be manually started + * using virDomainResume(). In all other cases, the guest domain will + * be running. * * If the VIR_DOMAIN_START_AUTODESTROY flag is set, the guest * domain will be automatically destroyed when the virConnectPtr @@ -15332,6 +15374,12 @@ error: * fail if it cannot do so for the given system; this can allow less * pressure on file system cache, but also risks slowing saves to NFS. * + * Normally, the managed saved state will remember whether the domain + * was running or paused, and start will resume to the same state. + * Specifying VIR_DOMAIN_SAVE_RUNNING or VIR_DOMAIN_SAVE_PAUSED in + * @flags will override the default saved into the file. These two + * flags are mutually exclusive. + * * Returns 0 in case of success or -1 in case of failure */ int virDomainManagedSave(virDomainPtr dom, unsigned int flags) @@ -15354,6 +15402,12 @@ int virDomainManagedSave(virDomainPtr dom, unsigned int flags) goto error; } + if ((flags & VIR_DOMAIN_SAVE_RUNNING) && (flags & VIR_DOMAIN_SAVE_PAUSED)) { + virLibDomainError(VIR_ERR_INVALID_ARG, + _("running and paused flags are mutually exclusive")); + goto error; + } + if (conn->driver->domainManagedSave) { int ret; @@ -15785,10 +15839,22 @@ error: /** * virDomainRevertToSnapshot: * @snapshot: a domain snapshot object - * @flags: unused flag parameters; callers should pass 0 + * @flags: bitwise-OR of virDomainSnapshotRevertFlags * * Revert the domain to a given snapshot. * + * Normally, the domain will revert to the same state the domain was + * in while the snapshot was taken (whether inactive, running, or + * paused), except that disk snapshots default to reverting to + * inactive state. Including VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING in + * @flags overrides the snapshot state to guarantee a running domain + * after the revert; or including VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED in + * @flags guarantees a paused domain after the revert. These two + * flags are mutually exclusive. While a persistent domain does not + * need either flag, it is not possible to revert a transient domain + * into an inactive state, so transient domains require the use of one + * of these two flags. + * * Returns 0 if the creation is successful, -1 on error. */ int @@ -15814,6 +15880,13 @@ virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto error; } + if ((flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && + (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { + virLibDomainError(VIR_ERR_INVALID_ARG, + _("running and paused flags are mutually exclusive")); + goto error; + } + if (conn->driver->domainRevertToSnapshot) { int ret = conn->driver->domainRevertToSnapshot(snapshot, flags); if (ret < 0) -- 1.7.4.4

Implement the new running/paused overrides for saved state management. Unfortunately, for virDomainSaveImageDefineXML, the saved state updates are write-only - I don't know of any way to expose a way to query the current run/pause setting of an existing save image file to the user without adding a new API or modifying the domain xml of virDomainSaveImageGetXMLDesc to include a new element to reflect the state bit encoded into the save image. However, I don't think this is a show-stopper, since the API is designed to leave the state bit alone unless an explicit flag is used to change it. * src/qemu/qemu_driver.c (qemuDomainSaveInternal) (qemuDomainSaveImageOpen): Adjust signature. (qemuDomainSaveFlags, qemuDomainManagedSave) (qemuDomainRestoreFlags, qemuDomainSaveImageGetXMLDesc) (qemuDomainSaveImageDefineXML, qemuDomainObjRestore): Adjust callers. --- src/qemu/qemu_driver.c | 68 ++++++++++++++++++++++++++++++++--------------- 1 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 6becd31..4ae6128 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2213,7 +2213,7 @@ qemuCompressProgramName(int compress) static int qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, virDomainObjPtr vm, const char *path, - int compressed, bool bypass_cache, const char *xmlin) + int compressed, const char *xmlin, unsigned int flags) { char *xml = NULL; struct qemud_save_header header; @@ -2232,6 +2232,7 @@ qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, gid_t gid = getgid(); int directFlag = 0; virFileDirectFdPtr directFd = NULL; + bool bypass_cache = flags & VIR_DOMAIN_SAVE_BYPASS_CACHE; if (qemuProcessAutoDestroyActive(driver, vm)) { qemuReportError(VIR_ERR_OPERATION_INVALID, @@ -2267,6 +2268,11 @@ qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, goto endjob; } } + /* libvirt.c already guaranteed these two flags are exclusive. */ + if (flags & VIR_DOMAIN_SAVE_RUNNING) + header.was_running = 1; + else if (flags & VIR_DOMAIN_SAVE_PAUSED) + header.was_running = 0; /* Get XML for the domain. Restore needs only the inactive xml, * including secure. We should get the same result whether xmlin @@ -2508,7 +2514,9 @@ qemuDomainSaveFlags(virDomainPtr dom, const char *path, const char *dxml, int ret = -1; virDomainObjPtr vm = NULL; - virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE, -1); + virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE | + VIR_DOMAIN_SAVE_RUNNING | + VIR_DOMAIN_SAVE_PAUSED, -1); qemuDriverLock(driver); @@ -2546,8 +2554,7 @@ qemuDomainSaveFlags(virDomainPtr dom, const char *path, const char *dxml, } ret = qemuDomainSaveInternal(driver, dom, vm, path, compressed, - (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0, - dxml); + dxml, flags); vm = NULL; cleanup: @@ -2585,7 +2592,9 @@ qemuDomainManagedSave(virDomainPtr dom, unsigned int flags) int ret = -1; int compressed; - virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE, -1); + virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE | + VIR_DOMAIN_SAVE_RUNNING | + VIR_DOMAIN_SAVE_PAUSED, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -2616,8 +2625,7 @@ qemuDomainManagedSave(virDomainPtr dom, unsigned int flags) compressed = QEMUD_SAVE_FORMAT_RAW; ret = qemuDomainSaveInternal(driver, dom, vm, name, compressed, - (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0, - NULL); + NULL, flags); vm = NULL; cleanup: @@ -3766,15 +3774,16 @@ cleanup: return ret; } -/* Return -1 on failure, -2 if edit was specified but xmlin does not - * represent any changes, and opened fd on all other success. */ +/* Return -1 on failure, -2 if edit was specified but xmlin and state + * (-1 for no change, 0 for paused, 1 for running) do not represent + * any changes, and opened fd on all other success. */ static int ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4) qemuDomainSaveImageOpen(struct qemud_driver *driver, const char *path, virDomainDefPtr *ret_def, struct qemud_save_header *ret_header, bool bypass_cache, virFileDirectFdPtr *directFd, - const char *xmlin, bool edit) + const char *xmlin, int state, bool edit) { int fd; struct qemud_save_header header; @@ -3854,7 +3863,8 @@ qemuDomainSaveImageOpen(struct qemud_driver *driver, goto error; } - if (edit && STREQ(xml, xmlin)) { + if (edit && STREQ(xml, xmlin) && + (state < 0 || state == header.was_running)) { VIR_FREE(xml); if (VIR_CLOSE(fd) < 0) { virReportSystemError(errno, _("cannot close file: %s"), path); @@ -3862,6 +3872,8 @@ qemuDomainSaveImageOpen(struct qemud_driver *driver, } return -2; } + if (state >= 0) + header.was_running = state; /* Create a domain from this XML */ if (!(def = virDomainDefParseString(driver->caps, xml, @@ -4024,14 +4036,22 @@ qemuDomainRestoreFlags(virConnectPtr conn, int ret = -1; struct qemud_save_header header; virFileDirectFdPtr directFd = NULL; + int state = -1; - virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE, -1); + virCheckFlags(VIR_DOMAIN_SAVE_BYPASS_CACHE | + VIR_DOMAIN_SAVE_RUNNING | + VIR_DOMAIN_SAVE_PAUSED, -1); qemuDriverLock(driver); + if (flags & VIR_DOMAIN_SAVE_RUNNING) + state = 1; + else if (flags & VIR_DOMAIN_SAVE_PAUSED) + state = 0; + fd = qemuDomainSaveImageOpen(driver, path, &def, &header, (flags & VIR_DOMAIN_SAVE_BYPASS_CACHE) != 0, - &directFd, dxml, false); + &directFd, dxml, state, false); if (fd < 0) goto cleanup; @@ -4094,7 +4114,7 @@ qemuDomainSaveImageGetXMLDesc(virConnectPtr conn, const char *path, qemuDriverLock(driver); fd = qemuDomainSaveImageOpen(driver, path, &def, &header, false, NULL, - NULL, false); + NULL, -1, false); if (fd < 0) goto cleanup; @@ -4119,13 +4139,20 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, struct qemud_save_header header; char *xml = NULL; size_t len; + int state = -1; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SAVE_RUNNING | + VIR_DOMAIN_SAVE_PAUSED, -1); qemuDriverLock(driver); + if (flags & VIR_DOMAIN_SAVE_RUNNING) + state = 1; + else if (flags & VIR_DOMAIN_SAVE_PAUSED) + state = 0; + fd = qemuDomainSaveImageOpen(driver, path, &def, &header, false, NULL, - dxml, true); + dxml, state, true); if (fd < 0) { /* Check for special case of no change needed. */ @@ -4150,11 +4177,8 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, goto cleanup; } - if (lseek(fd, sizeof(header), SEEK_SET) != sizeof(header)) { - virReportSystemError(errno, _("cannot seek in '%s'"), path); - goto cleanup; - } - if (safewrite(fd, xml, len) != len || + if (safewrite(fd, &header, sizeof(header) != sizeof(header)) || + safewrite(fd, xml, len) != len || VIR_CLOSE(fd) < 0) { virReportSystemError(errno, _("failed to write xml to '%s'"), path); goto cleanup; @@ -4185,7 +4209,7 @@ qemuDomainObjRestore(virConnectPtr conn, virFileDirectFdPtr directFd = NULL; fd = qemuDomainSaveImageOpen(driver, path, &def, &header, - bypass_cache, &directFd, NULL, false); + bypass_cache, &directFd, NULL, -1, false); if (fd < 0) goto cleanup; -- 1.7.4.4

On 08/27/2011 07:16 AM, Eric Blake wrote:
Implement the new running/paused overrides for saved state management.
Unfortunately, for virDomainSaveImageDefineXML, the saved state updates are write-only - I don't know of any way to expose a way to query the current run/pause setting of an existing save image file to the user without adding a new API or modifying the domain xml of virDomainSaveImageGetXMLDesc to include a new element to reflect the state bit encoded into the save image. However, I don't think this is a show-stopper, since the API is designed to leave the state bit alone unless an explicit flag is used to change it.
@@ -4150,11 +4177,8 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, goto cleanup; }
- if (lseek(fd, sizeof(header), SEEK_SET) != sizeof(header)) { - virReportSystemError(errno, _("cannot seek in '%s'"), path); - goto cleanup; - } - if (safewrite(fd, xml, len) != len || + if (safewrite(fd,&header, sizeof(header) != sizeof(header)) || + safewrite(fd, xml, len) != len ||
I need to test this better before posting. A misplaced ), along with the fact that opening the fd does not leave the fd positioned at 0, means I need to squash in this: diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 0498054..8682b98 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4177,7 +4177,11 @@ qemuDomainSaveImageDefineXML(virConnectPtr conn, const char *path, goto cleanup; } - if (safewrite(fd, &header, sizeof(header) != sizeof(header)) || + if (lseek(fd, 0, SEEK_SET) != 0) { + virReportSystemError(errno, _("cannot seek in '%s'"), path); + goto cleanup; + } + if (safewrite(fd, &header, sizeof(header)) != sizeof(header) || safewrite(fd, xml, len) != len || VIR_CLOSE(fd) < 0) { virReportSystemError(errno, _("failed to write xml to '%s'"), path); -- 1.7.4.4 -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Pretty straight-forward exposure of new flags. For most commands, we let the API reject mutually exclusive flags; but for save-image-edit, we do the sanity check ourselves to avoid looping on flag failure if the edit cycle is ever enhanced to allow the user to retry an edit to fix up an xml validation error. * tools/virsh.c (cmdManagedSave, cmdRestore, cmdSave) (cmdSaveImageDefine, cmdSaveImageEdit): Add new flags. * tools/virsh.pod (managedsave, restore, save, save-image-define) (save-image-edit): Document them. --- tools/virsh.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++----- tools/virsh.pod | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 15b9bdd..4426d2b 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1605,6 +1605,8 @@ static const vshCmdOptDef opts_save[] = { {"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("where to save the data")}, {"xml", VSH_OT_STRING, 0, N_("filename containing updated XML for the target")}, + {"running", VSH_OT_BOOL, 0, N_("set domain to be running on restore")}, + {"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on restore")}, {NULL, 0, 0, NULL} }; @@ -1627,6 +1629,10 @@ cmdSave(vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool(cmd, "bypass-cache")) flags |= VIR_DOMAIN_SAVE_BYPASS_CACHE; + if (vshCommandOptBool(cmd, "running")) + flags |= VIR_DOMAIN_SAVE_RUNNING; + if (vshCommandOptBool(cmd, "paused")) + flags |= VIR_DOMAIN_SAVE_PAUSED; if (vshCommandOptString(cmd, "xml", &xmlfile) < 0) { vshError(ctl, "%s", _("malformed xml argument")); @@ -1714,6 +1720,8 @@ static const vshCmdOptDef opts_save_image_define[] = { {"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("saved state file to modify")}, {"xml", VSH_OT_STRING, VSH_OFLAG_REQ, N_("filename containing updated XML for the target")}, + {"running", VSH_OT_BOOL, 0, N_("set domain to be running on restore")}, + {"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on restore")}, {NULL, 0, 0, NULL} }; @@ -1724,6 +1732,12 @@ cmdSaveImageDefine(vshControl *ctl, const vshCmd *cmd) bool ret = false; const char *xmlfile = NULL; char *xml = NULL; + int flags = 0; + + if (vshCommandOptBool(cmd, "running")) + flags |= VIR_DOMAIN_SAVE_RUNNING; + if (vshCommandOptBool(cmd, "paused")) + flags |= VIR_DOMAIN_SAVE_PAUSED; if (!vshConnectionUsability(ctl, ctl->conn)) return false; @@ -1763,6 +1777,8 @@ static const vshCmdInfo info_save_image_edit[] = { static const vshCmdOptDef opts_save_image_edit[] = { {"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("saved state file to edit")}, + {"running", VSH_OT_BOOL, 0, N_("set domain to be running on restore")}, + {"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on restore")}, {NULL, 0, 0, NULL} }; @@ -1774,7 +1790,22 @@ cmdSaveImageEdit(vshControl *ctl, const vshCmd *cmd) char *tmp = NULL; char *doc = NULL; char *doc_edited = NULL; - int flags = VIR_DOMAIN_XML_SECURE; + int getxml_flags = VIR_DOMAIN_XML_SECURE; + int define_flags = 0; + + if (vshCommandOptBool(cmd, "running")) + define_flags |= VIR_DOMAIN_SAVE_RUNNING; + if (vshCommandOptBool(cmd, "paused")) + define_flags |= VIR_DOMAIN_SAVE_PAUSED; + + /* Normally, we let the API reject mutually exclusive flags. + * However, in the edit cycle, we let the user retry if the define + * step fails, but the define step will always fail on invalid + * flags, so we reject it up front to avoid looping. */ + if (define_flags == (VIR_DOMAIN_SAVE_RUNNING | VIR_DOMAIN_SAVE_PAUSED)) { + vshError(ctl, "%s", _("--running and --saved are mutually exclusive")); + return false; + } if (!vshConnectionUsability(ctl, ctl->conn)) return false; @@ -1783,7 +1814,7 @@ cmdSaveImageEdit(vshControl *ctl, const vshCmd *cmd) return false; /* Get the XML configuration of the saved image. */ - doc = virDomainSaveImageGetXMLDesc(ctl->conn, file, flags); + doc = virDomainSaveImageGetXMLDesc(ctl->conn, file, getxml_flags); if (!doc) goto cleanup; @@ -1801,8 +1832,9 @@ cmdSaveImageEdit(vshControl *ctl, const vshCmd *cmd) if (!doc_edited) goto cleanup; - /* Compare original XML with edited. Has it changed at all? */ - if (STREQ(doc, doc_edited)) { + /* Compare original XML with edited. Short-circuit if it did not + * change, and we do not have any flags. */ + if (STREQ(doc, doc_edited) && !define_flags) { vshPrint(ctl, _("Saved image %s XML configuration not changed.\n"), file); ret = true; @@ -1810,7 +1842,8 @@ cmdSaveImageEdit(vshControl *ctl, const vshCmd *cmd) } /* Everything checks out, so redefine the xml. */ - if (virDomainSaveImageDefineXML(ctl->conn, file, doc_edited, 0) < 0) { + if (virDomainSaveImageDefineXML(ctl->conn, file, doc_edited, + define_flags) < 0) { vshError(ctl, _("Failed to update %s"), file); goto cleanup; } @@ -1843,6 +1876,8 @@ static const vshCmdInfo info_managedsave[] = { static const vshCmdOptDef opts_managedsave[] = { {"bypass-cache", VSH_OT_BOOL, 0, N_("avoid file system cache when saving")}, {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"running", VSH_OT_BOOL, 0, N_("set domain to be running on next start")}, + {"paused", VSH_OT_BOOL, 0, N_("set domain to be paused on next start")}, {NULL, 0, 0, NULL} }; @@ -1859,6 +1894,10 @@ cmdManagedSave(vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool(cmd, "bypass-cache")) flags |= VIR_DOMAIN_SAVE_BYPASS_CACHE; + if (vshCommandOptBool(cmd, "running")) + flags |= VIR_DOMAIN_SAVE_RUNNING; + if (vshCommandOptBool(cmd, "paused")) + flags |= VIR_DOMAIN_SAVE_PAUSED; if (!(dom = vshCommandOptDomain(ctl, cmd, &name))) return false; @@ -2202,6 +2241,8 @@ static const vshCmdOptDef opts_restore[] = { N_("avoid file system cache when restoring")}, {"xml", VSH_OT_STRING, 0, N_("filename containing updated XML for the target")}, + {"running", VSH_OT_BOOL, 0, N_("restore domain into running state")}, + {"paused", VSH_OT_BOOL, 0, N_("restore domain into paused state")}, {NULL, 0, 0, NULL} }; @@ -2222,6 +2263,10 @@ cmdRestore(vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool(cmd, "bypass-cache")) flags |= VIR_DOMAIN_SAVE_BYPASS_CACHE; + if (vshCommandOptBool(cmd, "running")) + flags |= VIR_DOMAIN_SAVE_RUNNING; + if (vshCommandOptBool(cmd, "paused")) + flags |= VIR_DOMAIN_SAVE_PAUSED; if (vshCommandOptString(cmd, "xml", &xmlfile) < 0) { vshError(ctl, "%s", _("malformed xml argument")); diff --git a/tools/virsh.pod b/tools/virsh.pod index 81d7a1e..5967d1f 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -546,6 +546,7 @@ The editor used can be supplied by the C<$VISUAL> or C<$EDITOR> environment variables, and defaults to C<vi>. =item B<managedsave> I<domain-id> [I<--bypass-cache>] +[{I<--running> | I<--paused>}] Save and destroy (stop) a running domain, so it can be restarted from the same state at a later time. When the virsh B<start> command is next run for @@ -553,6 +554,11 @@ the domain, it will automatically be started from this saved state. If I<--bypass-cache> is specified, the save will avoid the file system cache, although this may slow down the operation. +Normally, starting a managed save will decide between running or paused +based on the state the domain was in when the save was done; passing +either the I<--running> or I<--paused> flag will allow overriding which +state the B<start> should use. + The B<dominfo> command can be used to query whether a domain currently has any managed save image. @@ -635,6 +641,7 @@ The exact behavior of a domain when it reboots is set by the I<on_reboot> parameter in the domain's XML definition. =item B<restore> I<state-file> [I<--bypass-cache>] [I<--xml> B<file>] +[{I<--running> | I<--paused>}] Restores a domain from a B<virsh save> state file. See I<save> for more info. @@ -647,12 +654,18 @@ in the host-specific portions of the domain XML. For example, it can be used to account for file naming differences in underlying storage due to disk snapshots taken after the guest was saved. +Normally, restoring a saved image will use the state recorded in the +save image to decide between running or paused; passing either the +I<--running> or I<--paused> flag will allow overriding which state the +domain should be started in. + B<Note>: To avoid corrupting file system contents within the domain, you should not reuse the saved state file for a second B<restore> unless you have also reverted all storage volumes back to the same contents as when the state file was created. =item B<save> I<domain-id> I<state-file> [I<--bypass-cache>] [I<--xml> B<file>] +[{I<--running> | I<--paused>}] Saves a running domain (RAM, but not disk state) to a state file so that it can be restored @@ -672,12 +685,17 @@ in the host-specific portions of the domain XML. For example, it can be used to account for file naming differences that are planned to be made via disk snapshots of underlying storage after the guest is saved. +Normally, restoring a saved image will decide between running or paused +based on the state the domain was in when the save was done; passing +either the I<--running> or I<--paused> flag will allow overriding which +state the B<restore> should use. + Domain saved state files assume that disk images will be unchanged between the creation and restore point. For a more complete system restore point, where the disk state is saved alongside the memory state, see the B<snapshot> family of commands. -=item B<save-image-define> I<file> I<xml> +=item B<save-image-define> I<file> I<xml> [{I<--running> | I<--paused>}] Update the domain XML that will be used when I<file> is later used in the B<restore> command. The I<xml> argument must be a file @@ -686,17 +704,27 @@ host-specific portions of the domain XML. For example, it can be used to account for file naming differences resulting from creating disk snapshots of underlying storage after the guest was saved. +The save image records whether the domain should be restored to a +running or paused state. Normally, this command does not alter the +recorded state; passing either the I<--running> or I<--paused> flag +will allow overriding which state the B<restore> should use. + =item B<save-image-dumpxml> I<file> [I<--security-info>] Extract the domain XML that was in effect at the time the saved state file I<file> was created with the B<save> command. Using I<--security-info> will also include security sensitive information. -=item B<save-image-edit> I<file> +=item B<save-image-edit> I<file> [{I<--running> | I<--paused>}] Edit the XML configuration associated with a saved state file I<file> created by the B<save> command. +The save image records whether the domain should be restored to a +running or paused state. Normally, this command does not alter the +recorded state; passing either the I<--running> or I<--paused> flag +will allow overriding which state the B<restore> should use. + This is equivalent to: virsh save-image-dumpxml state-file > state-file.xml -- 1.7.4.4

On 08/27/2011 08:25 AM, Eric Blake wrote:
Pretty straight-forward exposure of new flags. For most commands, we let the API reject mutually exclusive flags; but for save-image-edit, we do the sanity check ourselves to avoid looping on flag failure if the edit cycle is ever enhanced to allow the user to retry an edit to fix up an xml validation error.
* tools/virsh.c (cmdManagedSave, cmdRestore, cmdSave) (cmdSaveImageDefine, cmdSaveImageEdit): Add new flags. * tools/virsh.pod (managedsave, restore, save, save-image-define) (save-image-edit): Document them. --- tools/virsh.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++----- tools/virsh.pod | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 7 deletions(-)
Missed a couple. I'm squashing this in, for my v4 posting. diff --git i/tools/virsh.c w/tools/virsh.c index 4426d2b..6131ec6 100644 --- i/tools/virsh.c +++ w/tools/virsh.c @@ -12357,6 +12357,8 @@ static const vshCmdInfo info_snapshot_revert[] = { static const vshCmdOptDef opts_snapshot_revert[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")}, + {"running", VSH_OT_BOOL, 0, N_("after reverting, change state to running")}, + {"paused", VSH_OT_BOOL, 0, N_("after reverting, change state to paused")}, {NULL, 0, 0, NULL} }; @@ -12367,6 +12369,12 @@ cmdDomainSnapshotRevert(vshControl *ctl, const vshCmd *cmd) bool ret = false; const char *name = NULL; virDomainSnapshotPtr snapshot = NULL; + int flags = 0; + + if (vshCommandOptBool(cmd, "running")) + flags |= VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING; + if (vshCommandOptBool(cmd, "paused")) + flags |= VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12382,7 +12390,7 @@ cmdDomainSnapshotRevert(vshControl *ctl, const vshCmd *cmd) if (snapshot == NULL) goto cleanup; - if (virDomainRevertToSnapshot(snapshot, 0) < 0) + if (virDomainRevertToSnapshot(snapshot, flags) < 0) goto cleanup; ret = true; diff --git i/tools/virsh.pod w/tools/virsh.pod index 5967d1f..69af408 100644 --- i/tools/virsh.pod +++ w/tools/virsh.pod @@ -1627,14 +1627,22 @@ Output the snapshot XML for the domain's snapshot named I<snapshot>. Output the name of the parent snapshot for the given I<snapshot>, if any. -=item B<snapshot-revert> I<domain> I<snapshot> +=item B<snapshot-revert> I<domain> I<snapshot> [{I<--running> | I<--paused>}] Revert the given domain to the snapshot specified by I<snapshot>. Be aware -that this is a destructive action; any changes in the domain since the +that this is a destructive action; any changes in the domain since the last snapshot was taken will be lost. Also note that the state of the domain after snapshot-revert is complete will be the state of the domain at the time the original snapshot was taken. +Normally, reverting to a snapshot leaves the domain in the state it was +at the time the snapshot was created, except that a disk snapshot with +no vm state leaves the domain in an inactive state. Passing either the +I<--running> or I<--paused> flag will perform additional state changes +(such as booting an inactive domain, or pausing a running domain). Since +transient domains cannot be inactive, it is required to use one of these +flags when reverting to a disk snapshot of a transient domain. + =item B<snapshot-delete> I<domain> I<snapshot> I<--children> Delete the snapshot for the domain named I<snapshot>. If this snapshot -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Commit 6766ff10 introduced a corner case bug with snapshot creation: if a snapshot is created, but then we hit OOM while trying to create the return value of the function, then we have polluted the internal directory with the snapshot metadata with no way to clean it up from the running libvirtd. * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Don't write metadata file on OOM condition. --- src/qemu/qemu_driver.c | 15 +++++++++------ 1 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index cce11c3..77b7609 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8583,18 +8583,21 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, * do; we've successfully taken the snapshot, and we are now running * on it, so we have to go forward the best we can */ - if (qemuDomainSnapshotWriteMetadata(vm, snap, driver->snapshotDir) < 0) - goto cleanup; - vm->current_snapshot = snap; snapshot = virGetDomainSnapshot(domain, snap->def->name); cleanup: if (vm) { - if (snapshot) - vm->current_snapshot = snap; - else if (snap) + if (snapshot) { + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->snapshotDir) < 0) + VIR_WARN("unable to save metadata for snapshot %s", + snap->def->name); + else + vm->current_snapshot = snap; + } else if (snap) { virDomainSnapshotObjListRemove(&vm->snapshots, snap); + } virDomainObjUnlock(vm); } virDomainSnapshotDefFree(def); -- 1.7.4.4

If you take a checkpoint snapshot of a running domain, then pause qemu, then restore the snapshot, the result should be a running domain, but the code was leaving things paused. Furthermore, if you take a checkpoint of a paused domain, then run, then restore, there was a brief but non-deterministic window of time where the domain was running rather than paused. Fix both of these discrepancies by always pausing before restoring. Also, check that the VM is active every time lock is dropped between two monitor calls. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Always pause before reversion. --- src/qemu/qemu_driver.c | 61 +++++++++++++++++++++++++++++++++-------------- 1 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 12f8179..7950aff 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8776,44 +8776,69 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, if (snap->def->state == VIR_DOMAIN_RUNNING || snap->def->state == VIR_DOMAIN_PAUSED) { - + /* When using the loadvm monitor command, qemu does not know + * whether to pause or run the reverted domain, and just stays + * in the same state as before the monitor command, whether + * that is paused or running. We always pause before loadvm, + * to have finer control. */ if (virDomainObjIsActive(vm)) { priv = vm->privateData; + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + if (qemuProcessStopCPUs(driver, vm, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_NONE) < 0) + goto endjob; + /* Create an event now in case the restore fails, so + * that user will be alerted that they are now paused. + * If restore later succeeds to a running state, we + * replace this event with another. */ + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + } qemuDomainObjEnterMonitorWithDriver(driver, vm); rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name); qemuDomainObjExitMonitorWithDriver(driver, vm); - if (rc < 0) + if (rc < 0) { + /* XXX resume domain if it was running before the + * failed loadvm attempt? */ goto endjob; + } } else { if (qemuDomainSnapshotSetCurrentActive(vm, driver->snapshotDir) < 0) goto endjob; rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL, - false, false, -1, NULL, VIR_VM_OP_CREATE); + true, false, -1, NULL, VIR_VM_OP_CREATE); virDomainAuditStart(vm, "from-snapshot", rc >= 0); + if (rc < 0) + goto endjob; if (qemuDomainSnapshotSetCurrentInactive(vm, driver->snapshotDir) < 0) goto endjob; - if (rc < 0) - goto endjob; } + /* Touch up domain state. */ if (snap->def->state == VIR_DOMAIN_PAUSED) { - /* qemu unconditionally starts the domain running again after - * loadvm, so let's pause it to keep consistency - * XXX we should have used qemuProcessStart's start_paused instead - */ - rc = qemuProcessStopCPUs(driver, vm, - VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, - QEMU_ASYNC_JOB_NONE); + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); + } else { + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + rc = qemuProcessStartCPUs(driver, vm, snapshot->domain->conn, + VIR_DOMAIN_RUNNING_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_NONE); if (rc < 0) goto endjob; - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); - } else { - virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, - VIR_DOMAIN_RUNNING_FROM_SNAPSHOT); + virDomainEventFree(event); event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:19AM -0600, Eric Blake wrote:
If you take a checkpoint snapshot of a running domain, then pause qemu, then restore the snapshot, the result should be a running domain, but the code was leaving things paused. Furthermore, if you take a checkpoint of a paused domain, then run, then restore, there was a brief but non-deterministic window of time where the domain was running rather than paused. Fix both of these discrepancies by always pausing before restoring.
Also, check that the VM is active every time lock is dropped between two monitor calls.
* src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Always pause before reversion.
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

If you take a checkpoint snapshot of a running domain, then pause qemu, then restore the snapshot, the result should be a running domain, but the code was leaving things paused. Furthermore, if you take a checkpoint of a paused domain, then run, then restore, there was a brief but non-deterministic window of time where the domain was running rather than paused. Fix both of these discrepancies by always pausing before restoring. Also, check that the VM is active every time lock is dropped between two monitor calls. Finally, straighten out the events that get emitted on each transition. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Always pause before reversion, and improve events. --- v2: rebase state transitions on top of updated patch 1/43, and add lots of helpful comments :) src/qemu/qemu_driver.c | 114 +++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 94 insertions(+), 20 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 4ae6128..0498054 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8803,11 +8803,25 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, char uuidstr[VIR_UUID_STRING_BUFLEN]; virDomainEventPtr event = NULL; virDomainEventPtr event2 = NULL; + int detail; qemuDomainObjPrivatePtr priv; int rc; virCheckFlags(0, -1); + /* We have the following transitions, which create the following events: + * 1. inactive -> inactive: none + * 2. inactive -> running: EVENT_STARTED + * 3. inactive -> paused: EVENT_STARTED, EVENT_PAUSED + * 4. running -> inactive: EVENT_STOPPED + * 5. running -> running: none + * 6. running -> paused: EVENT_PAUSED + * 7. paused -> inactive: EVENT_STOPPED + * 8. paused -> running: EVENT_RESUMED + * 9. paused -> paused: none + * Also, several transitions occur even if we fail partway through. + */ + qemuDriverLock(driver); virUUIDFormat(snapshot->domain->uuid, uuidstr); vm = virDomainFindByUUID(&driver->domains, snapshot->domain->uuid); @@ -8842,44 +8856,102 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, if (snap->def->state == VIR_DOMAIN_RUNNING || snap->def->state == VIR_DOMAIN_PAUSED) { - + /* Transitions 2, 3, 5, 6, 8, 9 */ + bool was_running = false; + bool was_stopped = false; + + /* When using the loadvm monitor command, qemu does not know + * whether to pause or run the reverted domain, and just stays + * in the same state as before the monitor command, whether + * that is paused or running. We always pause before loadvm, + * to have finer control. */ if (virDomainObjIsActive(vm)) { + /* Transitions 5, 6, 8, 9 */ priv = vm->privateData; + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + /* Transitions 5, 6 */ + was_running = true; + if (qemuProcessStopCPUs(driver, vm, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_NONE) < 0) + goto endjob; + /* Create an event now in case the restore fails, so + * that user will be alerted that they are now paused. + * If restore later succeeds, we might replace this. */ + detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + } qemuDomainObjEnterMonitorWithDriver(driver, vm); rc = qemuMonitorLoadSnapshot(priv->mon, snap->def->name); qemuDomainObjExitMonitorWithDriver(driver, vm); - if (rc < 0) + if (rc < 0) { + /* XXX resume domain if it was running before the + * failed loadvm attempt? */ goto endjob; + } } else { + /* Transitions 2, 3 */ + was_stopped = true; rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL, - false, false, -1, NULL, snap, + true, false, -1, NULL, snap, VIR_VM_OP_CREATE); virDomainAuditStart(vm, "from-snapshot", rc >= 0); + detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); if (rc < 0) goto endjob; } - event = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_STARTED, - VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); + /* Touch up domain state. */ if (snap->def->state == VIR_DOMAIN_PAUSED) { - /* qemu unconditionally starts the domain running again after - * loadvm, so let's pause it to keep consistency - * XXX we should have used qemuProcessStart's start_paused instead - */ - rc = qemuProcessStopCPUs(driver, vm, - VIR_DOMAIN_PAUSED_FROM_SNAPSHOT, - QEMU_ASYNC_JOB_NONE); + /* Transitions 3, 6, 9 */ + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); + if (was_stopped) { + /* Transition 3, use event as-is and add event2 */ + detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + } /* else transition 6 and 9 use event as-is */ + } else { + /* Transitions 2, 5, 8 */ + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto endjob; + } + rc = qemuProcessStartCPUs(driver, vm, snapshot->domain->conn, + VIR_DOMAIN_RUNNING_FROM_SNAPSHOT, + QEMU_ASYNC_JOB_NONE); if (rc < 0) goto endjob; - event2 = virDomainEventNewFromObj(vm, - VIR_DOMAIN_EVENT_SUSPENDED, - VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT); - } else { - virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, - VIR_DOMAIN_RUNNING_FROM_SNAPSHOT); + virDomainEventFree(event); + if (was_stopped) { + /* Transition 2 */ + detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + } else if (was_running) { + /* Transition 8 */ + detail = VIR_DOMAIN_EVENT_RESUMED; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_RESUMED, + detail); + } } } else { + /* Transitions 1, 4, 7 */ /* qemu is a little funny with running guests and the restoration * of snapshots. If the snapshot was taken online, * then after a "loadvm" monitor command, the VM is set running @@ -8891,11 +8963,13 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, */ if (virDomainObjIsActive(vm)) { + /* Transitions 4, 7 */ qemuProcessStop(driver, vm, 0, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT); virDomainAuditStop(vm, "from-snapshot"); + detail = VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT; event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, - VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + detail); if (!vm->persistent) { if (qemuDomainObjEndJob(driver, vm) > 0) virDomainRemoveInactive(&driver->domains, vm); -- 1.7.4.4

qemuDomainSnapshotRevertInactive has the same FIXMEs as qemuDomainSnapshotCreateInactive, so algorithmic fixes to properly handle partial loop iterations should be applied later to both functions, but we're not making the situation any worse in this patch. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Use qemu-img rather than 'qemu -loadvm' to revert to offline snapshot. (qemuDomainSnapshotRevertInactive): New helper. --- src/qemu/qemu_driver.c | 65 +++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 7950aff..1caa870 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8738,6 +8738,52 @@ cleanup: return xml; } +/* The domain is expected to be locked and inactive. */ +static int +qemuDomainSnapshotRevertInactive(virDomainObjPtr vm, + virDomainSnapshotObjPtr snap) +{ + const char *qemuimgarg[] = { NULL, "snapshot", "-a", NULL, NULL, NULL }; + int ret = -1; + int i; + + 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 */ + /* FIXME: if we fail halfway through this loop, we are in an + * inconsistent state. I'm not quite sure what to do about that + */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support" + " snapshotting"), + vm->def->disks[i]->info.alias); + goto cleanup; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) + goto cleanup; + } + } + + ret = 0; + +cleanup: + VIR_FREE(qemuimgarg[0]); + return ret; +} + static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, unsigned int flags) { @@ -8844,14 +8890,9 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); } } else { - /* qemu is a little funny with running guests and the restoration - * of snapshots. If the snapshot was taken online, - * then after a "loadvm" monitor command, the VM is set running - * again. If the snapshot was taken offline, then after a "loadvm" - * monitor command the VM is left paused. Unpausing it leads to - * the memory state *before* the loadvm with the disk *after* the - * loadvm, which obviously is bound to corrupt something. - * Therefore we destroy the domain and set it to "off" in this case. + /* Newer qemu -loadvm refuses to revert to the state of a snapshot + * created by qemu-img snapshot -c. If the domain is running, we + * must take it offline; then do the revert using qemu-img. */ if (virDomainObjIsActive(vm)) { @@ -8861,6 +8902,12 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); if (!vm->persistent) { + /* XXX it should be impossible to create an offline snapshot + * of a transient domain. Once we fix 'undefine' to convert + * a defined domain back to transient, that transition should + * be rejected if any offline snapshots exist. For now, we + * just stop the transient domain and quit, without reverting + * any disk state. */ if (qemuDomainObjEndJob(driver, vm) > 0) virDomainRemoveInactive(&driver->domains, vm); vm = NULL; @@ -8868,7 +8915,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, } } - if (qemuDomainSnapshotSetCurrentActive(vm, driver->snapshotDir) < 0) + if (qemuDomainSnapshotRevertInactive(vm, snap) < 0) goto endjob; } -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:20AM -0600, Eric Blake wrote:
qemuDomainSnapshotRevertInactive has the same FIXMEs as qemuDomainSnapshotCreateInactive, so algorithmic fixes to properly handle partial loop iterations should be applied later to both functions, but we're not making the situation any worse in this patch.
* src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Use qemu-img rather than 'qemu -loadvm' to revert to offline snapshot. (qemuDomainSnapshotRevertInactive): New helper. --- src/qemu/qemu_driver.c | 65 +++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 7950aff..1caa870 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8738,6 +8738,52 @@ cleanup: return xml; }
+/* The domain is expected to be locked and inactive. */ +static int +qemuDomainSnapshotRevertInactive(virDomainObjPtr vm, + virDomainSnapshotObjPtr snap) +{ + const char *qemuimgarg[] = { NULL, "snapshot", "-a", NULL, NULL, NULL }; + int ret = -1; + int i; + + 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 */ + /* FIXME: if we fail halfway through this loop, we are in an + * inconsistent state. I'm not quite sure what to do about that + */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support" + " snapshotting"), + vm->def->disks[i]->info.alias); + goto cleanup; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) + goto cleanup; + } + } + + ret = 0; + +cleanup: + VIR_FREE(qemuimgarg[0]); + return ret; +}
I know that virRun is implemented in terms of virCommand, but should be we aiming to convert virRun uses over to directly use virCommand ?
@@ -8844,14 +8890,9 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT); } } else { - /* qemu is a little funny with running guests and the restoration - * of snapshots. If the snapshot was taken online, - * then after a "loadvm" monitor command, the VM is set running - * again. If the snapshot was taken offline, then after a "loadvm" - * monitor command the VM is left paused. Unpausing it leads to - * the memory state *before* the loadvm with the disk *after* the - * loadvm, which obviously is bound to corrupt something. - * Therefore we destroy the domain and set it to "off" in this case. + /* Newer qemu -loadvm refuses to revert to the state of a snapshot + * created by qemu-img snapshot -c. If the domain is running, we + * must take it offline; then do the revert using qemu-img. */
if (virDomainObjIsActive(vm)) { @@ -8861,6 +8902,12 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); if (!vm->persistent) { + /* XXX it should be impossible to create an offline snapshot + * of a transient domain. Once we fix 'undefine' to convert + * a defined domain back to transient, that transition should + * be rejected if any offline snapshots exist. For now, we + * just stop the transient domain and quit, without reverting + * any disk state. */ if (qemuDomainObjEndJob(driver, vm) > 0) virDomainRemoveInactive(&driver->domains, vm); vm = NULL; @@ -8868,7 +8915,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, } }
- if (qemuDomainSnapshotSetCurrentActive(vm, driver->snapshotDir) < 0) + if (qemuDomainSnapshotRevertInactive(vm, snap) < 0) goto endjob; }
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 08/24/2011 10:10 AM, Daniel P. Berrange wrote:
On Wed, Aug 24, 2011 at 09:22:20AM -0600, Eric Blake wrote:
+{ + const char *qemuimgarg[] = { NULL, "snapshot", "-a", NULL, NULL, NULL };
+ for (i = 0; i< vm->def->ndisks; i++) {
+ + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL)< 0)
I know that virRun is implemented in terms of virCommand, but should be we aiming to convert virRun uses over to directly use virCommand ?
Here, virRun really is nicer, because it is the only interface that lets us modify _just_ qemuimgarg[4] without having to alter the earlier array elements. If we directly used virCommand, we'd have to start from scratch each time around, or add a new virCommand interface that would let us delete or otherwise modify arguments previously added. That, and this new code was merely copying after other functions that did qemu-img snapshot -c and qemu-img snapshot -d for multiple files.
ACK
Daniel
-- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Commit 5e47785 broke reverts to offline system checkpoint snapshots with older qemu, since there is no longer any code path to use qemu -loadvm on next boot. Meanwhile, reverts to offline system checkpoints have been broken for newer qemu, both before and after that commit, since -loadvm no longer works to revert to disk state without accompanying vm state. Fix both of these by using qemu-img to revert disk state. qemuDomainSnapshotRevertInactive has the same FIXMEs as qemuDomainSnapshotCreateInactive, so algorithmic fixes to properly handle partial loop iterations should be applied later to both functions, but we're not making the situation any worse in this patch. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Use qemu-img rather than 'qemu -loadvm' to revert to offline snapshot. (qemuDomainSnapshotRevertInactive): New helper. --- v3: tweaks to commit message, and minor changes to rebase on updated 1/43 and 2/43 src/qemu/qemu_driver.c | 64 ++++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8682b98..6f6447e 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8797,6 +8797,52 @@ cleanup: return xml; } +/* The domain is expected to be locked and inactive. */ +static int +qemuDomainSnapshotRevertInactive(virDomainObjPtr vm, + virDomainSnapshotObjPtr snap) +{ + const char *qemuimgarg[] = { NULL, "snapshot", "-a", NULL, NULL, NULL }; + int ret = -1; + int i; + + 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 */ + /* FIXME: if we fail halfway through this loop, we are in an + * inconsistent state. I'm not quite sure what to do about that + */ + if (vm->def->disks[i]->device == VIR_DOMAIN_DISK_DEVICE_DISK) { + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("Disk device '%s' does not support" + " snapshotting"), + vm->def->disks[i]->info.alias); + goto cleanup; + } + + qemuimgarg[4] = vm->def->disks[i]->src; + + if (virRun(qemuimgarg, NULL) < 0) + goto cleanup; + } + } + + ret = 0; + +cleanup: + VIR_FREE(qemuimgarg[0]); + return ret; +} + static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, unsigned int flags) { @@ -8956,14 +9002,9 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, } } else { /* Transitions 1, 4, 7 */ - /* qemu is a little funny with running guests and the restoration - * of snapshots. If the snapshot was taken online, - * then after a "loadvm" monitor command, the VM is set running - * again. If the snapshot was taken offline, then after a "loadvm" - * monitor command the VM is left paused. Unpausing it leads to - * the memory state *before* the loadvm with the disk *after* the - * loadvm, which obviously is bound to corrupt something. - * Therefore we destroy the domain and set it to "off" in this case. + /* Newer qemu -loadvm refuses to revert to the state of a snapshot + * created by qemu-img snapshot -c. If the domain is running, we + * must take it offline; then do the revert using qemu-img. */ if (virDomainObjIsActive(vm)) { @@ -8975,12 +9016,19 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, VIR_DOMAIN_EVENT_STOPPED, detail); if (!vm->persistent) { + /* XXX For transient domains, enforce that one of the + * flags is set to get domain running again. For now, + * reverting to offline on a transient domain ends up + * killing the domain, without reverting any disk state. */ if (qemuDomainObjEndJob(driver, vm) > 0) virDomainRemoveInactive(&driver->domains, vm); vm = NULL; goto cleanup; } } + + if (qemuDomainSnapshotRevertInactive(vm, snap) < 0) + goto endjob; } ret = 0; -- 1.7.4.4

For a system checkpoint of a running or paused domain, it's fairly easy to honor new flags for altering which state to use after the revert. For an inactive snapshot, the revert has to be done while there is no qemu process, so do back-to-back transitions; this also lets us revert to inactive snapshots even for transient domains. * src/qemu/qemu_driver.c (qemuDomainRevertToSnapshot): Support new flags. --- src/qemu/qemu_driver.c | 59 +++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 6f6447e..094455f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8857,7 +8857,8 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuDomainObjPrivatePtr priv; int rc; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED, -1); /* We have the following transitions, which create the following events: * 1. inactive -> inactive: none @@ -8889,6 +8890,17 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto cleanup; } + if (!vm->persistent && + snap->def->state != VIR_DOMAIN_RUNNING && + snap->def->state != VIR_DOMAIN_PAUSED && + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) == 0) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("transient domain needs to request run or pause " + "to revert to inactive snapshot")); + goto cleanup; + } + if (vm->current_snapshot) { vm->current_snapshot->def->current = false; if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, @@ -8962,7 +8974,9 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, } /* Touch up domain state. */ - if (snap->def->state == VIR_DOMAIN_PAUSED) { + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) && + (snap->def->state == VIR_DOMAIN_PAUSED || + (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) { /* Transitions 3, 6, 9 */ virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_FROM_SNAPSHOT); @@ -9015,20 +9029,49 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, detail); + } + + if (qemuDomainSnapshotRevertInactive(vm, snap) < 0) { if (!vm->persistent) { - /* XXX For transient domains, enforce that one of the - * flags is set to get domain running again. For now, - * reverting to offline on a transient domain ends up - * killing the domain, without reverting any disk state. */ if (qemuDomainObjEndJob(driver, vm) > 0) virDomainRemoveInactive(&driver->domains, vm); vm = NULL; goto cleanup; } + goto endjob; } - if (qemuDomainSnapshotRevertInactive(vm, snap) < 0) - goto endjob; + if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { + /* Flush first event, now do transition 2 or 3 */ + bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0; + + if (event) + qemuDomainEventQueue(driver, event); + rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL, + paused, false, -1, NULL, NULL, + VIR_VM_OP_CREATE); + virDomainAuditStart(vm, "from-snapshot", rc >= 0); + if (rc < 0) { + if (!vm->persistent) { + if (qemuDomainObjEndJob(driver, vm) > 0) + virDomainRemoveInactive(&driver->domains, vm); + vm = NULL; + goto cleanup; + } + goto endjob; + } + detail = VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT; + event = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_STARTED, + detail); + if (paused) { + detail = VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT; + event2 = virDomainEventNewFromObj(vm, + VIR_DOMAIN_EVENT_SUSPENDED, + detail); + } + } } ret = 0; -- 1.7.4.4

https://bugzilla.redhat.com/show_bug.cgi?id=727709 mentions that if qemu fails to create the snapshot (such as what happens on Fedora 15 qemu, which has qmp but where savevm is only in hmp, and where libvirt is old enough to not try the hmp fallback), then 'virsh snapshot-list dom' will show a garbage snapshot entry, and the libvirt internal directory for storing snapshot metadata will have a bogus file. This fixes the fallout bug of polluting the snapshot-list with garbage on failure (the root cause of the F15 bug of not having fallback to hmp has already been fixed in newer libvirt releases). * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Allocate memory before making snapshot, and cleanup on failure. Don't dereference NULL if transient domain exited during snapshot creation. --- src/qemu/qemu_driver.c | 43 ++++++++++++++++++++++++------------------- 1 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 1caa870..d712bf2 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8459,8 +8459,12 @@ cleanup: _("resuming after snapshot failed")); } - if (qemuDomainObjEndJob(driver, vm) == 0) + if (qemuDomainObjEndJob(driver, vm) == 0) { + /* Only possible if a transient vm quit while our locks were down, + * in which case we don't want to save snapshot metadata. */ *vmptr = NULL; + ret = -1; + } return ret; } @@ -8474,7 +8478,7 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, virDomainSnapshotObjPtr snap = NULL; virDomainSnapshotPtr snapshot = NULL; char uuidstr[VIR_UUID_STRING_BUFLEN]; - virDomainSnapshotDefPtr def; + virDomainSnapshotDefPtr def = NULL; virCheckFlags(0, NULL); @@ -8502,8 +8506,17 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) goto cleanup; + def = NULL; snap->def->state = virDomainObjGetState(vm, NULL); + if (vm->current_snapshot) { + snap->def->parent = strdup(vm->current_snapshot->def->name); + if (snap->def->parent == NULL) { + virReportOOMError(); + goto cleanup; + } + vm->current_snapshot = NULL; + } /* actually do the snapshot */ if (!virDomainObjIsActive(vm)) { @@ -8515,32 +8528,24 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, goto cleanup; } - /* FIXME: if we fail after this point, there's not a whole lot we can + /* If we fail after this point, there's not a whole lot we can * do; we've successfully taken the snapshot, and we are now running * on it, so we have to go forward the best we can */ - - if (vm->current_snapshot) { - def->parent = strdup(vm->current_snapshot->def->name); - if (def->parent == NULL) { - virReportOOMError(); - goto cleanup; - } - } - - /* Now we set the new current_snapshot for the domain */ - vm->current_snapshot = snap; - - if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, - driver->snapshotDir) < 0) - /* qemuDomainSnapshotWriteMetadata set the error */ + if (qemuDomainSnapshotWriteMetadata(vm, snap, driver->snapshotDir) < 0) goto cleanup; snapshot = virGetDomainSnapshot(domain, snap->def->name); cleanup: - if (vm) + if (vm) { + if (snapshot) + vm->current_snapshot = snap; + else if (snap) + virDomainSnapshotObjListRemove(&vm->snapshots, snap); virDomainObjUnlock(vm); + } + virDomainSnapshotDefFree(def); qemuDriverUnlock(driver); return snapshot; } -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:21AM -0600, Eric Blake wrote:
https://bugzilla.redhat.com/show_bug.cgi?id=727709 mentions that if qemu fails to create the snapshot (such as what happens on Fedora 15 qemu, which has qmp but where savevm is only in hmp, and where libvirt is old enough to not try the hmp fallback), then 'virsh snapshot-list dom' will show a garbage snapshot entry, and the libvirt internal directory for storing snapshot metadata will have a bogus file.
This fixes the fallout bug of polluting the snapshot-list with garbage on failure (the root cause of the F15 bug of not having fallback to hmp has already been fixed in newer libvirt releases).
* src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Allocate memory before making snapshot, and cleanup on failure. Don't dereference NULL if transient domain exited during snapshot creation. --- src/qemu/qemu_driver.c | 43 ++++++++++++++++++++++++------------------- 1 files changed, 24 insertions(+), 19 deletions(-)
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 08/24/2011 10:11 AM, Daniel P. Berrange wrote:
On Wed, Aug 24, 2011 at 09:22:21AM -0600, Eric Blake wrote:
https://bugzilla.redhat.com/show_bug.cgi?id=727709 mentions that if qemu fails to create the snapshot (such as what happens on Fedora 15 qemu, which has qmp but where savevm is only in hmp, and where libvirt is old enough to not try the hmp fallback), then 'virsh snapshot-list dom' will show a garbage snapshot entry, and the libvirt internal directory for storing snapshot metadata will have a bogus file.
This fixes the fallout bug of polluting the snapshot-list with garbage on failure (the root cause of the F15 bug of not having fallback to hmp has already been fixed in newer libvirt releases).
* src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Allocate memory before making snapshot, and cleanup on failure. Don't dereference NULL if transient domain exited during snapshot creation. --- src/qemu/qemu_driver.c | 43 ++++++++++++++++++++++++------------------- 1 files changed, 24 insertions(+), 19 deletions(-)
ACK
I've now pushed this patch. Even though 2 and 3 were ack'ed, I'll wait to push them until I fix patch 1 (since otherwise it causes me some rebase conflicts). Where do I stand on getting reviews for the rest of the series, even as I continue to fix the problems indentified so far in the review? -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Changing the current vm, and writing that change to the file system, all before a new qemu starts, is risky; it's hard to roll back if starting the new qemu fails for some reason. Instead of abusing vm->current_snapshot and making the command line generator decide whether the current snapshot warrants using -loadvm, it is better to just directly pass a snapshot all the way through the call chain if it is to be loaded. This frees up the last use of snapshot->def->active for qemu's use, so the next patch can repurpose that field for tracking which snapshot is current. * src/qemu/qemu_command.c (qemuBuildCommandLine): Don't use active field of snapshot. * src/qemu/qemu_process.c (qemuProcessStart): Add a parameter. * src/qemu/qemu_process.h (qemuProcessStart): Update prototype. * src/qemu/qemu_migration.c (qemuMigrationPrepareAny): Update callers. * src/qemu/qemu_driver.c (qemudDomainCreate) (qemuDomainSaveImageStartVM, qemuDomainObjStart) (qemuDomainRevertToSnapshot): Likewise. (qemuDomainSnapshotSetCurrentActive) (qemuDomainSnapshotSetCurrentInactive): Delete unused functions. --- src/qemu/qemu_command.c | 7 ++--- src/qemu/qemu_driver.c | 47 ++++---------------------------------------- src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_process.c | 10 +------- src/qemu/qemu_process.h | 1 + 5 files changed, 12 insertions(+), 55 deletions(-) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 287ad8d..61fc2c7 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -2866,7 +2866,7 @@ qemuBuildCommandLine(virConnectPtr conn, virBitmapPtr qemuCaps, const char *migrateFrom, int migrateFd, - virDomainSnapshotObjPtr current_snapshot, + virDomainSnapshotObjPtr snapshot, enum virVMOperationType vmop) { int i; @@ -4782,9 +4782,8 @@ qemuBuildCommandLine(virConnectPtr conn, } } - if (current_snapshot && current_snapshot->def->active) - virCommandAddArgList(cmd, "-loadvm", current_snapshot->def->name, - NULL); + if (snapshot) + virCommandAddArgList(cmd, "-loadvm", snapshot->def->name, NULL); if (def->namespaceData) { qemuDomainCmdlineDefPtr qemucmd; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d712bf2..c4183de 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -804,12 +804,6 @@ qemudShutdown(void) { } -static int qemuDomainSnapshotSetCurrentActive(virDomainObjPtr vm, - char *snapshotDir); -static int qemuDomainSnapshotSetCurrentInactive(virDomainObjPtr vm, - char *snapshotDir); - - static virDrvOpenStatus qemudOpen(virConnectPtr conn, virConnectAuthPtr auth ATTRIBUTE_UNUSED, unsigned int flags) @@ -1297,7 +1291,7 @@ static virDomainPtr qemudDomainCreate(virConnectPtr conn, const char *xml, if (qemuProcessStart(conn, driver, vm, NULL, (flags & VIR_DOMAIN_START_PAUSED) != 0, (flags & VIR_DOMAIN_START_AUTODESTROY) != 0, - -1, NULL, VIR_VM_OP_CREATE) < 0) { + -1, NULL, NULL, VIR_VM_OP_CREATE) < 0) { virDomainAuditStart(vm, "booted", false); if (qemuDomainObjEndJob(driver, vm) > 0) virDomainRemoveInactive(&driver->domains, @@ -3920,7 +3914,7 @@ qemuDomainSaveImageStartVM(virConnectPtr conn, /* Set the migration source and start it up. */ ret = qemuProcessStart(conn, driver, vm, "stdio", true, - false, *fd, path, VIR_VM_OP_RESTORE); + false, *fd, path, NULL, VIR_VM_OP_RESTORE); if (intermediatefd != -1) { if (ret < 0) { @@ -4462,7 +4456,7 @@ qemuDomainObjStart(virConnectPtr conn, } ret = qemuProcessStart(conn, driver, vm, NULL, start_paused, - autodestroy, -1, NULL, VIR_VM_OP_CREATE); + autodestroy, -1, NULL, NULL, VIR_VM_OP_CREATE); virDomainAuditStart(vm, "booted", ret >= 0); if (ret >= 0) { virDomainEventPtr event = @@ -8318,32 +8312,6 @@ cleanup: return ret; } -static int qemuDomainSnapshotSetCurrentActive(virDomainObjPtr vm, - char *snapshotDir) -{ - if (vm->current_snapshot) { - vm->current_snapshot->def->active = 1; - - return qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, - snapshotDir); - } - - return 0; -} - -static int qemuDomainSnapshotSetCurrentInactive(virDomainObjPtr vm, - char *snapshotDir) -{ - if (vm->current_snapshot) { - vm->current_snapshot->def->active = 0; - - return qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, - snapshotDir); - } - - return 0; -} - static int qemuDomainSnapshotIsAllowed(virDomainObjPtr vm) { @@ -8861,17 +8829,12 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } } else { - if (qemuDomainSnapshotSetCurrentActive(vm, driver->snapshotDir) < 0) - goto endjob; - rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL, - true, false, -1, NULL, VIR_VM_OP_CREATE); + true, false, -1, NULL, vm->current_snapshot, + VIR_VM_OP_CREATE); virDomainAuditStart(vm, "from-snapshot", rc >= 0); if (rc < 0) goto endjob; - if (qemuDomainSnapshotSetCurrentInactive(vm, - driver->snapshotDir) < 0) - goto endjob; } /* Touch up domain state. */ diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index a84faf6..b88d9c0 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -1034,7 +1034,7 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, * -incoming $migrateFrom */ if (qemuProcessStart(dconn, driver, vm, migrateFrom, true, - true, dataFD[0], NULL, + true, dataFD[0], NULL, NULL, VIR_VM_OP_MIGRATE_IN_START) < 0) { virDomainAuditStart(vm, "migrated", false); /* Note that we don't set an error here because qemuProcessStart diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 616c8e2..f691bbb 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -2614,6 +2614,7 @@ int qemuProcessStart(virConnectPtr conn, bool autodestroy, int stdin_fd, const char *stdin_path, + virDomainSnapshotObjPtr snapshot, enum virVMOperationType vmop) { int ret; @@ -2816,16 +2817,9 @@ int qemuProcessStart(virConnectPtr conn, VIR_DEBUG("Building emulator command line"); if (!(cmd = qemuBuildCommandLine(conn, driver, vm->def, priv->monConfig, priv->monJSON != 0, priv->qemuCaps, - migrateFrom, stdin_fd, - vm->current_snapshot, vmop))) + migrateFrom, stdin_fd, snapshot, vmop))) goto cleanup; -#if 0 - /* XXX */ - if (qemuDomainSnapshotSetCurrentInactive(vm, driver->snapshotDir) < 0) - goto cleanup; -#endif - /* now that we know it is about to start call the hook if present */ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { char *xml = virDomainDefFormat(vm->def, 0); diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index e9b910d..96ba3f3 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -52,6 +52,7 @@ int qemuProcessStart(virConnectPtr conn, bool autodestroy, int stdin_fd, const char *stdin_path, + virDomainSnapshotObjPtr snapshot, enum virVMOperationType vmop); void qemuProcessStop(struct qemud_driver *driver, -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:22AM -0600, Eric Blake wrote:
Changing the current vm, and writing that change to the file system, all before a new qemu starts, is risky; it's hard to roll back if starting the new qemu fails for some reason. Instead of abusing vm->current_snapshot and making the command line generator decide whether the current snapshot warrants using -loadvm, it is better to just directly pass a snapshot all the way through the call chain if it is to be loaded.
This frees up the last use of snapshot->def->active for qemu's use, so the next patch can repurpose that field for tracking which snapshot is current.
* src/qemu/qemu_command.c (qemuBuildCommandLine): Don't use active field of snapshot. * src/qemu/qemu_process.c (qemuProcessStart): Add a parameter. * src/qemu/qemu_process.h (qemuProcessStart): Update prototype. * src/qemu/qemu_migration.c (qemuMigrationPrepareAny): Update callers. * src/qemu/qemu_driver.c (qemudDomainCreate) (qemuDomainSaveImageStartVM, qemuDomainObjStart) (qemuDomainRevertToSnapshot): Likewise. (qemuDomainSnapshotSetCurrentActive) (qemuDomainSnapshotSetCurrentInactive): Delete unused functions. --- src/qemu/qemu_command.c | 7 ++--- src/qemu/qemu_driver.c | 47 ++++---------------------------------------- src/qemu/qemu_migration.c | 2 +- src/qemu/qemu_process.c | 10 +------- src/qemu/qemu_process.h | 1 + 5 files changed, 12 insertions(+), 55 deletions(-)
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Audit all changes to the qemu vm->current_snapshot, and make them update the saved xml file for both the previous and the new snapshot, so that there is always at most one snapshot with <active>1</active> in the xml, and that snapshot is used as the current snapshot even across libvirtd restarts. * src/conf/domain_conf.h (_virDomainSnapshotDef): Alter member type and name. * src/conf/domain_conf.c (virDomainSnapshotDefParseString) (virDomainSnapshotDefFormat): Update clients. * docs/schemas/domainsnapshot.rng: Tighten rng. * src/qemu/qemu_driver.c (qemuDomainSnapshotLoad): Reload current snapshot. (qemuDomainSnapshotCreateXML, qemuDomainRevertToSnapshot) (qemuDomainSnapshotDiscard): Track current snapshot. --- docs/schemas/domainsnapshot.rng | 5 ++- src/conf/domain_conf.c | 6 ++- src/conf/domain_conf.h | 4 +- src/qemu/qemu_driver.c | 81 +++++++++++++++++++++++++++++---------- 4 files changed, 72 insertions(+), 24 deletions(-) diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 86bab0b..410833f 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -29,7 +29,10 @@ </optional> <optional> <element name='active'> - <text/> + <choice> + <value>0</value> + <value>1</value> + </choice> </element> </optional> <optional> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 0a2c9eb..44212cf 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -10960,6 +10960,7 @@ virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, virDomainSnapshotDefPtr ret = NULL; char *creation = NULL, *state = NULL; struct timeval tv; + int active; xml = virXMLParseCtxt(NULL, xmlStr, "domainsnapshot.xml", &ctxt); if (!xml) { @@ -11016,11 +11017,12 @@ virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, goto cleanup; } - if (virXPathLong("string(./active)", ctxt, &def->active) < 0) { + if (virXPathInt("string(./active)", ctxt, &active) < 0) { virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not find 'active' element")); goto cleanup; } + def->current = active != 0; } else def->creationTime = tv.tv_sec; @@ -11062,7 +11064,7 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, virBufferAsprintf(&buf, " <uuid>%s</uuid>\n", domain_uuid); virBufferAddLit(&buf, " </domain>\n"); if (internal) - virBufferAsprintf(&buf, " <active>%ld</active>\n", def->active); + virBufferAsprintf(&buf, " <active>%d</active>\n", def->current); virBufferAddLit(&buf, "</domainsnapshot>\n"); if (virBufferError(&buf)) { diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 2cc9b06..8382d28 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1297,13 +1297,15 @@ enum virDomainTaintFlags { typedef struct _virDomainSnapshotDef virDomainSnapshotDef; typedef virDomainSnapshotDef *virDomainSnapshotDefPtr; struct _virDomainSnapshotDef { + /* Public XML. */ char *name; char *description; char *parent; long long creationTime; /* in seconds */ int state; - long active; + /* Internal use. */ + bool current; /* At most one snapshot in the list should have this set */ }; typedef struct _virDomainSnapshotObj virDomainSnapshotObj; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index c4183de..b492c97 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -294,6 +294,7 @@ static void qemuDomainSnapshotLoad(void *payload, char *fullpath; virDomainSnapshotDefPtr def = NULL; virDomainSnapshotObjPtr snap = NULL; + virDomainSnapshotObjPtr current = NULL; char ebuf[1024]; virDomainObjLock(vm); @@ -339,7 +340,8 @@ static void qemuDomainSnapshotLoad(void *payload, def = virDomainSnapshotDefParseString(xmlStr, 0); if (def == NULL) { /* Nothing we can do here, skip this one */ - VIR_ERROR(_("Failed to parse snapshot XML from file '%s'"), fullpath); + VIR_ERROR(_("Failed to parse snapshot XML from file '%s'"), + fullpath); VIR_FREE(fullpath); VIR_FREE(xmlStr); continue; @@ -348,12 +350,22 @@ static void qemuDomainSnapshotLoad(void *payload, snap = virDomainSnapshotAssignDef(&vm->snapshots, def); if (snap == NULL) { virDomainSnapshotDefFree(def); + } else if (snap->def->current) { + current = snap; + if (!vm->current_snapshot) + vm->current_snapshot = snap; } VIR_FREE(fullpath); VIR_FREE(xmlStr); } + if (vm->current_snapshot != current) { + VIR_ERROR(_("Too many snapshots claiming to be current for domain %s"), + vm->def->name); + vm->current_snapshot = NULL; + } + /* FIXME: qemu keeps internal track of snapshots. We can get access * to this info via the "info snapshots" monitor command for running * domains, or via "qemu-img snapshot -l" for shutoff domains. It would @@ -8477,12 +8489,17 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, def = NULL; snap->def->state = virDomainObjGetState(vm, NULL); + snap->def->current = true; if (vm->current_snapshot) { snap->def->parent = strdup(vm->current_snapshot->def->name); if (snap->def->parent == NULL) { virReportOOMError(); goto cleanup; } + vm->current_snapshot->def->current = false; + if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, + driver->snapshotDir) < 0) + goto cleanup; vm->current_snapshot = NULL; } @@ -8502,6 +8519,7 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, */ if (qemuDomainSnapshotWriteMetadata(vm, snap, driver->snapshotDir) < 0) goto cleanup; + vm->current_snapshot = snap; snapshot = virGetDomainSnapshot(domain, snap->def->name); @@ -8788,7 +8806,17 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto cleanup; } - vm->current_snapshot = snap; + if (vm->current_snapshot) { + vm->current_snapshot->def->current = false; + if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, + driver->snapshotDir) < 0) + goto cleanup; + vm->current_snapshot = NULL; + /* XXX Should we restore vm->current_snapshot after this point + * in the failure cases where we know there was no change? */ + } + + snap->def->current = true; if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; @@ -8830,7 +8858,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, } } else { rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL, - true, false, -1, NULL, vm->current_snapshot, + true, false, -1, NULL, snap, VIR_VM_OP_CREATE); virDomainAuditStart(vm, "from-snapshot", rc >= 0); if (rc < 0) @@ -8894,6 +8922,15 @@ endjob: vm = NULL; cleanup: + if (vm && ret == 0) { + if (qemuDomainSnapshotWriteMetadata(vm, snap, + driver->snapshotDir) < 0) + ret = -1; + else + vm->current_snapshot = snap; + } else if (snap) { + snap->def->current = false; + } if (event) qemuDomainEventQueue(driver, event); if (vm) @@ -8912,7 +8949,7 @@ static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, int ret = -1; int i; qemuDomainObjPrivatePtr priv; - virDomainSnapshotObjPtr parentsnap; + virDomainSnapshotObjPtr parentsnap = NULL; if (!virDomainObjIsActive(vm)) { qemuimgarg[0] = qemuFindQemuImgBinary(); @@ -8951,31 +8988,35 @@ static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, qemuDomainObjExitMonitorWithDriver(driver, vm); } + if (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, + vm->def->name, snap->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + 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; + VIR_WARN("missing parent snapshot matching name '%s'", + snap->def->parent); + } else { + parentsnap->def->current = true; + if (qemuDomainSnapshotWriteMetadata(vm, parentsnap, + driver->snapshotDir) < 0) { + VIR_WARN("failed to set parent snapshot '%s' as current", + snap->def->parent); + parentsnap->def->current = false; + parentsnap = NULL; + } } - - /* Now we set the new current_snapshot for the domain */ - vm->current_snapshot = parentsnap; - } else { - vm->current_snapshot = NULL; } + vm->current_snapshot = parentsnap; } - if (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, - vm->def->name, snap->def->name) < 0) { - virReportOOMError(); - goto cleanup; - } - unlink(snapFile); - + if (unlink(snapFile) < 0) + VIR_WARN("Failed to unlink %s", snapFile); virDomainSnapshotObjListRemove(&vm->snapshots, snap); ret = 0; -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:23AM -0600, Eric Blake wrote:
Audit all changes to the qemu vm->current_snapshot, and make them update the saved xml file for both the previous and the new snapshot, so that there is always at most one snapshot with <active>1</active> in the xml, and that snapshot is used as the current snapshot even across libvirtd restarts.
* src/conf/domain_conf.h (_virDomainSnapshotDef): Alter member type and name. * src/conf/domain_conf.c (virDomainSnapshotDefParseString) (virDomainSnapshotDefFormat): Update clients. * docs/schemas/domainsnapshot.rng: Tighten rng. * src/qemu/qemu_driver.c (qemuDomainSnapshotLoad): Reload current snapshot. (qemuDomainSnapshotCreateXML, qemuDomainRevertToSnapshot) (qemuDomainSnapshotDiscard): Track current snapshot. --- docs/schemas/domainsnapshot.rng | 5 ++- src/conf/domain_conf.c | 6 ++- src/conf/domain_conf.h | 4 +- src/qemu/qemu_driver.c | 81 +++++++++++++++++++++++++++++---------- 4 files changed, 72 insertions(+), 24 deletions(-)
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 08/24/2011 10:26 AM, Daniel P. Berrange wrote:
On Wed, Aug 24, 2011 at 09:22:23AM -0600, Eric Blake wrote:
Audit all changes to the qemu vm->current_snapshot, and make them update the saved xml file for both the previous and the new snapshot, so that there is always at most one snapshot with <active>1</active> in the xml, and that snapshot is used as the current snapshot even across libvirtd restarts.
* src/conf/domain_conf.h (_virDomainSnapshotDef): Alter member type and name. * src/conf/domain_conf.c (virDomainSnapshotDefParseString) (virDomainSnapshotDefFormat): Update clients. * docs/schemas/domainsnapshot.rng: Tighten rng. * src/qemu/qemu_driver.c (qemuDomainSnapshotLoad): Reload current snapshot. (qemuDomainSnapshotCreateXML, qemuDomainRevertToSnapshot) (qemuDomainSnapshotDiscard): Track current snapshot. --- docs/schemas/domainsnapshot.rng | 5 ++- src/conf/domain_conf.c | 6 ++- src/conf/domain_conf.h | 4 +- src/qemu/qemu_driver.c | 81 +++++++++++++++++++++++++++++---------- 4 files changed, 72 insertions(+), 24 deletions(-)
ACK
5 and 6 are now pushed. There is still a bug in tracking the current snapshot across virDomainSnapshotDelete(, VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN), fixed in patch 8.5/43, but that one still needs a review before I can push it. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

A future patch will make it impossible to remove a domain if it would leave behind any libvirt-tracked metadata about snapshots, since stale metadata interferes with a new domain by the same name. But requiring snaphot contents to be deleted before removing a domain is harsh; with qemu, qemu-img can still make use of the contents after the libvirt domain is gone. Therefore, we need an option to get rid of libvirt tracking information, but not the actual contents. For hypervisors that do not track any metadata in libvirt, the implementation is trivial; all remaining hypervisors (really, just qemu) will be dealt with separately. * include/libvirt/libvirt.h.in (VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY): New flag. * src/libvirt.c (virDomainSnapshotDelete): Document it. * src/esx/esx_driver.c (esxDomainSnapshotDelete): Trivially supported when there is no libvirt metadata. * src/vbox/vbox_tmpl.c (vboxDomainSnapshotDelete): Likewise. --- include/libvirt/libvirt.h.in | 3 ++- src/esx/esx_driver.c | 10 +++++++++- src/libvirt.c | 10 +++++++--- src/vbox/vbox_tmpl.c | 10 +++++++++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index a625479..eae0a10 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2579,7 +2579,8 @@ int virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, /* Delete a snapshot */ typedef enum { - VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), /* Also delete children */ + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY = (1 << 1), /* Delete just metadata */ } virDomainSnapshotDeleteFlags; int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index c097651..beeafbd 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4543,7 +4543,8 @@ esxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, unsigned int flags) esxVI_TaskInfoState taskInfoState; char *taskInfoErrorMessage = NULL; - virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, -1); if (esxVI_EnsureSession(priv->primary) < 0) { return -1; @@ -4561,6 +4562,13 @@ esxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, unsigned int flags) goto cleanup; } + /* ESX snapshots do not require any libvirt metadata, making this + * flag trivial once we know we have a valid snapshot. */ + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) { + result = 0; + goto cleanup; + } + if (esxVI_RemoveSnapshot_Task(priv->primary, snapshotTree->snapshot, removeChildren, &task) < 0 || esxVI_WaitForTaskCompletion(priv->primary, task, snapshot->domain->uuid, diff --git a/src/libvirt.c b/src/libvirt.c index a1197e0..a80c140 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15798,14 +15798,18 @@ error: /** * virDomainSnapshotDelete: * @snapshot: a domain snapshot object - * @flags: flag parameters + * @flags: bitwise-or of supported virDomainSnapshotDeleteFlags * * Delete the snapshot. * * If @flags is 0, then just this snapshot is deleted, and changes from * this snapshot are automatically merged into children snapshots. If - * flags is VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, then this snapshot - * and any children snapshots are deleted. + * @flags includes VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, then this snapshot + * and any children snapshots are deleted. If @flags includes + * VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, then any snapshot metadata + * tracked by libvirt is removed while keeping the snapshot contents + * intact; if a hypervisor does not require any libvirt metadata to + * track snapshots, then this flag is silently ignored. * * Returns 0 if the snapshot was successfully deleted, -1 on error. */ diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 822e899..8de2bae 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -6361,7 +6361,8 @@ vboxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, PRUint32 state; nsresult rc; - virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, -1); vboxIIDFromUUID(&domiid, dom->uuid); rc = VBOX_OBJECT_GET_MACHINE(domiid.value, &machine); @@ -6382,6 +6383,13 @@ vboxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto cleanup; } + /* VBOX snapshots do not require any libvirt metadata, making this + * flag trivial once we know we have a valid snapshot. */ + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) { + ret = 0; + goto cleanup; + } + if (state >= MachineState_FirstOnline && state <= MachineState_LastOnline) { vboxError(VIR_ERR_OPERATION_INVALID, "%s", -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:24AM -0600, Eric Blake wrote:
A future patch will make it impossible to remove a domain if it would leave behind any libvirt-tracked metadata about snapshots, since stale metadata interferes with a new domain by the same name. But requiring snaphot contents to be deleted before removing a domain is harsh; with qemu, qemu-img can still make use of the contents after the libvirt domain is gone. Therefore, we need an option to get rid of libvirt tracking information, but not the actual contents. For hypervisors that do not track any metadata in libvirt, the implementation is trivial; all remaining hypervisors (really, just qemu) will be dealt with separately.
* include/libvirt/libvirt.h.in (VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY): New flag. * src/libvirt.c (virDomainSnapshotDelete): Document it. * src/esx/esx_driver.c (esxDomainSnapshotDelete): Trivially supported when there is no libvirt metadata. * src/vbox/vbox_tmpl.c (vboxDomainSnapshotDelete): Likewise. --- include/libvirt/libvirt.h.in | 3 ++- src/esx/esx_driver.c | 10 +++++++++- src/libvirt.c | 10 +++++++--- src/vbox/vbox_tmpl.c | 10 +++++++++- 4 files changed, 27 insertions(+), 6 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index a625479..eae0a10 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2579,7 +2579,8 @@ int virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
/* Delete a snapshot */ typedef enum { - VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), /* Also delete children */ + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY = (1 << 1), /* Delete just metadata */ } virDomainSnapshotDeleteFlags;
int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index c097651..beeafbd 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4543,7 +4543,8 @@ esxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, unsigned int flags) esxVI_TaskInfoState taskInfoState; char *taskInfoErrorMessage = NULL;
- virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, -1);
if (esxVI_EnsureSession(priv->primary) < 0) { return -1; @@ -4561,6 +4562,13 @@ esxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, unsigned int flags) goto cleanup; }
+ /* ESX snapshots do not require any libvirt metadata, making this + * flag trivial once we know we have a valid snapshot. */ + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) { + result = 0; + goto cleanup; + } + if (esxVI_RemoveSnapshot_Task(priv->primary, snapshotTree->snapshot, removeChildren, &task) < 0 || esxVI_WaitForTaskCompletion(priv->primary, task, snapshot->domain->uuid, diff --git a/src/libvirt.c b/src/libvirt.c index a1197e0..a80c140 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15798,14 +15798,18 @@ error: /** * virDomainSnapshotDelete: * @snapshot: a domain snapshot object - * @flags: flag parameters + * @flags: bitwise-or of supported virDomainSnapshotDeleteFlags * * Delete the snapshot. * * If @flags is 0, then just this snapshot is deleted, and changes from * this snapshot are automatically merged into children snapshots. If - * flags is VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, then this snapshot - * and any children snapshots are deleted. + * @flags includes VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, then this snapshot + * and any children snapshots are deleted. If @flags includes + * VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, then any snapshot metadata + * tracked by libvirt is removed while keeping the snapshot contents + * intact; if a hypervisor does not require any libvirt metadata to + * track snapshots, then this flag is silently ignored. * * Returns 0 if the snapshot was successfully deleted, -1 on error. */ diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 822e899..8de2bae 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -6361,7 +6361,8 @@ vboxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, PRUint32 state; nsresult rc;
- virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, -1);
vboxIIDFromUUID(&domiid, dom->uuid); rc = VBOX_OBJECT_GET_MACHINE(domiid.value, &machine); @@ -6382,6 +6383,13 @@ vboxDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto cleanup; }
+ /* VBOX snapshots do not require any libvirt metadata, making this + * flag trivial once we know we have a valid snapshot. */ + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY) { + ret = 0; + goto cleanup; + } + if (state >= MachineState_FirstOnline && state <= MachineState_LastOnline) { vboxError(VIR_ERR_OPERATION_INVALID, "%s",
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Even though I recently added 'virsh snapshot-parent', doing it one snapshot at a time is painful, so make it possible to expand the snapshot-list table at once. * tools/virsh.c (cmdSnapshotList): Add --parent. * tools/virsh.pod (snapshot-list): Document it. --- For debugging purposes, I split patch 26/43 into two pieces, then floated this piece earlier in the series. tools/virsh.c | 39 +++++++++++++++++++++++++++++++++------ tools/virsh.pod | 5 ++++- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 15b9bdd..c114cfc 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12069,6 +12069,7 @@ static const vshCmdInfo info_snapshot_list[] = { static const vshCmdOptDef opts_snapshot_list[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"parent", VSH_OT_BOOL, 0, N_("add a column showing parent snapshot")}, {NULL, 0, 0, NULL} }; @@ -12077,6 +12078,9 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom = NULL; bool ret = false; + int flags = 0; + int parent_filter = 0; /* 0 for no parent information needed, + 1 for parent column */ int numsnaps; char **names = NULL; int actual = 0; @@ -12086,11 +12090,16 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) char *doc = NULL; virDomainSnapshotPtr snapshot = NULL; char *state = NULL; + char *parent = NULL; long long creation_longlong; time_t creation_time_t; char timestr[100]; struct tm time_info; + if (vshCommandOptBool(cmd, "parent")) { + parent_filter = 1; + } + if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12098,19 +12107,25 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) if (dom == NULL) goto cleanup; - numsnaps = virDomainSnapshotNum(dom, 0); + numsnaps = virDomainSnapshotNum(dom, flags); if (numsnaps < 0) goto cleanup; - vshPrintExtra(ctl, " %-20s %-25s %s\n", _("Name"), _("Creation Time"), _("State")); - vshPrintExtra(ctl, "---------------------------------------------------\n"); + if (parent_filter > 0) + vshPrintExtra(ctl, " %-20s %-25s %-15s %s", + _("Name"), _("Creation Time"), _("State"), _("Parent")); + else + vshPrintExtra(ctl, " %-20s %-25s %s", + _("Name"), _("Creation Time"), _("State")); + vshPrintExtra(ctl, "\n\ +------------------------------------------------------------\n"); if (numsnaps) { if (VIR_ALLOC_N(names, numsnaps) < 0) goto cleanup; - actual = virDomainSnapshotListNames(dom, names, numsnaps, 0); + actual = virDomainSnapshotListNames(dom, names, numsnaps, flags); if (actual < 0) goto cleanup; @@ -12118,6 +12133,7 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) for (i = 0; i < actual; i++) { /* free up memory from previous iterations of the loop */ + VIR_FREE(parent); VIR_FREE(state); if (snapshot) virDomainSnapshotFree(snapshot); @@ -12137,6 +12153,11 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) if (!xml) continue; + if (parent_filter) { + parent = virXPathString("string(/domainsnapshot/parent/name)", + ctxt); + } + state = virXPathString("string(/domainsnapshot/state)", ctxt); if (state == NULL) continue; @@ -12149,9 +12170,14 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) continue; } localtime_r(&creation_time_t, &time_info); - strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z", &time_info); + strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z", + &time_info); - vshPrint(ctl, " %-20s %-25s %s\n", names[i], timestr, state); + if (parent) + vshPrint(ctl, " %-20s %-25s %-15s %s\n", + names[i], timestr, state, parent); + else + vshPrint(ctl, " %-20s %-25s %s\n", names[i], timestr, state); } } @@ -12159,6 +12185,7 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) cleanup: /* this frees up memory from the last iteration of the loop */ + VIR_FREE(parent); VIR_FREE(state); if (snapshot) virDomainSnapshotFree(snapshot); diff --git a/tools/virsh.pod b/tools/virsh.pod index e17f309..ce7ad02 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1585,10 +1585,13 @@ Output the snapshot XML for the domain's current snapshot (if any). If I<--name> is specified, just list the snapshot name instead of the full xml. -=item B<snapshot-list> I<domain> +=item B<snapshot-list> I<domain> [I<--parent>] List all of the available snapshots for the given domain. +If I<--parent> is specified, add a column to the output table giving +the name of the parent of each snapshot. + =item B<snapshot-dumpxml> I<domain> I<snapshot> Output the snapshot XML for the domain's snapshot named I<snapshot>. -- 1.7.4.4

Each snapshot lookup was iterating over the entire hash table, O(n), instead of honing in directly on the hash key, amortized O(1). Besides, fixing this means that virDomainSnapshotFindByName can now be used inside another virHashForeach iteration (without this patch, attempts to lookup a snapshot by name during a hash iteration will fail due to nested iteration). * src/conf/domain_conf.c (virDomainSnapshotFindByName): Simplify. (virDomainSnapshotObjListSearchName): Delete unused function. --- This patch is necessary prior to patch 8/43 to fix the recursive iteration bug reported by Dan. The actual patch 8/43 does not have to change! src/conf/domain_conf.c | 20 ++++---------------- 1 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 44212cf..6f19b6c 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -11213,23 +11213,11 @@ int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots) return count; } -static int virDomainSnapshotObjListSearchName(const void *payload, - const void *name ATTRIBUTE_UNUSED, - const void *data) -{ - virDomainSnapshotObjPtr obj = (virDomainSnapshotObjPtr)payload; - int want = 0; - - if (STREQ(obj->def->name, (const char *)data)) - want = 1; - - return want; -} - -virDomainSnapshotObjPtr virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, - const char *name) +virDomainSnapshotObjPtr +virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, + const char *name) { - return virHashSearch(snapshots->objs, virDomainSnapshotObjListSearchName, name); + return virHashLookup(snapshots->objs, name); } void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, -- 1.7.4.4

This one's nasty. Ever since we fixed virHashForEach to prevent nested hash iterations for safety reasons, virDomainSnapshotDelete with VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN has been broken for qemu: it deletes children, while leaving grandchildren intact but pointing to a no-longer-present parent. But even before then, the code would often appear to succeed to clean up grandchildren, but risked memory corruption if you have a large and deep hierarchy of snapshots. For acting on just children, a single virHashForEach is sufficient. But for acting on an entire subtree, it requires iteration; and since we declared recursion as invalid, we have to switch to a while loop. Doing this correctly requires quite a bit of overhaul, so I added a new helper function to isolate the algorithm from the actions, so that callers do not have to reinvent the iteration. * src/conf/domain_conf.h (_virDomainSnapshotDef): Add mark. (virDomainSnapshotForEachDescendant): New prototype. * src/libvirt_private.syms (domain_conf.h): Export it. * src/conf/domain_conf.c (virDomainSnapshotMarkDescendant) (virDomainSnapshotActOnDescendant) (virDomainSnapshotForEachDescendant): New functions. * src/qemu/qemu_driver.c (qemuDomainSnapshotDiscardChildren): Replace... (qemuDomainSnapshotDiscardDescenent): ...with callback that doesn't nest hash traversal. (qemuDomainSnapshotDelete): Use new function. --- src/conf/domain_conf.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 8 +++- src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 35 ++++++---------- 4 files changed, 124 insertions(+), 23 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 44212cf..75fa7f7 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -11266,6 +11266,109 @@ int virDomainSnapshotHasChildren(virDomainSnapshotObjPtr snap, return children.number; } +typedef enum { + MARK_NONE, /* No relation determined yet */ + MARK_DESCENDANT, /* Descendant of target */ + MARK_OTHER, /* Not a descendant of target */ +} snapshot_mark; + +struct snapshot_mark_descendant { + const char *name; /* Parent's name on round 1, NULL on other rounds. */ + virDomainSnapshotObjListPtr snapshots; + bool marked; /* True if descendants were found in this round */ +}; + +/* To be called in a loop until no more descendants are found. + * Additionally marking known unrelated snapshots reduces the number + * of total hash searches needed. */ +static void +virDomainSnapshotMarkDescendant(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr obj = payload; + struct snapshot_mark_descendant *curr = data; + virDomainSnapshotObjPtr parent = NULL; + + /* Learned on a previous pass. */ + if (obj->mark) + return; + + if (curr->name) { + /* First round can only find root nodes and direct children. */ + if (!obj->def->parent) { + obj->mark = MARK_OTHER; + } else if (STREQ(obj->def->parent, curr->name)) { + obj->mark = MARK_DESCENDANT; + curr->marked = true; + } + } else { + /* All remaining rounds propagate marks from parents to children. */ + parent = virDomainSnapshotFindByName(curr->snapshots, obj->def->parent); + if (!parent) { + VIR_WARN("snapshot hash table is inconsistent!"); + obj->mark = MARK_OTHER; + return; + } + if (parent->mark) { + obj->mark = parent->mark; + if (obj->mark == MARK_DESCENDANT) + curr->marked = true; + } + } +} + +struct snapshot_act_on_descendant { + int number; + virHashIterator iter; + void *data; +}; + +static void +virDomainSnapshotActOnDescendant(void *payload, + const void *name, + void *data) +{ + virDomainSnapshotObjPtr obj = payload; + struct snapshot_act_on_descendant *curr = data; + + if (obj->mark == MARK_DESCENDANT) { + curr->number++; + (curr->iter)(payload, name, curr->data); + } + obj->mark = MARK_NONE; +} + +/* Run iter(data) on all descendants of snapshot, while ignoring all + * other entries in snapshots. Return the number of descendants + * visited. No particular ordering is guaranteed. */ +int +virDomainSnapshotForEachDescendant(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data) +{ + struct snapshot_mark_descendant mark; + struct snapshot_act_on_descendant act; + + /* virHashForEach does not support nested traversal, so we must + * instead iterate until no more snapshots get marked. We + * guarantee that on exit, all marks have been cleared again. */ + mark.name = snapshot->def->name; + mark.snapshots = snapshots; + mark.marked = true; + while (mark.marked) { + mark.marked = false; + virHashForEach(snapshots->objs, virDomainSnapshotMarkDescendant, &mark); + mark.name = NULL; + } + act.number = 0; + act.iter = iter; + act.data = data; + virHashForEach(snapshots->objs, virDomainSnapshotActOnDescendant, &act); + + return act.number; +} int virDomainChrDefForeach(virDomainDefPtr def, bool abortOnError, diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 8382d28..d266605 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1139,7 +1139,6 @@ struct _virDomainClockDef { # define VIR_DOMAIN_CPUMASK_LEN 1024 - typedef struct _virDomainVcpuPinDef virDomainVcpuPinDef; typedef virDomainVcpuPinDef *virDomainVcpuPinDefPtr; struct _virDomainVcpuPinDef { @@ -1312,6 +1311,9 @@ typedef struct _virDomainSnapshotObj virDomainSnapshotObj; typedef virDomainSnapshotObj *virDomainSnapshotObjPtr; struct _virDomainSnapshotObj { virDomainSnapshotDefPtr def; + + /* Internal use only */ + int mark; /* Used in identifying descendents. */ }; typedef struct _virDomainSnapshotObjList virDomainSnapshotObjList; @@ -1341,6 +1343,10 @@ void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, virDomainSnapshotObjPtr snapshot); int virDomainSnapshotHasChildren(virDomainSnapshotObjPtr snap, virDomainSnapshotObjListPtr snapshots); +int virDomainSnapshotForEachDescendant(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data); /* Guest VM runtime state */ typedef struct _virDomainStateReason virDomainStateReason; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0618b49..fcce810 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -384,6 +384,7 @@ virDomainSnapshotDefFormat; virDomainSnapshotDefFree; virDomainSnapshotDefParseString; virDomainSnapshotFindByName; +virDomainSnapshotForEachDescendant; virDomainSnapshotHasChildren; virDomainSnapshotObjListGetNames; virDomainSnapshotObjListNum; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index b492c97..1405b71 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -9031,31 +9031,21 @@ cleanup: struct snap_remove { struct qemud_driver *driver; virDomainObjPtr vm; - char *parent; int err; }; -static void qemuDomainSnapshotDiscardChildren(void *payload, - const void *name ATTRIBUTE_UNUSED, - void *data) +static void +qemuDomainSnapshotDiscardDescendant(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) { virDomainSnapshotObjPtr snap = payload; struct snap_remove *curr = data; - struct snap_remove this; - - if (snap->def->parent && STREQ(snap->def->parent, curr->parent)) { - this.driver = curr->driver; - this.vm = curr->vm; - this.parent = snap->def->name; - this.err = 0; - virHashForEach(curr->vm->snapshots.objs, - qemuDomainSnapshotDiscardChildren, &this); - - if (this.err) - curr->err = this.err; - else - this.err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap); - } + int err; + + err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap); + if (err && !curr->err) + curr->err = err; } struct snap_reparent { @@ -9131,10 +9121,11 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) { rem.driver = driver; rem.vm = vm; - rem.parent = snap->def->name; rem.err = 0; - virHashForEach(vm->snapshots.objs, qemuDomainSnapshotDiscardChildren, - &rem); + virDomainSnapshotForEachDescendant(&vm->snapshots, + snap, + qemuDomainSnapshotDiscardDescendant, + &rem); if (rem.err < 0) goto endjob; } else { -- 1.7.4.4

On Wed, Aug 24, 2011 at 09:22:25AM -0600, Eric Blake wrote:
This one's nasty. Ever since we fixed virHashForEach to prevent nested hash iterations for safety reasons, virDomainSnapshotDelete with VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN has been broken for qemu: it deletes children, while leaving grandchildren intact but pointing to a no-longer-present parent. But even before then, the code would often appear to succeed to clean up grandchildren, but risked memory corruption if you have a large and deep hierarchy of snapshots.
For acting on just children, a single virHashForEach is sufficient. But for acting on an entire subtree, it requires iteration; and since we declared recursion as invalid, we have to switch to a while loop. Doing this correctly requires quite a bit of overhaul, so I added a new helper function to isolate the algorithm from the actions, so that callers do not have to reinvent the iteration.
* src/conf/domain_conf.h (_virDomainSnapshotDef): Add mark. (virDomainSnapshotForEachDescendant): New prototype. * src/libvirt_private.syms (domain_conf.h): Export it. * src/conf/domain_conf.c (virDomainSnapshotMarkDescendant) (virDomainSnapshotActOnDescendant) (virDomainSnapshotForEachDescendant): New functions. * src/qemu/qemu_driver.c (qemuDomainSnapshotDiscardChildren): Replace... (qemuDomainSnapshotDiscardDescenent): ...with callback that doesn't nest hash traversal. (qemuDomainSnapshotDelete): Use new function. --- src/conf/domain_conf.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++ src/conf/domain_conf.h | 8 +++- src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 35 ++++++---------- 4 files changed, 124 insertions(+), 23 deletions(-)
Perhaps I'm mis-understanding what this patch is attempting to fix, but after applying everything upto this point, I see the following behaviour which does not appear correct $ virsh list Id Name State ---------------------------------- 5 vm1 running $ virsh snapshot-list vm1 Name Creation Time State --------------------------------------------------- $ virsh snapshot-create vm1 Domain snapshot 1314203761 created $ virsh snapshot-create vm1 Domain snapshot 1314203763 created $ virsh snapshot-create vm1 Domain snapshot 1314203767 created $ virsh snapshot-create vm1 Domain snapshot 1314203777 created $ virsh snapshot-create vm1 Domain snapshot 1314203781 created $ virsh snapshot-list vm1 Name Creation Time State --------------------------------------------------- 1314203761 2011-08-24 17:36:01 +0100 running 1314203763 2011-08-24 17:36:03 +0100 running 1314203767 2011-08-24 17:36:07 +0100 running 1314203777 2011-08-24 17:36:17 +0100 running 1314203781 2011-08-24 17:36:21 +0100 running $ virsh snapshot-delete vm1 --children 1314203767 Domain snapshot 1314203767 deleted $ virsh snapshot-list vm1 Name Creation Time State --------------------------------------------------- 1314203761 2011-08-24 17:36:01 +0100 running 1314203763 2011-08-24 17:36:03 +0100 running 1314203781 2011-08-24 17:36:21 +0100 running IIUC, 1314203781 should have been deleted. I also still saw errors in libvirtd logs 17:36:36.175: 4423: info : libvirt version: 0.9.4 17:36:36.175: 4423: error : virHashSearch:604 : Hash operation not allowed during iteration 17:36:36.176: 4423: warning : virDomainSnapshotMarkDescendant:11309 : snapshot hash table is inconsistent! 17:36:36.176: 4423: error : virHashSearch:604 : Hash operation not allowed during iteration 17:36:36.176: 4423: warning : virDomainSnapshotMarkDescendant:11309 : snapshot hash table is inconsistent! 17:36:36.176: 4423: error : virHashSearch:604 : Hash operation not allowed during iteration 17:36:36.176: 4423: warning : virDomainSnapshotMarkDescendant:11309 : snapshot hash table is inconsistent! Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 08/24/2011 10:38 AM, Daniel P. Berrange wrote:
On Wed, Aug 24, 2011 at 09:22:25AM -0600, Eric Blake wrote:
This one's nasty. Ever since we fixed virHashForEach to prevent nested hash iterations for safety reasons, virDomainSnapshotDelete with VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN has been broken for qemu: it deletes children, while leaving grandchildren intact but pointing to a no-longer-present parent. But even before then, the code would often appear to succeed to clean up grandchildren, but risked memory corruption if you have a large and deep hierarchy of snapshots.
I also still saw errors in libvirtd logs
17:36:36.175: 4423: info : libvirt version: 0.9.4 17:36:36.175: 4423: error : virHashSearch:604 : Hash operation not allowed during iteration 17:36:36.176: 4423: warning : virDomainSnapshotMarkDescendant:11309 : snapshot hash table is inconsistent!
Ouch; I can reproduce. I guess I didn't test this one as thoroughly as I had planned, and this is indeed a bug in my 'forEachDescendant' iterator. I'll post an update once I figure out where I went wrong. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On 08/24/2011 12:34 PM, Eric Blake wrote:
On 08/24/2011 10:38 AM, Daniel P. Berrange wrote:
On Wed, Aug 24, 2011 at 09:22:25AM -0600, Eric Blake wrote:
This one's nasty. Ever since we fixed virHashForEach to prevent nested hash iterations for safety reasons, virDomainSnapshotDelete with VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN has been broken for qemu: it deletes children, while leaving grandchildren intact but pointing to a no-longer-present parent. But even before then, the code would often appear to succeed to clean up grandchildren, but risked memory corruption if you have a large and deep hierarchy of snapshots.
I also still saw errors in libvirtd logs
17:36:36.175: 4423: info : libvirt version: 0.9.4 17:36:36.175: 4423: error : virHashSearch:604 : Hash operation not allowed during iteration 17:36:36.176: 4423: warning : virDomainSnapshotMarkDescendant:11309 : snapshot hash table is inconsistent!
Ouch; I can reproduce. I guess I didn't test this one as thoroughly as I had planned, and this is indeed a bug in my 'forEachDescendant' iterator. I'll post an update once I figure out where I went wrong.
Found the problem - virDomainSnapshotFindByName is doing a full hash search instead of a simple lookup - and recursive searches are verboten. Fix that, and we still have a problem - if the current snapshot is in the set of snapshots being deleted, then the parent snapshot needs to become current again. I'll post a couple of patches shortly. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Deleting a snapshot and all its descendants had problems with tracking the current snapshot. The deletion does not necessarily proceed in depth-first order, so a parent could be deleted before a child, wreaking havoc on passing the notion of the current snapshot to the parent. Furthermore, even if traversal were depth-first, doing multiple file writes to pass current up the chain one snapshot at a time is wasteful, comparing to a single update to the current snapshot at the end of the algorithm. * src/qemu/qemu_driver.c (snap_remove): Add field. (qemuDomainSnapshotDiscard): Add parameter. (qemuDomainSnapshotDiscardDescendant): Adjust accordingly. (qemuDomainSnapshotDelete): Properly reset current. --- This patch should be applied immediately after 8/43 to fix one other bug with snapshot deletion that I noticed in the process of fixing the nested hash iteration. It will cause a few patches later in the series to have a merge conflict if you apply them via 'git am', but my git repository (see 0/43) has been rebased appropriately. After adding 7.5/43, 7.9/43, and 8.5/43 in the right places, I was successfully able to do: virsh> snapshot-create-as dom s1 virsh> snapshot-create-as dom s2 virsh> snapshot-create-as dom s3 virsh> snapshot-create-as dom s4 virsh> snapshot-delete --children dom s2 virsh> snapshot-current --name dom s1 virsh> snapshot-list --parent dom correct listing with just s1 left A note of caution: I tested with snapshots named 's1' instead of '1'; because I just discovered another libvirt bug that I just filed: https://bugzilla.redhat.com/show_bug.cgi?id=733143 Namely, qemu-img prefers deleting snapshot ids over snapshot tags, so if your tags are simple integers but do not line up with ids, then you risk libvirt deleting the wrong snapshot data. src/qemu/qemu_driver.c | 20 ++++++++++++++------ 1 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 1405b71..c959e58 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8940,9 +8940,11 @@ cleanup: return ret; } -static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, - virDomainObjPtr vm, - virDomainSnapshotObjPtr snap) +static int +qemuDomainSnapshotDiscard(struct qemud_driver *driver, + virDomainObjPtr vm, + virDomainSnapshotObjPtr snap, + bool update_current) { const char *qemuimgarg[] = { NULL, "snapshot", "-d", NULL, NULL, NULL }; char *snapFile = NULL; @@ -8995,7 +8997,7 @@ static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, } if (snap == vm->current_snapshot) { - if (snap->def->parent) { + if (update_current && snap->def->parent) { parentsnap = virDomainSnapshotFindByName(&vm->snapshots, snap->def->parent); if (!parentsnap) { @@ -9032,6 +9034,7 @@ struct snap_remove { struct qemud_driver *driver; virDomainObjPtr vm; int err; + bool current; }; static void @@ -9043,7 +9046,9 @@ qemuDomainSnapshotDiscardDescendant(void *payload, struct snap_remove *curr = data; int err; - err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap); + if (snap->def->current) + curr->current = true; + err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap, false); if (err && !curr->err) curr->err = err; } @@ -9122,12 +9127,15 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, rem.driver = driver; rem.vm = vm; rem.err = 0; + rem.current = false; virDomainSnapshotForEachDescendant(&vm->snapshots, snap, qemuDomainSnapshotDiscardDescendant, &rem); if (rem.err < 0) goto endjob; + if (rem.current) + vm->current_snapshot = snap; } else { rep.driver = driver; rep.snap = snap; @@ -9139,7 +9147,7 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto endjob; } - ret = qemuDomainSnapshotDiscard(driver, vm, snap); + ret = qemuDomainSnapshotDiscard(driver, vm, snap, true); endjob: if (qemuDomainObjEndJob(driver, vm) == 0) -- 1.7.4.4

Similar to the last patch in isolating the filtering from the client actions, so that clients don't have to reinvent the filtering. * src/conf/domain_conf.h (virDomainSnapshotForEachChild): New prototype. * src/libvirt_private.syms (domain_conf.h): Export it. * src/conf/domain_conf.c (virDomainSnapshotActOnChild) (virDomainSnapshotForEachChild): New functions. (virDomainSnapshotCountChildren): Delete. (virDomainSnapshotHasChildren): Simplify. * src/qemu/qemu_driver.c (qemuDomainSnapshotReparentChildren) (qemuDomainSnapshotDelete): Likewise. --- src/conf/domain_conf.c | 48 ++++++++++++++++++++++++++++++++------------- src/conf/domain_conf.h | 4 +++ src/libvirt_private.syms | 1 + src/qemu/qemu_driver.c | 31 ++++++++++++++--------------- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 75fa7f7..01bbd04 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -11238,32 +11238,52 @@ void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, virHashRemoveEntry(snapshots->objs, snapshot->def->name); } -struct snapshot_has_children { - char *name; +struct snapshot_act_on_child { + char *parent; int number; + virHashIterator iter; + void *data; }; -static void virDomainSnapshotCountChildren(void *payload, - const void *name ATTRIBUTE_UNUSED, - void *data) +static void +virDomainSnapshotActOnChild(void *payload, + const void *name, + void *data) { virDomainSnapshotObjPtr obj = payload; - struct snapshot_has_children *curr = data; + struct snapshot_act_on_child *curr = data; - if (obj->def->parent && STREQ(obj->def->parent, curr->name)) + if (obj->def->parent && STREQ(curr->parent, obj->def->parent)) { curr->number++; + if (curr->iter) + (curr->iter)(payload, name, curr->data); + } } -int virDomainSnapshotHasChildren(virDomainSnapshotObjPtr snap, - virDomainSnapshotObjListPtr snapshots) +/* Run iter(data) on all direct children of snapshot, while ignoring all + * other entries in snapshots. Return the number of children + * visited. No particular ordering is guaranteed. */ +int +virDomainSnapshotForEachChild(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data) { - struct snapshot_has_children children; + struct snapshot_act_on_child act; - children.name = snap->def->name; - children.number = 0; - virHashForEach(snapshots->objs, virDomainSnapshotCountChildren, &children); + act.parent = snapshot->def->name; + act.number = 0; + act.iter = iter; + act.data = data; + virHashForEach(snapshots->objs, virDomainSnapshotActOnChild, &act); - return children.number; + return act.number; +} + +int virDomainSnapshotHasChildren(virDomainSnapshotObjPtr snap, + virDomainSnapshotObjListPtr snapshots) +{ + return virDomainSnapshotForEachChild(snapshots, snap, NULL, NULL); } typedef enum { diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index d266605..5f752ec 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1343,6 +1343,10 @@ void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, virDomainSnapshotObjPtr snapshot); int virDomainSnapshotHasChildren(virDomainSnapshotObjPtr snap, virDomainSnapshotObjListPtr snapshots); +int virDomainSnapshotForEachChild(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data); int virDomainSnapshotForEachDescendant(virDomainSnapshotObjListPtr snapshots, virDomainSnapshotObjPtr snapshot, virHashIterator iter, diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index fcce810..74c5ff4 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -384,6 +384,7 @@ virDomainSnapshotDefFormat; virDomainSnapshotDefFree; virDomainSnapshotDefParseString; virDomainSnapshotFindByName; +virDomainSnapshotForEachChild; virDomainSnapshotForEachDescendant; virDomainSnapshotHasChildren; virDomainSnapshotObjListGetNames; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 1405b71..5101167 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -9050,7 +9050,7 @@ qemuDomainSnapshotDiscardDescendant(void *payload, struct snap_reparent { struct qemud_driver *driver; - virDomainSnapshotObjPtr snap; + const char *parent; virDomainObjPtr vm; int err; }; @@ -9067,22 +9067,20 @@ qemuDomainSnapshotReparentChildren(void *payload, return; } - if (snap->def->parent && STREQ(snap->def->parent, rep->snap->def->name)) { - VIR_FREE(snap->def->parent); + VIR_FREE(snap->def->parent); - if (rep->snap->def->parent != NULL) { - snap->def->parent = strdup(rep->snap->def->parent); + if (rep->parent != NULL) { + snap->def->parent = strdup(rep->parent); - if (snap->def->parent == NULL) { - virReportOOMError(); - rep->err = -1; - return; - } + if (snap->def->parent == NULL) { + virReportOOMError(); + rep->err = -1; + return; } - - rep->err = qemuDomainSnapshotWriteMetadata(rep->vm, snap, - rep->driver->snapshotDir); } + + rep->err = qemuDomainSnapshotWriteMetadata(rep->vm, snap, + rep->driver->snapshotDir); } static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, @@ -9130,11 +9128,12 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto endjob; } else { rep.driver = driver; - rep.snap = snap; + rep.parent = snap->def->parent; rep.vm = vm; rep.err = 0; - virHashForEach(vm->snapshots.objs, qemuDomainSnapshotReparentChildren, - &rep); + virDomainSnapshotForEachChild(&vm->snapshots, snap, + qemuDomainSnapshotReparentChildren, + &rep); if (rep.err < 0) goto endjob; } -- 1.7.4.4

Adding this was trivial compared to the previous patch for fixing qemu snapshot deletion in the first place. * src/qemu/qemu_driver.c (qemuDomainSnapshotDiscard): Add parameter. (qemuDomainSnapshotDiscardDescendant, qemuDomainSnapshotDelete): Update callers. --- src/qemu/qemu_driver.c | 78 ++++++++++++++++++++++++++--------------------- 1 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 5101167..3b59a35 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8942,7 +8942,8 @@ cleanup: static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, virDomainObjPtr vm, - virDomainSnapshotObjPtr snap) + virDomainSnapshotObjPtr snap, + bool metadata_only) { const char *qemuimgarg[] = { NULL, "snapshot", "-d", NULL, NULL, NULL }; char *snapFile = NULL; @@ -8951,41 +8952,43 @@ static int qemuDomainSnapshotDiscard(struct qemud_driver *driver, qemuDomainObjPrivatePtr priv; virDomainSnapshotObjPtr parentsnap = NULL; - 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 (!metadata_only) { + if (!virDomainObjIsActive(vm)) { + qemuimgarg[0] = qemuFindQemuImgBinary(); + if (qemuimgarg[0] == NULL) + /* qemuFindQemuImgBinary set the error */ + goto cleanup; - 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; + 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); } - } 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 (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, @@ -9031,6 +9034,7 @@ cleanup: struct snap_remove { struct qemud_driver *driver; virDomainObjPtr vm; + bool metadata_only; int err; }; @@ -9043,7 +9047,8 @@ qemuDomainSnapshotDiscardDescendant(void *payload, struct snap_remove *curr = data; int err; - err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap); + err = qemuDomainSnapshotDiscard(curr->driver, curr->vm, snap, + curr->metadata_only); if (err && !curr->err) curr->err = err; } @@ -9093,8 +9098,10 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, char uuidstr[VIR_UUID_STRING_BUFLEN]; struct snap_remove rem; struct snap_reparent rep; + bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY); - virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, -1); qemuDriverLock(driver); virUUIDFormat(snapshot->domain->uuid, uuidstr); @@ -9119,6 +9126,7 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) { rem.driver = driver; rem.vm = vm; + rem.metadata_only = metadata_only; rem.err = 0; virDomainSnapshotForEachDescendant(&vm->snapshots, snap, @@ -9138,7 +9146,7 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto endjob; } - ret = qemuDomainSnapshotDiscard(driver, vm, snap); + ret = qemuDomainSnapshotDiscard(driver, vm, snap, metadata_only); endjob: if (qemuDomainObjEndJob(driver, vm) == 0) -- 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. Also, it is handy to filter the list of snapshots to just those that have no parents; document that flag now, but implement it in later patches. * include/libvirt/libvirt.h.in (VIR_DOMAIN_SNAPSHOT_LIST_ROOTS) (VIR_DOMAIN_SNAPSHOT_LIST_METADATA): New flags. * src/libvirt.c (virDomainSnapshotNum) (virDomainSnapshotListNames): Document them. * src/esx/esx_driver.c (esxDomainSnapshotNum) (esxDomainSnapshotListNames): Implement trivial flag. * src/vbox/vbox_tmpl.c (vboxDomainSnapshotNum) (vboxDomainSnapshotListNames): Likewise. * src/qemu/qemu_driver.c (qemuDomainSnapshotNum) (qemuDomainSnapshotListNames): Likewise. --- include/libvirt/libvirt.h.in | 9 +++++++++ src/esx/esx_driver.c | 10 +++++++--- src/libvirt.c | 27 ++++++++++++++++++++++----- src/qemu/qemu_driver.c | 8 ++++++-- src/vbox/vbox_tmpl.c | 15 +++++++++++++-- 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index eae0a10..20fdbdf 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2551,6 +2551,15 @@ virDomainSnapshotPtr virDomainSnapshotCreateXML(virDomainPtr domain, char *virDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, unsigned int flags); +/* Flags valid for both virDomainSnapshotNum() and + * virDomainSnapshotListNames(). */ +typedef enum { + VIR_DOMAIN_SNAPSHOT_LIST_ROOTS = (1 << 0), /* Filter by snapshots which + have no parents */ + VIR_DOMAIN_SNAPSHOT_LIST_METADATA = (1 << 1), /* Filter by snapshots which + have metadata */ +} virDomainSnapshotListFlags; + /* 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..dbc7694 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_LIST_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_LIST_METADATA) + return 0; + if (esxVI_LookupRootSnapshotTreeList(priv->primary, domain->uuid, &rootSnapshotTreeList) < 0) { return -1; @@ -4357,14 +4361,14 @@ esxDomainSnapshotListNames(virDomainPtr domain, char **names, int nameslen, esxPrivate *priv = domain->conn->privateData; esxVI_VirtualMachineSnapshotTree *rootSnapshotTreeList = NULL; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); if (names == NULL || nameslen < 0) { ESX_ERROR(VIR_ERR_INVALID_ARG, "%s", _("Invalid argument")); return -1; } - if (nameslen == 0) { + if (nameslen == 0 || (flags & VIR_DOMAIN_SNAPSHOT_LIST_METADATA)) { return 0; } diff --git a/src/libvirt.c b/src/libvirt.c index a80c140..4034d4e 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15532,11 +15532,19 @@ error: /** * virDomainSnapshotNum: * @domain: a domain object - * @flags: unused flag parameters; callers should pass 0 + * @flags: bitwise-or of supported virDomainSnapshotListFlags + * + * Provides the number of domain snapshots for this domain. * - * Provides the number of domain snapshots for this domain.. + * If @flags includes VIR_DOMAIN_SNAPSHOT_LIST_ROOTS, then the result is + * filtered to the number of snapshots that have no parents. * - * Returns the number of domain snapshost found or -1 in case of error. + * If @flags includes VIR_DOMAIN_SNAPSHOT_LIST_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 the flag were not given. + * + * Returns the number of domain snapshots found or -1 in case of error. */ int virDomainSnapshotNum(virDomainPtr domain, unsigned int flags) @@ -15572,11 +15580,20 @@ error: * @domain: a domain object * @names: array to collect the list of names of snapshots * @nameslen: size of @names - * @flags: unused flag parameters; callers should pass 0 + * @flags: bitwise-or of supported virDomainSnapshotListFlags * * Collect the list of domain snapshots for the given domain, and store * their names in @names. Caller is responsible for freeing each member - * of the array. + * of the array. The value to use for @nameslen can be determined by + * virDomainSnapshotNum() with the same @flags. + * + * If @flags includes VIR_DOMAIN_SNAPSHOT_LIST_ROOTS, then the result is + * filtered to the number of snapshots that have no parents. + * + * If @flags includes VIR_DOMAIN_SNAPSHOT_LIST_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 the flag were not given. * * Returns the number of domain snapshots found or -1 in case of error. */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 3b59a35..9dd1ceb 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8544,7 +8544,7 @@ static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names, virDomainObjPtr vm = NULL; int n = -1; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, domain->uuid); @@ -8572,7 +8572,7 @@ static int qemuDomainSnapshotNum(virDomainPtr domain, virDomainObjPtr vm = NULL; int n = -1; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, domain->uuid); @@ -8584,6 +8584,10 @@ static int qemuDomainSnapshotNum(virDomainPtr domain, goto cleanup; } + /* All qemu snapshots have libvirt metadata, so + * VIR_DOMAIN_SNAPSHOT_LIST_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..fc9739e 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_LIST_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_LIST_METADATA) { + ret = 0; + goto cleanup; + } + rc = machine->vtbl->GetSnapshotCount(machine, &snapshotCount); if (NS_FAILED(rc)) { vboxError(VIR_ERR_INTERNAL_ERROR, @@ -5901,7 +5907,7 @@ vboxDomainSnapshotListNames(virDomainPtr dom, int count = 0; int i; - virCheckFlags(0, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); vboxIIDFromUUID(&iid, dom->uuid); rc = VBOX_OBJECT_GET_MACHINE(iid.value, &machine); @@ -5911,6 +5917,11 @@ vboxDomainSnapshotListNames(virDomainPtr dom, goto cleanup; } + if (flags & VIR_DOMAIN_SNAPSHOT_LIST_METADATA) { + ret = 0; + goto cleanup; + } + if ((count = vboxDomainSnapshotGetAll(dom, machine, &snapshots)) < 0) goto cleanup; -- 1.7.4.4

Filtering for roots is pretty easy to do. * src/conf/domain_conf.h (virDomainSnapshotObjListGetNames) (virDomainSnapshotObjListNum): Update prototype. * src/conf/domain_conf.c (virDomainSnapshotObjListCopyNames) (virDomainSnapshotObjListGetNames, virDomainSnapshotObjListCount) (virDomainSnapshotObjListNum): Support filtering. * src/qemu/qemu_driver.c (qemuDomainSnapshotNum) (qemuDomainSnapshotListNames): Update callers. --- src/conf/domain_conf.c | 33 +++++++++++++++++++++++---------- src/conf/domain_conf.h | 6 ++++-- src/qemu/qemu_driver.c | 11 +++++++---- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 01bbd04..f48051a 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -11155,6 +11155,7 @@ struct virDomainSnapshotNameData { int numnames; int maxnames; char **const names; + unsigned int flags; }; static void virDomainSnapshotObjListCopyNames(void *payload, @@ -11166,6 +11167,8 @@ static void virDomainSnapshotObjListCopyNames(void *payload, if (data->oom) return; + if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS) && obj->def->parent) + return; if (data->numnames < data->maxnames) { if (!(data->names[data->numnames] = strdup(obj->def->name))) @@ -11176,9 +11179,10 @@ static void virDomainSnapshotObjListCopyNames(void *payload, } int virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, - char **const names, int maxnames) + char **const names, int maxnames, + unsigned int flags) { - struct virDomainSnapshotNameData data = { 0, 0, maxnames, names }; + struct virDomainSnapshotNameData data = { 0, 0, maxnames, names, flags }; int i; virHashForEach(snapshots->objs, virDomainSnapshotObjListCopyNames, &data); @@ -11195,22 +11199,31 @@ cleanup: return -1; } -static void virDomainSnapshotObjListCount(void *payload ATTRIBUTE_UNUSED, +struct virDomainSnapshotNumData { + int count; + unsigned int flags; +}; + +static void virDomainSnapshotObjListCount(void *payload, const void *name ATTRIBUTE_UNUSED, - void *data) + void *opaque) { - int *count = data; + virDomainSnapshotObjPtr obj = payload; + struct virDomainSnapshotNumData *data = opaque; - (*count)++; + if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS) && obj->def->parent) + return; + data->count++; } -int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots) +int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots, + unsigned int flags) { - int count = 0; + struct virDomainSnapshotNumData data = { 0, flags }; - virHashForEach(snapshots->objs, virDomainSnapshotObjListCount, &count); + virHashForEach(snapshots->objs, virDomainSnapshotObjListCount, &data); - return count; + return data.count; } static int virDomainSnapshotObjListSearchName(const void *payload, diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 5f752ec..503fb58 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1335,8 +1335,10 @@ virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr s int virDomainSnapshotObjListInit(virDomainSnapshotObjListPtr objs); int virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, - char **const names, int maxnames); -int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots); + char **const names, int maxnames, + unsigned int flags); +int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots, + unsigned int flags); virDomainSnapshotObjPtr virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, const char *name); void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 9dd1ceb..d2cb1d7 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8544,7 +8544,8 @@ static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names, virDomainObjPtr vm = NULL; int n = -1; - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | + VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, domain->uuid); @@ -8556,7 +8557,8 @@ static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names, goto cleanup; } - n = virDomainSnapshotObjListGetNames(&vm->snapshots, names, nameslen); + n = virDomainSnapshotObjListGetNames(&vm->snapshots, names, nameslen, + flags); cleanup: if (vm) @@ -8572,7 +8574,8 @@ static int qemuDomainSnapshotNum(virDomainPtr domain, virDomainObjPtr vm = NULL; int n = -1; - virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_LIST_ROOTS | + VIR_DOMAIN_SNAPSHOT_LIST_METADATA, -1); qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, domain->uuid); @@ -8588,7 +8591,7 @@ static int qemuDomainSnapshotNum(virDomainPtr domain, * VIR_DOMAIN_SNAPSHOT_LIST_METADATA makes no difference to our * answer. */ - n = virDomainSnapshotObjListNum(&vm->snapshots); + n = virDomainSnapshotObjListNum(&vm->snapshots, flags); cleanup: if (vm) -- 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 | 27 ++++++++++++++++++++++++ src/vbox/vbox_tmpl.c | 11 ++++++++- 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 20fdbdf..36f1b34 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 dbc7694..90f55c3 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 4034d4e..b0160fb 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -2043,6 +2043,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 @@ -2092,7 +2096,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. */ @@ -2860,12 +2877,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. */ @@ -6814,8 +6835,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 */ @@ -6866,6 +6888,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 d2cb1d7..6557854 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1472,6 +1472,7 @@ static int qemuDomainShutdown(virDomainPtr dom) { virDomainObjPtr vm; int ret = -1; qemuDomainObjPrivatePtr priv; + int nsnapshots; qemuDriverLock(driver); vm = virDomainFindByUUID(&driver->domains, dom->uuid); @@ -1485,6 +1486,14 @@ static int qemuDomainShutdown(virDomainPtr dom) { goto cleanup; } + if (!vm->persistent && + (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { + 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; @@ -1580,6 +1589,7 @@ qemuDomainDestroyFlags(virDomainPtr dom, int ret = -1; virDomainEventPtr event = NULL; qemuDomainObjPrivatePtr priv; + int nsnapshots; virCheckFlags(0, -1); @@ -1593,6 +1603,14 @@ qemuDomainDestroyFlags(virDomainPtr dom, goto cleanup; } + if (!vm->persistent && + (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete transient domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + priv = vm->privateData; priv->fakeReboot = false; @@ -4712,6 +4730,7 @@ qemuDomainUndefineFlags(virDomainPtr dom, virDomainEventPtr event = NULL; char *name = NULL; int ret = -1; + int nsnapshots; virCheckFlags(VIR_DOMAIN_UNDEFINE_MANAGED_SAVE, -1); @@ -4726,6 +4745,14 @@ qemuDomainUndefineFlags(virDomainPtr dom, goto cleanup; } + if (!virDomainObjIsActive(vm) && + (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot delete inactive domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + if (!vm->persistent) { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cannot undefine transient domain")); diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index fc9739e..9e1c6e3 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

On Wed, Aug 24, 2011 at 09:22:30AM -0600, Eric Blake wrote:
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 | 27 ++++++++++++++++++++++++ src/vbox/vbox_tmpl.c | 11 ++++++++- 5 files changed, 106 insertions(+), 15 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 20fdbdf..36f1b34 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;
This feels a little bit wrong to me, for the same reasons we discussed wrt deleteing disks on undefine, particularly once we start storing snapshots in external files across arbitrary storage, instead of just inside qcow2 images https://www.redhat.com/archives/libvir-list/2011-July/msg01548.html Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 08/24/2011 10:49 AM, Daniel P. Berrange wrote:
On Wed, Aug 24, 2011 at 09:22:30AM -0600, Eric Blake wrote:
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.
+/* 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 */
This one is always safe - the only things deleted are libvirt files, leaving all the qcow2 disks and external drives intact.
+ VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL = (1<< 2), /* If last use of domain, + then also remove any + snapshot data */
But I am starting to be convinced by your assertion that this is dangerous - there is no way to cleanly report partial failure with a flag like this, which means that in the failure case, it did not add any convenience, but made things worse.
This feels a little bit wrong to me, for the same reasons we discussed wrt deleteing disks on undefine, particularly once we start storing snapshots in external files across arbitrary storage, instead of just inside qcow2 images
https://www.redhat.com/archives/libvir-list/2011-July/msg01548.html
How about a compromise? Are you willing to agree that deleting snapshot metadata is always safe (the user's disk images are still intact, whether qcow2 or external, and only the hidden files in /var/lib/libvirt are removed so that they cannot interfere with later creation of a new domain by the same name)? That is, should I rework this patch for just the metadata flag, or drop it entirely? And there's still the O(n) vs. O(n^2) consideration; if we drop this patch (or even if we keep the metadata portion of this patch but drop the full deletion aspect), I have to wonder if there is any way we can improve existing virDomainSnapshotDelete (or create a new API) that allows deletion of multiple snapshots without inherent quadratic behavior caused by deleting one snapshot at a time forcing all other snapshots to be checked for reparenting. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

On 08/24/2011 10:56 AM, Eric Blake wrote:
+ VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL = (1<< 2), /* If last use of domain, + then also remove any + snapshot data */
But I am starting to be convinced by your assertion that this is dangerous - there is no way to cleanly report partial failure with a flag like this, which means that in the failure case, it did not add any convenience, but made things worse.
[perhaps this email is more for my reference than anything else, since I am using the giant thread and all replies in it to track what I still need to do before v4 is ready...] https://www.redhat.com/archives/libvir-list/2011-August/msg01355.html had more arguments on which flags are most appropriate; the consensus there was that snapshot metadata should be automatically deleted on the last reference to a domain (such as shutting down a transient domain), and that only undefine of a persistent domain should fail because snapshot metadata exists (for all other situations, the auto-deletion of metadata is sufficient). Meanwhile, I also need to guarantee that it is possible to reload snapshot state from external sources (i.e. allow a snapshot redefinition to reinstate metadata). My holdup for posting a v4 of this series is getting all that added, but I will keep my git repository up-to-date with the latest state of my series as I keep posting stream-of-development patches as replies into this v3 series. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Prepare for code sharing. No semantic change. * src/qemu/qemu_driver.c (qemuFindQemuImgBinary) (qemuDomainSnapshotWriteMetadata) (qemuDomainSnapshotDiscard): Float up. (qemuDomainSnapshotDiscardDescendant): Likewise, and rename... (qemuDomainSnapshotDiscardAll): ...for generic use. (qemuDomainSnapshotDelete): Update caller. --- src/qemu/qemu_driver.c | 371 ++++++++++++++++++++++++------------------------ 1 files changed, 188 insertions(+), 183 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 6557854..90b1d91 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1580,6 +1580,193 @@ cleanup: } +/* 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; +} + +static int +qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, + virDomainSnapshotObjPtr snapshot, + char *snapshotDir) +{ + int fd = -1; + char *newxml = NULL; + int ret = -1; + char *snapDir = NULL; + char *snapFile = NULL; + char uuidstr[VIR_UUID_STRING_BUFLEN]; + + virUUIDFormat(vm->def->uuid, uuidstr); + newxml = virDomainSnapshotDefFormat(uuidstr, snapshot->def, 1); + if (newxml == NULL) { + virReportOOMError(); + return -1; + } + + if (virAsprintf(&snapDir, "%s/%s", snapshotDir, vm->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + if (virFileMakePath(snapDir) < 0) { + virReportSystemError(errno, _("cannot create snapshot directory '%s'"), + snapDir); + goto cleanup; + } + + if (virAsprintf(&snapFile, "%s/%s.xml", snapDir, snapshot->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + fd = open(snapFile, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); + if (fd < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to create snapshot file '%s'"), snapFile); + goto cleanup; + } + if (safewrite(fd, newxml, strlen(newxml)) != strlen(newxml)) { + virReportSystemError(errno, _("Failed to write snapshot data to %s"), + snapFile); + goto cleanup; + } + + ret = 0; + +cleanup: + VIR_FREE(snapFile); + VIR_FREE(snapDir); + VIR_FREE(newxml); + VIR_FORCE_CLOSE(fd); + 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 (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, + vm->def->name, snap->def->name) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (snap == vm->current_snapshot) { + if (snap->def->parent) { + parentsnap = virDomainSnapshotFindByName(&vm->snapshots, + snap->def->parent); + if (!parentsnap) { + VIR_WARN("missing parent snapshot matching name '%s'", + snap->def->parent); + } else { + parentsnap->def->current = true; + if (qemuDomainSnapshotWriteMetadata(vm, parentsnap, + driver->snapshotDir) < 0) { + VIR_WARN("failed to set parent snapshot '%s' as current", + snap->def->parent); + parentsnap->def->current = false; + parentsnap = NULL; + } + } + } + vm->current_snapshot = parentsnap; + } + + if (unlink(snapFile) < 0) + VIR_WARN("Failed to unlink %s", 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; +}; + +/* 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) @@ -8283,75 +8470,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) -{ - int fd = -1; - char *newxml = NULL; - int ret = -1; - char *snapDir = NULL; - char *snapFile = NULL; - char uuidstr[VIR_UUID_STRING_BUFLEN]; - - virUUIDFormat(vm->def->uuid, uuidstr); - newxml = virDomainSnapshotDefFormat(uuidstr, snapshot->def, 1); - if (newxml == NULL) { - virReportOOMError(); - return -1; - } - - if (virAsprintf(&snapDir, "%s/%s", snapshotDir, vm->def->name) < 0) { - virReportOOMError(); - goto cleanup; - } - if (virFileMakePath(snapDir) < 0) { - virReportSystemError(errno, _("cannot create snapshot directory '%s'"), - snapDir); - goto cleanup; - } - - if (virAsprintf(&snapFile, "%s/%s.xml", snapDir, snapshot->def->name) < 0) { - virReportOOMError(); - goto cleanup; - } - fd = open(snapFile, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR); - if (fd < 0) { - qemuReportError(VIR_ERR_OPERATION_FAILED, - _("failed to create snapshot file '%s'"), snapFile); - goto cleanup; - } - if (safewrite(fd, newxml, strlen(newxml)) != strlen(newxml)) { - virReportSystemError(errno, _("Failed to write snapshot data to %s"), - snapFile); - goto cleanup; - } - - ret = 0; - -cleanup: - VIR_FREE(snapFile); - VIR_FREE(snapDir); - VIR_FREE(newxml); - VIR_FORCE_CLOSE(fd); - return ret; -} - - static int qemuDomainSnapshotIsAllowed(virDomainObjPtr vm) { int i; @@ -8974,119 +9092,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 = NULL; - - 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 (virAsprintf(&snapFile, "%s/%s/%s.xml", driver->snapshotDir, - vm->def->name, snap->def->name) < 0) { - virReportOOMError(); - goto cleanup; - } - - if (snap == vm->current_snapshot) { - if (snap->def->parent) { - parentsnap = virDomainSnapshotFindByName(&vm->snapshots, - snap->def->parent); - if (!parentsnap) { - VIR_WARN("missing parent snapshot matching name '%s'", - snap->def->parent); - } else { - parentsnap->def->current = true; - if (qemuDomainSnapshotWriteMetadata(vm, parentsnap, - driver->snapshotDir) < 0) { - VIR_WARN("failed to set parent snapshot '%s' as current", - snap->def->parent); - parentsnap->def->current = false; - parentsnap = NULL; - } - } - } - vm->current_snapshot = parentsnap; - } - - if (unlink(snapFile) < 0) - VIR_WARN("Failed to unlink %s", 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; const char *parent; @@ -9164,7 +9169,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 90b1d91..5a18309 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -774,6 +774,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); @@ -1581,19 +1582,19 @@ cleanup: /* 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; } static int @@ -1667,7 +1668,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; @@ -1739,7 +1740,6 @@ qemuDomainSnapshotDiscard(struct qemud_driver *driver, cleanup: VIR_FREE(snapFile); - VIR_FREE(qemuimgarg[0]); return ret; } @@ -8494,14 +8494,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; @@ -8534,7 +8535,6 @@ qemuDomainSnapshotCreateInactive(virDomainObjPtr vm, ret = 0; cleanup: - VIR_FREE(qemuimgarg[0]); return ret; } @@ -8650,7 +8650,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, @@ -8883,14 +8883,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; @@ -8923,7 +8924,6 @@ qemuDomainSnapshotRevertInactive(virDomainObjPtr vm, ret = 0; cleanup: - VIR_FREE(qemuimgarg[0]); return ret; } @@ -9063,7 +9063,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 5a18309..f79d9bc 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1778,7 +1778,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); @@ -1792,10 +1793,24 @@ qemuDomainDestroyFlags(virDomainPtr dom, if (!vm->persistent && (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { - 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; @@ -4919,7 +4934,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); @@ -4934,10 +4951,24 @@ qemuDomainUndefineFlags(virDomainPtr dom, if (!virDomainObjIsActive(vm) && (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { - 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

On Wed, Aug 24, 2011 at 09:22:33AM -0600, Eric Blake wrote:
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(-)
I'm not entirely sure this is a good idea, for the same reason that we rejected the patch to add 'UNDEFINE_DISKS' to the virDomainUndefine call. Particularly when we start storing snapshots in files outside the main disk image (ie not qcow2 external snapshots), we get into error reporting problems. Do we just ignore any errors deleting the snapshots ? How does the app detect this & cleanup. Do we report the errors, in which case how does the app know how far through the destroy process we got. These feel like policy decisions to me, and so for app code to decide. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

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 15b9bdd..8069a8c 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1422,6 +1422,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 */ @@ -1434,6 +1487,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} }; @@ -1441,18 +1498,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; @@ -1460,61 +1535,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_LIST_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; } @@ -2500,6 +2638,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} }; @@ -2507,8 +2649,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; @@ -2516,13 +2665,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_LIST_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; } @@ -2577,6 +2780,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} }; @@ -2584,8 +2791,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; @@ -2593,13 +2821,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_LIST_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 e17f309..337fcf4 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -409,7 +409,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 @@ -418,6 +418,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. @@ -877,7 +886,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 @@ -887,6 +896,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>] @@ -917,16 +935,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 f79d9bc..8d769c9 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7729,6 +7729,7 @@ qemudDomainMigratePerform (virDomainPtr dom, virDomainObjPtr vm; int ret = -1; const char *dconnuri = NULL; + int nsnapshots; virCheckFlags(QEMU_MIGRATION_FLAGS, -1); @@ -7749,6 +7750,13 @@ qemudDomainMigratePerform (virDomainPtr dom, goto cleanup; } + if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot migrate domain with %d snapshots"), + nsnapshots); + goto cleanup; + } + if (flags & VIR_MIGRATE_PEER2PEER) { dconnuri = uri; uri = NULL; @@ -7825,6 +7833,7 @@ qemuDomainMigrateBegin3(virDomainPtr domain, struct qemud_driver *driver = domain->conn->privateData; virDomainObjPtr vm; char *xml = NULL; + int nsnapshots; virCheckFlags(QEMU_MIGRATION_FLAGS, NULL); @@ -7838,6 +7847,13 @@ qemuDomainMigrateBegin3(virDomainPtr domain, goto cleanup; } + if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { + 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; @@ -7999,6 +8015,7 @@ qemuDomainMigratePerform3(virDomainPtr dom, struct qemud_driver *driver = dom->conn->privateData; virDomainObjPtr vm; int ret = -1; + int nsnapshots; virCheckFlags(QEMU_MIGRATION_FLAGS, -1); @@ -8012,6 +8029,13 @@ qemuDomainMigratePerform3(virDomainPtr dom, goto cleanup; } + if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { + 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

On 08/24/2011 09:22 AM, Eric Blake wrote:
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.
* src/qemu/qemu_driver.c (qemudDomainMigratePerform) (qemuDomainMigrateBegin3, qemuDomainMigratePerform3): Add restriction.
Yuck. I just noticed that this patch adds a migration restriction in qemu_driver, but we already have migration restriction in _two_ other places in qemu_migration.c. I'll refactor this to put all migration restrictions into a single site, under qemuMigrationIsAllowed, to cut down on the duplication. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

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. This patch consolidates two existing locations for migration validation into one helper function, then enhances that function to also do the new checks. If we could always trust the source to validate migration, then the destination would not have to do anything; but since older servers that did not do checking can migrate to newer destinations, we have to repeat some of the same checks on the destination; meanwhile, we want to detect failures as soon as possible. With migration v2, this means that validation will reject things at Prepare on the destination if the XML exposes the problem, otherwise at Perform on the source; with migration v3, this means that validation will reject things at Begin on the source, or if the source is old and the XML exposes the problem, then at Prepare on the destination. 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_migration.h (qemuMigrationIsAllowed): Make static. * src/qemu/qemu_migration.c (qemuMigrationIsAllowed): Alter signature, and allow checks for both outgoing and incoming. (qemuMigrationBegin, qemuMigrationPrepareAny) (qemuMigrationPerformJob): Update callers. --- diff in v3.5: move checks to qemu_migration rather than qemu_driver I still need to post a v4 of my pending series, which has some more rebase updates; mainly to resolve comments that Dan has already made about v3. src/qemu/qemu_migration.c | 48 ++++++++++++++++++++++++++++++-------------- src/qemu/qemu_migration.h | 2 - 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index d239cc8..f849d05 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -701,9 +701,36 @@ error: return NULL; } -bool -qemuMigrationIsAllowed(virDomainDefPtr def) +/* Validate whether the domain is safe to migrate. If vm is NULL, + * then this is being run in the v2 Prepare stage on the destination + * (where we only have the target xml); if vm is provided, then this + * is being run in either v2 Perform or v3 Begin (where we also have + * access to all of the domain's metadata, such as whether it is + * marked autodestroy or has snapshots). While it would be nice to + * assume that checking on source is sufficient to prevent ever + * talking to the destination in the first place, we are stuck with + * the fact that older servers did not do checks on the source. */ +static bool +qemuMigrationIsAllowed(struct qemud_driver *driver, virDomainObjPtr vm, + virDomainDefPtr def) { + int nsnapshots; + + if (vm) { + if (qemuProcessAutoDestroyActive(driver, vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is marked for auto destroy")); + return false; + } + if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, 0))) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + _("cannot migrate domain with %d snapshots"), + nsnapshots); + return false; + } + + def = vm->def; + } if (def->nhostdevs > 0) { qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", _("Domain with assigned host devices cannot be migrated")); @@ -915,13 +942,7 @@ char *qemuMigrationBegin(struct qemud_driver *driver, if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) qemuMigrationJobSetPhase(driver, vm, QEMU_MIGRATION_PHASE_BEGIN3); - if (qemuProcessAutoDestroyActive(driver, vm)) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is marked for auto destroy")); - goto cleanup; - } - - if (!qemuMigrationIsAllowed(vm->def)) + if (!qemuMigrationIsAllowed(driver, vm, NULL)) goto cleanup; if (!(mig = qemuMigrationEatCookie(driver, vm, NULL, 0, 0))) @@ -990,7 +1011,7 @@ qemuMigrationPrepareAny(struct qemud_driver *driver, VIR_DOMAIN_XML_INACTIVE))) goto cleanup; - if (!qemuMigrationIsAllowed(def)) + if (!qemuMigrationIsAllowed(driver, NULL, def)) goto cleanup; /* Target domain name, maybe renamed. */ @@ -2194,11 +2215,8 @@ qemuMigrationPerformJob(struct qemud_driver *driver, goto endjob; } - if (qemuProcessAutoDestroyActive(driver, vm)) { - qemuReportError(VIR_ERR_OPERATION_INVALID, - "%s", _("domain is marked for auto destroy")); - goto endjob; - } + if (!qemuMigrationIsAllowed(driver, vm, NULL)) + goto cleanup; resume = virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING; diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index ace411d..ec70422 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -73,8 +73,6 @@ bool qemuMigrationJobIsActive(virDomainObjPtr vm, int qemuMigrationJobFinish(struct qemud_driver *driver, virDomainObjPtr obj) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; -bool qemuMigrationIsAllowed(virDomainDefPtr def) - ATTRIBUTE_NONNULL(1); int qemuMigrationSetOffline(struct qemud_driver *driver, virDomainObjPtr vm); -- 1.7.4.4

On 08/30/2011 04:10 PM, Eric Blake wrote:
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.
I'm starting to work on that now, with VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE; I'm also adding a flag VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT, which can mark a just-redefined snapshot as current. I'll probably stick the redefine earlier in the series, then rebase this patch for migration on top of that.
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.
This part of my analysis is still not quite right. The prohibition on snapshot metadata should only be on migration with VIR_MIGRATE_UNDEFINE_SOURCE; otherwise, if the domain is persistent, the domain remains behind to allow migration of the snapshot data after the migration instead of before (besides, transferring snapshot metadata before migrating requires the domain to already exist and be persistent on the target, whereas transferring snapshot metadata after migration allows transferring to either persistent or transient target). For transient domains, and without a migration v4, there is no window of time where the domain is accessible on both source and destination to do the transfer with both hosts seeing the same metadata; it will be up to the management app to save the snapshot dumpxml files pre-migration, then to redefine from those files post-migration, and the metadata is automatically cleaned up on the source when the last reference to the transient domain goes away. -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

Minor semantic change - allow domain xml to be generated in place within a larger buffer, rather than having to go through a temporary string. * src/conf/domain_conf.c (virDomainDefFormatInternal): Add parameter. (virDomainDefFormat, virDomainObjFormat): Update callers. --- src/conf/domain_conf.c | 229 ++++++++++++++++++++++++----------------------- 1 files changed, 117 insertions(+), 112 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index f48051a..27c77fe 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -9901,12 +9901,15 @@ verify(((VIR_DOMAIN_XML_INTERNAL_STATUS | & DUMPXML_FLAGS) == 0); /* This internal version can accept VIR_DOMAIN_XML_INTERNAL_*, - * whereas the public version cannot. */ -static char * + * whereas the public version cannot. Also, it appends to an existing + * buffer, rather than flattening to string. Return -1 on failure. */ +static int virDomainDefFormatInternal(virDomainDefPtr def, - unsigned int flags) + unsigned int flags, + virBufferPtr buf) { - virBuffer buf = VIR_BUFFER_INITIALIZER; + /* XXX Also need to take an indentation parameter - either int or + * string prefix, so that snapshot xml gets uniform indentation. */ unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; const char *type = NULL; @@ -9915,7 +9918,7 @@ virDomainDefFormatInternal(virDomainDefPtr def, virCheckFlags(DUMPXML_FLAGS | VIR_DOMAIN_XML_INTERNAL_STATUS | VIR_DOMAIN_XML_INTERNAL_ACTUAL_NET, - NULL); + -1); if (!(type = virDomainVirtTypeToString(def->virtType))) { virDomainReportError(VIR_ERR_INTERNAL_ERROR, @@ -9926,99 +9929,99 @@ virDomainDefFormatInternal(virDomainDefPtr def, if (def->id == -1) flags |= VIR_DOMAIN_XML_INACTIVE; - virBufferAsprintf(&buf, "<domain type='%s'", type); + virBufferAsprintf(buf, "<domain type='%s'", type); if (!(flags & VIR_DOMAIN_XML_INACTIVE)) - virBufferAsprintf(&buf, " id='%d'", def->id); + virBufferAsprintf(buf, " id='%d'", def->id); if (def->namespaceData && def->ns.href) - virBufferAsprintf(&buf, " %s", (def->ns.href)()); - virBufferAddLit(&buf, ">\n"); + virBufferAsprintf(buf, " %s", (def->ns.href)()); + virBufferAddLit(buf, ">\n"); - virBufferEscapeString(&buf, " <name>%s</name>\n", def->name); + virBufferEscapeString(buf, " <name>%s</name>\n", def->name); uuid = def->uuid; virUUIDFormat(uuid, uuidstr); - virBufferAsprintf(&buf, " <uuid>%s</uuid>\n", uuidstr); + virBufferAsprintf(buf, " <uuid>%s</uuid>\n", uuidstr); if (def->description) - virBufferEscapeString(&buf, " <description>%s</description>\n", + virBufferEscapeString(buf, " <description>%s</description>\n", def->description); - virBufferAsprintf(&buf, " <memory>%lu</memory>\n", def->mem.max_balloon); - virBufferAsprintf(&buf, " <currentMemory>%lu</currentMemory>\n", + virBufferAsprintf(buf, " <memory>%lu</memory>\n", def->mem.max_balloon); + virBufferAsprintf(buf, " <currentMemory>%lu</currentMemory>\n", def->mem.cur_balloon); /* add blkiotune only if there are any */ if (def->blkio.weight) { - virBufferAsprintf(&buf, " <blkiotune>\n"); - virBufferAsprintf(&buf, " <weight>%u</weight>\n", + virBufferAsprintf(buf, " <blkiotune>\n"); + virBufferAsprintf(buf, " <weight>%u</weight>\n", def->blkio.weight); - virBufferAsprintf(&buf, " </blkiotune>\n"); + virBufferAsprintf(buf, " </blkiotune>\n"); } /* add memtune only if there are any */ if (def->mem.hard_limit || def->mem.soft_limit || def->mem.min_guarantee || def->mem.swap_hard_limit) - virBufferAsprintf(&buf, " <memtune>\n"); + virBufferAsprintf(buf, " <memtune>\n"); if (def->mem.hard_limit) { - virBufferAsprintf(&buf, " <hard_limit>%lu</hard_limit>\n", + virBufferAsprintf(buf, " <hard_limit>%lu</hard_limit>\n", def->mem.hard_limit); } if (def->mem.soft_limit) { - virBufferAsprintf(&buf, " <soft_limit>%lu</soft_limit>\n", + virBufferAsprintf(buf, " <soft_limit>%lu</soft_limit>\n", def->mem.soft_limit); } if (def->mem.min_guarantee) { - virBufferAsprintf(&buf, " <min_guarantee>%lu</min_guarantee>\n", + virBufferAsprintf(buf, " <min_guarantee>%lu</min_guarantee>\n", def->mem.min_guarantee); } if (def->mem.swap_hard_limit) { - virBufferAsprintf(&buf, " <swap_hard_limit>%lu</swap_hard_limit>\n", + virBufferAsprintf(buf, " <swap_hard_limit>%lu</swap_hard_limit>\n", def->mem.swap_hard_limit); } if (def->mem.hard_limit || def->mem.soft_limit || def->mem.min_guarantee || def->mem.swap_hard_limit) - virBufferAsprintf(&buf, " </memtune>\n"); + virBufferAsprintf(buf, " </memtune>\n"); if (def->mem.hugepage_backed) { - virBufferAddLit(&buf, " <memoryBacking>\n"); - virBufferAddLit(&buf, " <hugepages/>\n"); - virBufferAddLit(&buf, " </memoryBacking>\n"); + virBufferAddLit(buf, " <memoryBacking>\n"); + virBufferAddLit(buf, " <hugepages/>\n"); + virBufferAddLit(buf, " </memoryBacking>\n"); } for (n = 0 ; n < def->cpumasklen ; n++) if (def->cpumask[n] != 1) allones = 0; - virBufferAddLit(&buf, " <vcpu"); + virBufferAddLit(buf, " <vcpu"); if (!allones) { char *cpumask = NULL; if ((cpumask = virDomainCpuSetFormat(def->cpumask, def->cpumasklen)) == NULL) goto cleanup; - virBufferAsprintf(&buf, " cpuset='%s'", cpumask); + virBufferAsprintf(buf, " cpuset='%s'", cpumask); VIR_FREE(cpumask); } if (def->vcpus != def->maxvcpus) - virBufferAsprintf(&buf, " current='%u'", def->vcpus); - virBufferAsprintf(&buf, ">%u</vcpu>\n", def->maxvcpus); + virBufferAsprintf(buf, " current='%u'", def->vcpus); + virBufferAsprintf(buf, ">%u</vcpu>\n", def->maxvcpus); if (def->cputune.shares || def->cputune.vcpupin || def->cputune.period || def->cputune.quota) - virBufferAddLit(&buf, " <cputune>\n"); + virBufferAddLit(buf, " <cputune>\n"); if (def->cputune.shares) - virBufferAsprintf(&buf, " <shares>%lu</shares>\n", + virBufferAsprintf(buf, " <shares>%lu</shares>\n", def->cputune.shares); if (def->cputune.period) - virBufferAsprintf(&buf, " <period>%llu</period>\n", + virBufferAsprintf(buf, " <period>%llu</period>\n", def->cputune.period); if (def->cputune.quota) - virBufferAsprintf(&buf, " <quota>%lld</quota>\n", + virBufferAsprintf(buf, " <quota>%lld</quota>\n", def->cputune.quota); if (def->cputune.vcpupin) { int i; for (i = 0; i < def->cputune.nvcpupin; i++) { - virBufferAsprintf(&buf, " <vcpupin vcpu='%u' ", + virBufferAsprintf(buf, " <vcpupin vcpu='%u' ", def->cputune.vcpupin[i]->vcpuid); char *cpumask = NULL; @@ -10031,17 +10034,17 @@ virDomainDefFormatInternal(virDomainDefPtr def, goto cleanup; } - virBufferAsprintf(&buf, "cpuset='%s'/>\n", cpumask); + virBufferAsprintf(buf, "cpuset='%s'/>\n", cpumask); VIR_FREE(cpumask); } } if (def->cputune.shares || def->cputune.vcpupin || def->cputune.period || def->cputune.quota) - virBufferAddLit(&buf, " </cputune>\n"); + virBufferAddLit(buf, " </cputune>\n"); if (def->numatune.memory.nodemask) - virBufferAddLit(&buf, " <numatune>\n"); + virBufferAddLit(buf, " <numatune>\n"); if (def->numatune.memory.nodemask) { char *nodemask = NULL; @@ -10053,32 +10056,32 @@ virDomainDefFormatInternal(virDomainDefPtr def, goto cleanup; } - virBufferAsprintf(&buf, " <memory mode='%s' nodeset='%s'/>\n", + virBufferAsprintf(buf, " <memory mode='%s' nodeset='%s'/>\n", virDomainNumatuneMemModeTypeToString(def->numatune.memory.mode), nodemask); VIR_FREE(nodemask); } if (def->numatune.memory.nodemask) - virBufferAddLit(&buf, " </numatune>\n"); + virBufferAddLit(buf, " </numatune>\n"); if (def->sysinfo) - virDomainSysinfoDefFormat(&buf, def->sysinfo); + virDomainSysinfoDefFormat(buf, def->sysinfo); if (def->os.bootloader) { - virBufferEscapeString(&buf, " <bootloader>%s</bootloader>\n", + virBufferEscapeString(buf, " <bootloader>%s</bootloader>\n", def->os.bootloader); if (def->os.bootloaderArgs) - virBufferEscapeString(&buf, " <bootloader_args>%s</bootloader_args>\n", + virBufferEscapeString(buf, " <bootloader_args>%s</bootloader_args>\n", def->os.bootloaderArgs); } - virBufferAddLit(&buf, " <os>\n"); + virBufferAddLit(buf, " <os>\n"); - virBufferAddLit(&buf, " <type"); + virBufferAddLit(buf, " <type"); if (def->os.arch) - virBufferAsprintf(&buf, " arch='%s'", def->os.arch); + virBufferAsprintf(buf, " arch='%s'", def->os.arch); if (def->os.machine) - virBufferAsprintf(&buf, " machine='%s'", def->os.machine); + virBufferAsprintf(buf, " machine='%s'", def->os.machine); /* * HACK: For xen driver we previously used bogus 'linux' as the * os type for paravirt, whereas capabilities declare it to @@ -10086,27 +10089,27 @@ virDomainDefFormatInternal(virDomainDefPtr def, */ if (def->virtType == VIR_DOMAIN_VIRT_XEN && STREQ(def->os.type, "xen")) - virBufferAsprintf(&buf, ">%s</type>\n", "linux"); + virBufferAsprintf(buf, ">%s</type>\n", "linux"); else - virBufferAsprintf(&buf, ">%s</type>\n", def->os.type); + virBufferAsprintf(buf, ">%s</type>\n", def->os.type); if (def->os.init) - virBufferEscapeString(&buf, " <init>%s</init>\n", + virBufferEscapeString(buf, " <init>%s</init>\n", def->os.init); if (def->os.loader) - virBufferEscapeString(&buf, " <loader>%s</loader>\n", + virBufferEscapeString(buf, " <loader>%s</loader>\n", def->os.loader); if (def->os.kernel) - virBufferEscapeString(&buf, " <kernel>%s</kernel>\n", + virBufferEscapeString(buf, " <kernel>%s</kernel>\n", def->os.kernel); if (def->os.initrd) - virBufferEscapeString(&buf, " <initrd>%s</initrd>\n", + virBufferEscapeString(buf, " <initrd>%s</initrd>\n", def->os.initrd); if (def->os.cmdline) - virBufferEscapeString(&buf, " <cmdline>%s</cmdline>\n", + virBufferEscapeString(buf, " <cmdline>%s</cmdline>\n", def->os.cmdline); if (def->os.root) - virBufferEscapeString(&buf, " <root>%s</root>\n", + virBufferEscapeString(buf, " <root>%s</root>\n", def->os.root); if (!def->os.bootloader) { @@ -10119,21 +10122,21 @@ virDomainDefFormatInternal(virDomainDefPtr def, def->os.bootDevs[n]); goto cleanup; } - virBufferAsprintf(&buf, " <boot dev='%s'/>\n", boottype); + virBufferAsprintf(buf, " <boot dev='%s'/>\n", boottype); } if (def->os.bootmenu != VIR_DOMAIN_BOOT_MENU_DEFAULT) { const char *enabled = (def->os.bootmenu == VIR_DOMAIN_BOOT_MENU_ENABLED ? "yes" : "no"); - virBufferAsprintf(&buf, " <bootmenu enable='%s'/>\n", enabled); + virBufferAsprintf(buf, " <bootmenu enable='%s'/>\n", enabled); } if (def->os.bios.useserial) { const char *useserial = (def->os.bios.useserial == VIR_DOMAIN_BIOS_USESERIAL_YES ? "yes" : "no"); - virBufferAsprintf(&buf, " <bios useserial='%s'/>\n", useserial); + virBufferAsprintf(buf, " <bios useserial='%s'/>\n", useserial); } } @@ -10146,14 +10149,14 @@ virDomainDefFormatInternal(virDomainDefPtr def, _("unexpected smbios mode %d"), def->os.smbios_mode); goto cleanup; } - virBufferAsprintf(&buf, " <smbios mode='%s'/>\n", mode); + virBufferAsprintf(buf, " <smbios mode='%s'/>\n", mode); } - virBufferAddLit(&buf, " </os>\n"); + virBufferAddLit(buf, " </os>\n"); if (def->features) { int i; - virBufferAddLit(&buf, " <features>\n"); + virBufferAddLit(buf, " <features>\n"); for (i = 0 ; i < VIR_DOMAIN_FEATURE_LAST ; i++) { if (def->features & (1 << i)) { const char *name = virDomainFeatureTypeToString(i); @@ -10162,91 +10165,91 @@ virDomainDefFormatInternal(virDomainDefPtr def, _("unexpected feature %d"), i); goto cleanup; } - virBufferAsprintf(&buf, " <%s/>\n", name); + virBufferAsprintf(buf, " <%s/>\n", name); } } - virBufferAddLit(&buf, " </features>\n"); + virBufferAddLit(buf, " </features>\n"); } - if (virCPUDefFormatBuf(&buf, def->cpu, " ", 0) < 0) + if (virCPUDefFormatBuf(buf, def->cpu, " ", 0) < 0) goto cleanup; - virBufferAsprintf(&buf, " <clock offset='%s'", + virBufferAsprintf(buf, " <clock offset='%s'", virDomainClockOffsetTypeToString(def->clock.offset)); switch (def->clock.offset) { case VIR_DOMAIN_CLOCK_OFFSET_VARIABLE: - virBufferAsprintf(&buf, " adjustment='%lld'", def->clock.data.adjustment); + virBufferAsprintf(buf, " adjustment='%lld'", def->clock.data.adjustment); break; case VIR_DOMAIN_CLOCK_OFFSET_TIMEZONE: - virBufferEscapeString(&buf, " timezone='%s'", def->clock.data.timezone); + virBufferEscapeString(buf, " timezone='%s'", def->clock.data.timezone); break; } if (def->clock.ntimers == 0) { - virBufferAddLit(&buf, "/>\n"); + virBufferAddLit(buf, "/>\n"); } else { - virBufferAddLit(&buf, ">\n"); + virBufferAddLit(buf, ">\n"); for (n = 0; n < def->clock.ntimers; n++) { - if (virDomainTimerDefFormat(&buf, def->clock.timers[n]) < 0) + if (virDomainTimerDefFormat(buf, def->clock.timers[n]) < 0) goto cleanup; } - virBufferAddLit(&buf, " </clock>\n"); + virBufferAddLit(buf, " </clock>\n"); } - if (virDomainLifecycleDefFormat(&buf, def->onPoweroff, + if (virDomainLifecycleDefFormat(buf, def->onPoweroff, "on_poweroff", virDomainLifecycleTypeToString) < 0) goto cleanup; - if (virDomainLifecycleDefFormat(&buf, def->onReboot, + if (virDomainLifecycleDefFormat(buf, def->onReboot, "on_reboot", virDomainLifecycleTypeToString) < 0) goto cleanup; - if (virDomainLifecycleDefFormat(&buf, def->onCrash, + if (virDomainLifecycleDefFormat(buf, def->onCrash, "on_crash", virDomainLifecycleCrashTypeToString) < 0) goto cleanup; - virBufferAddLit(&buf, " <devices>\n"); + virBufferAddLit(buf, " <devices>\n"); if (def->emulator) - virBufferEscapeString(&buf, " <emulator>%s</emulator>\n", + virBufferEscapeString(buf, " <emulator>%s</emulator>\n", def->emulator); for (n = 0 ; n < def->ndisks ; n++) - if (virDomainDiskDefFormat(&buf, def->disks[n], flags) < 0) + if (virDomainDiskDefFormat(buf, def->disks[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->ncontrollers ; n++) - if (virDomainControllerDefFormat(&buf, def->controllers[n], flags) < 0) + if (virDomainControllerDefFormat(buf, def->controllers[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->nleases ; n++) - if (virDomainLeaseDefFormat(&buf, def->leases[n]) < 0) + if (virDomainLeaseDefFormat(buf, def->leases[n]) < 0) goto cleanup; for (n = 0 ; n < def->nfss ; n++) - if (virDomainFSDefFormat(&buf, def->fss[n], flags) < 0) + if (virDomainFSDefFormat(buf, def->fss[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->nnets ; n++) - if (virDomainNetDefFormat(&buf, def->nets[n], flags) < 0) + if (virDomainNetDefFormat(buf, def->nets[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->nsmartcards ; n++) - if (virDomainSmartcardDefFormat(&buf, def->smartcards[n], flags) < 0) + if (virDomainSmartcardDefFormat(buf, def->smartcards[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->nserials ; n++) - if (virDomainChrDefFormat(&buf, def->serials[n], flags) < 0) + if (virDomainChrDefFormat(buf, def->serials[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->nparallels ; n++) - if (virDomainChrDefFormat(&buf, def->parallels[n], flags) < 0) + if (virDomainChrDefFormat(buf, def->parallels[n], flags) < 0) goto cleanup; /* If there's a PV console that's preferred.. */ if (def->console) { - if (virDomainChrDefFormat(&buf, def->console, flags) < 0) + if (virDomainChrDefFormat(buf, def->console, flags) < 0) goto cleanup; } else if (def->nserials != 0) { /* ..else for legacy compat duplicate the first serial device as a @@ -10254,17 +10257,17 @@ virDomainDefFormatInternal(virDomainDefPtr def, virDomainChrDef console; memcpy(&console, def->serials[0], sizeof(console)); console.deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE; - if (virDomainChrDefFormat(&buf, &console, flags) < 0) + if (virDomainChrDefFormat(buf, &console, flags) < 0) goto cleanup; } for (n = 0 ; n < def->nchannels ; n++) - if (virDomainChrDefFormat(&buf, def->channels[n], flags) < 0) + if (virDomainChrDefFormat(buf, def->channels[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->ninputs ; n++) if (def->inputs[n]->bus == VIR_DOMAIN_INPUT_BUS_USB && - virDomainInputDefFormat(&buf, def->inputs[n], flags) < 0) + virDomainInputDefFormat(buf, def->inputs[n], flags) < 0) goto cleanup; if (def->ngraphics > 0) { @@ -10276,33 +10279,33 @@ virDomainDefFormatInternal(virDomainDefPtr def, { .alias = NULL }, }; - if (virDomainInputDefFormat(&buf, &autoInput, flags) < 0) + if (virDomainInputDefFormat(buf, &autoInput, flags) < 0) goto cleanup; for (n = 0 ; n < def->ngraphics ; n++) - if (virDomainGraphicsDefFormat(&buf, def->graphics[n], flags) < 0) + if (virDomainGraphicsDefFormat(buf, def->graphics[n], flags) < 0) goto cleanup; } for (n = 0 ; n < def->nsounds ; n++) - if (virDomainSoundDefFormat(&buf, def->sounds[n], flags) < 0) + if (virDomainSoundDefFormat(buf, def->sounds[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->nvideos ; n++) - if (virDomainVideoDefFormat(&buf, def->videos[n], flags) < 0) + if (virDomainVideoDefFormat(buf, def->videos[n], flags) < 0) goto cleanup; for (n = 0 ; n < def->nhostdevs ; n++) - if (virDomainHostdevDefFormat(&buf, def->hostdevs[n], flags) < 0) + if (virDomainHostdevDefFormat(buf, def->hostdevs[n], flags) < 0) goto cleanup; if (def->watchdog) - virDomainWatchdogDefFormat (&buf, def->watchdog, flags); + virDomainWatchdogDefFormat (buf, def->watchdog, flags); if (def->memballoon) - virDomainMemballoonDefFormat (&buf, def->memballoon, flags); + virDomainMemballoonDefFormat (buf, def->memballoon, flags); - virBufferAddLit(&buf, " </devices>\n"); + virBufferAddLit(buf, " </devices>\n"); if (def->seclabel.model) { const char *sectype = virDomainSeclabelTypeToString(def->seclabel.type); @@ -10314,47 +10317,52 @@ virDomainDefFormatInternal(virDomainDefPtr def, (flags & VIR_DOMAIN_XML_INACTIVE)) { /* This is the default for inactive xml, so nothing to output. */ } else { - virBufferAsprintf(&buf, " <seclabel type='%s' model='%s' relabel='%s'>\n", + virBufferAsprintf(buf, " <seclabel type='%s' model='%s' relabel='%s'>\n", sectype, def->seclabel.model, def->seclabel.norelabel ? "no" : "yes"); if (def->seclabel.label) - virBufferEscapeString(&buf, " <label>%s</label>\n", + virBufferEscapeString(buf, " <label>%s</label>\n", def->seclabel.label); if (!def->seclabel.norelabel && def->seclabel.imagelabel) - virBufferEscapeString(&buf, " <imagelabel>%s</imagelabel>\n", + virBufferEscapeString(buf, " <imagelabel>%s</imagelabel>\n", def->seclabel.imagelabel); if (def->seclabel.baselabel && (def->seclabel.type == VIR_DOMAIN_SECLABEL_DYNAMIC)) - virBufferEscapeString(&buf, " <baselabel>%s</baselabel>\n", + virBufferEscapeString(buf, " <baselabel>%s</baselabel>\n", def->seclabel.baselabel); - virBufferAddLit(&buf, " </seclabel>\n"); + virBufferAddLit(buf, " </seclabel>\n"); } } if (def->namespaceData && def->ns.format) { - if ((def->ns.format)(&buf, def->namespaceData) < 0) + if ((def->ns.format)(buf, def->namespaceData) < 0) goto cleanup; } - virBufferAddLit(&buf, "</domain>\n"); + virBufferAddLit(buf, "</domain>\n"); - if (virBufferError(&buf)) + if (virBufferError(buf)) goto no_memory; - return virBufferContentAndReset(&buf); + return 0; no_memory: virReportOOMError(); cleanup: - virBufferFreeAndReset(&buf); - return NULL; + virBufferFreeAndReset(buf); + return -1; } char * virDomainDefFormat(virDomainDefPtr def, unsigned int flags) { + virBuffer buf = VIR_BUFFER_INITIALIZER; + virCheckFlags(DUMPXML_FLAGS, NULL); - return virDomainDefFormatInternal(def, flags); + if (virDomainDefFormatInternal(def, flags, &buf) < 0) + return NULL; + + return virBufferContentAndReset(&buf); } @@ -10362,7 +10370,6 @@ static char *virDomainObjFormat(virCapsPtr caps, virDomainObjPtr obj, unsigned int flags) { - char *config_xml = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; int state; int reason; @@ -10384,11 +10391,9 @@ static char *virDomainObjFormat(virCapsPtr caps, ((caps->privateDataXMLFormat)(&buf, obj->privateData)) < 0) goto error; - if (!(config_xml = virDomainDefFormatInternal(obj->def, flags))) + if (virDomainDefFormatInternal(obj->def, flags, &buf) < 0) goto error; - virBufferAdd(&buf, config_xml, strlen(config_xml)); - VIR_FREE(config_xml); virBufferAddLit(&buf, "</domstatus>\n"); if (virBufferError(&buf)) -- 1.7.4.4

Just like VM saved state images (virsh save), snapshots MUST track the inactive domain xml to detect any ABI incompatibilities. The indentation is not perfect, but functionality comes before form. Later patches will actually supply a full domain; for now, this wires up the storage to support one, but doesn't ever generate one in dumpxml output. Happily, libvirt.c was already rejecting use of VIR_DOMAIN_XML_SECURE from read-only connections, even though before this patch, there was no one ever using that flag and there was no information to be secured by the use of that flag. * src/libvirt.c (virDomainSnapshotGetXMLDesc): Document flag. * src/conf/domain_conf.h (_virDomainSnapshotDef): Add member. (virDomainSnapshotDefParseString, virDomainSnapshotDefFormat): Update signature. * src/conf/domain_conf.c (virDomainSnapshotDefFree): Clean up. (virDomainSnapshotDefParseString): Optionally parse domain. (virDomainSnapshotDefFormat): Output full domain. * src/esx/esx_driver.c (esxDomainSnapshotCreateXML) (esxDomainSnapshotGetXMLDesc): Update callers. * src/vbox/vbox_tmpl.c (vboxDomainSnapshotCreateXML) (vboxDomainSnapshotGetXMLDesc): Likewise. * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML) (qemuDomainSnapshotLoad, qemuDomainSnapshotGetXMLDesc) (qemuDomainSnapshotWriteMetadata): Likewise. * docs/formatsnapshot.html.in: Rework doc example. Based on a patch by Philipp Hahn. --- docs/formatsnapshot.html.in | 47 ++++++++++++++++++++++++++++------------ src/conf/domain_conf.c | 50 +++++++++++++++++++++++++++++++++++++------ src/conf/domain_conf.h | 7 +++++- src/esx/esx_driver.c | 4 +- src/libvirt.c | 7 +++++- src/qemu/qemu_driver.c | 20 +++++++++++++---- src/vbox/vbox_tmpl.c | 4 +- 7 files changed, 107 insertions(+), 32 deletions(-) diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index 79ed1d2..edf20e8 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -46,27 +46,46 @@ snapshots. Readonly. </dd> <dt><code>domain</code></dt> - <dd>The domain that this snapshot was taken against. This - element contains exactly one child element, uuid. This - specifies the uuid of the domain that this snapshot was taken - against. Readonly. + <dd>The domain that this snapshot was taken against. Older + versions of libvirt stored only a single child element, uuid; + reverting to a snapshot like this is risky if the current + state of the domain differs from the state that the domain was + created in, and requires the use of the + <code>VIR_DOMAIN_SNAPSHOT_REVERT_FORCE</code> flag + in <code>virDomainRevertToSnapshot()</code>. Newer versions + of libvirt store the entire + inactive <a href="formatdomain.html">domain configuration</a> + at the time of the snapshot. Readonly. </dd> </dl> - <h2><a name="example">Example</a></h2> + <h2><a name="example">Examples</a></h2> + <p>Using this XML on creation:</p> <pre> <domainsnapshot> - <name>os-updates</name> <description>Snapshot of OS install and updates</description> - <state>running</state> - <creationTime>1270477159</creationTime> - <parent> - <name>bare-os-install</name> - </parent> - <domain> - <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid> - </domain> </domainsnapshot></pre> + + <p>Will result in XML similar to this from + virDomainSnapshotGetXMLDesc:</p> + <pre> +<domainsnapshot> + <name>1270477159</name> + <description>Snapshot of OS install and updates</description> + <state>running</state> + <creationTime>1270477159</creationTime> + <parent> + <name>bare-os-install</name> + </parent> + <domain> + <name>fedora</name> + <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid> + <memory>1048576</memory> + ... + </devices> + </domain> +</domainsnapshot></pre> + </body> </html> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 27c77fe..d2a9849 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -10953,11 +10953,17 @@ void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) VIR_FREE(def->name); VIR_FREE(def->description); VIR_FREE(def->parent); + virDomainDefFree(def->dom); VIR_FREE(def); } -virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, - int newSnapshot) +/* If newSnapshot is true, caps, expectedVirtTypes, and flags are ignored. */ +virDomainSnapshotDefPtr +virDomainSnapshotDefParseString(const char *xmlStr, + int newSnapshot, + virCapsPtr caps, + unsigned int expectedVirtTypes, + unsigned int flags) { xmlXPathContextPtr ctxt = NULL; xmlDocPtr xml = NULL; @@ -10966,6 +10972,7 @@ virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, char *creation = NULL, *state = NULL; struct timeval tv; int active; + char *tmp; xml = virXMLParseCtxt(NULL, xmlStr, "domainsnapshot.xml", &ctxt); if (!xml) { @@ -11028,9 +11035,29 @@ virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, goto cleanup; } def->current = active != 0; - } - else + + /* Older snapshots were created with just <domain>/<uuid>, and + * lack domain/@type. In that case, leave dom NULL, and + * clients will have to decide between best effort + * initialization or outright failure. */ + if ((tmp = virXPathString("string(./domain/@type)", ctxt))) { + VIR_FREE(tmp); + xmlNodePtr domainNode = virXPathNode("./domain", ctxt); + if (!domainNode) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in snapshot")); + goto cleanup; + } + def->dom = virDomainDefParseNode(caps, xml, domainNode, + expectedVirtTypes, flags); + if (!def->dom) + goto cleanup; + } else { + VIR_WARN("parsing older snapshot that lacks domain"); + } + } else { def->creationTime = tv.tv_sec; + } ret = def; @@ -11047,10 +11074,15 @@ cleanup: char *virDomainSnapshotDefFormat(char *domain_uuid, virDomainSnapshotDefPtr def, + unsigned int flags, int internal) { virBuffer buf = VIR_BUFFER_INITIALIZER; + virCheckFlags(VIR_DOMAIN_XML_SECURE, NULL); + + flags |= VIR_DOMAIN_XML_INACTIVE; + virBufferAddLit(&buf, "<domainsnapshot>\n"); virBufferAsprintf(&buf, " <name>%s</name>\n", def->name); if (def->description) @@ -11065,9 +11097,13 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, } virBufferAsprintf(&buf, " <creationTime>%lld</creationTime>\n", def->creationTime); - virBufferAddLit(&buf, " <domain>\n"); - virBufferAsprintf(&buf, " <uuid>%s</uuid>\n", domain_uuid); - virBufferAddLit(&buf, " </domain>\n"); + if (def->dom) { + virDomainDefFormatInternal(def->dom, flags, &buf); + } else { + virBufferAddLit(&buf, " <domain>\n"); + virBufferAsprintf(&buf, " <uuid>%s</uuid>\n", domain_uuid); + virBufferAddLit(&buf, " </domain>\n"); + } if (internal) virBufferAsprintf(&buf, " <active>%d</active>\n", def->current); virBufferAddLit(&buf, "</domainsnapshot>\n"); diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 503fb58..93bb7c8 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1302,6 +1302,7 @@ struct _virDomainSnapshotDef { char *parent; long long creationTime; /* in seconds */ int state; + virDomainDefPtr dom; /* Internal use. */ bool current; /* At most one snapshot in the list should have this set */ @@ -1325,10 +1326,14 @@ struct _virDomainSnapshotObjList { }; virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, - int newSnapshot); + int newSnapshot, + virCapsPtr caps, + unsigned int expectedVirtTypes, + unsigned int flags); void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def); char *virDomainSnapshotDefFormat(char *domain_uuid, virDomainSnapshotDefPtr def, + unsigned int flags, int internal); virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, const virDomainSnapshotDefPtr def); diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index 90f55c3..8fbbe8a 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4223,7 +4223,7 @@ esxDomainSnapshotCreateXML(virDomainPtr domain, const char *xmlDesc, return NULL; } - def = virDomainSnapshotDefParseString(xmlDesc, 1); + def = virDomainSnapshotDefParseString(xmlDesc, 1, NULL, 0, 0); if (def == NULL) { return NULL; @@ -4319,7 +4319,7 @@ esxDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, virUUIDFormat(snapshot->domain->uuid, uuid_string); - xml = virDomainSnapshotDefFormat(uuid_string, &def, 0); + xml = virDomainSnapshotDefFormat(uuid_string, &def, flags, 0); cleanup: esxVI_VirtualMachineSnapshotTree_Free(&rootSnapshotList); diff --git a/src/libvirt.c b/src/libvirt.c index b0160fb..fac2047 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15517,10 +15517,15 @@ error: /** * virDomainSnapshotGetXMLDesc: * @snapshot: a domain snapshot object - * @flags: unused flag parameters; callers should pass 0 + * @flags: bitwise-OR of subset of virDomainXMLFlags * * Provide an XML description of the domain snapshot. * + * No security-sensitive data will be included unless @flags contains + * VIR_DOMAIN_XML_SECURE; this flag is rejected on read-only + * connections. For this API, @flags should not contain either + * VIR_DOMAIN_XML_INACTIVE or VIR_DOMAIN_XML_UPDATE_CPU. + * * Returns a 0 terminated UTF-8 encoded XML instance, or NULL in case of error. * the caller must free() the returned value. */ diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 8d769c9..7cf945a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -337,7 +337,10 @@ static void qemuDomainSnapshotLoad(void *payload, continue; } - def = virDomainSnapshotDefParseString(xmlStr, 0); + def = virDomainSnapshotDefParseString(xmlStr, 0, qemu_driver->caps, + QEMU_EXPECTED_VIRT_TYPES, + (VIR_DOMAIN_XML_INACTIVE | + VIR_DOMAIN_XML_SECURE)); if (def == NULL) { /* Nothing we can do here, skip this one */ VIR_ERROR(_("Failed to parse snapshot XML from file '%s'"), @@ -1610,7 +1613,8 @@ qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, char uuidstr[VIR_UUID_STRING_BUFLEN]; virUUIDFormat(vm->def->uuid, uuidstr); - newxml = virDomainSnapshotDefFormat(uuidstr, snapshot->def, 1); + newxml = virDomainSnapshotDefFormat(uuidstr, snapshot->def, + VIR_DOMAIN_XML_SECURE, 1); if (newxml == NULL) { virReportOOMError(); return -1; @@ -1636,6 +1640,12 @@ qemuDomainSnapshotWriteMetadata(virDomainObjPtr vm, _("failed to create snapshot file '%s'"), snapFile); goto cleanup; } + /* XXX need virsh snapshot-edit, before this makes sense: + * char *tmp; + * virAsprintf(&tmp, "snapshot-edit %s", vm->def->name); + * virEmitXMLWarning(fd, snapshot->def->name, tmp); + * VIR_FREE(tmp); + */ if (safewrite(fd, newxml, strlen(newxml)) != strlen(newxml)) { virReportSystemError(errno, _("Failed to write snapshot data to %s"), snapFile); @@ -8681,7 +8691,7 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, if (!qemuDomainSnapshotIsAllowed(vm)) goto cleanup; - if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1))) + if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1, NULL, 0, 0))) goto cleanup; if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) @@ -8908,7 +8918,7 @@ static char *qemuDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, virDomainSnapshotObjPtr snap = NULL; char uuidstr[VIR_UUID_STRING_BUFLEN]; - virCheckFlags(0, NULL); + virCheckFlags(VIR_DOMAIN_XML_SECURE, NULL); qemuDriverLock(driver); virUUIDFormat(snapshot->domain->uuid, uuidstr); @@ -8927,7 +8937,7 @@ static char *qemuDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, goto cleanup; } - xml = virDomainSnapshotDefFormat(uuidstr, snap->def, 0); + xml = virDomainSnapshotDefFormat(uuidstr, snap->def, flags, 0); cleanup: if (vm) diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 9e1c6e3..0dd5efa 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -5661,7 +5661,7 @@ vboxDomainSnapshotCreateXML(virDomainPtr dom, virCheckFlags(0, NULL); - if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1))) + if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1, NULL, 0, 0))) goto cleanup; vboxIIDFromUUID(&domiid, dom->uuid); @@ -5843,7 +5843,7 @@ vboxDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, def->state = VIR_DOMAIN_SHUTOFF; virUUIDFormat(dom->uuid, uuidstr); - ret = virDomainSnapshotDefFormat(uuidstr, def, 0); + ret = virDomainSnapshotDefFormat(uuidstr, def, flags, 0); cleanup: virDomainSnapshotDefFree(def); -- 1.7.4.4

Commit 69278878 fixed one direction of arbitrarily-named snapshots, but not the round trip path. While auditing domain_conf, I found a couple other instances that weren't escaping arbitrary strings. * src/conf/domain_conf.c (virDomainFSDefFormat) (virDomainGraphicsListenDefFormat, virDomainSnapshotDefFormat): Escape arbitrary strings. --- src/conf/domain_conf.c | 14 +++++++------- 1 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index d2a9849..7e8da15 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -8834,8 +8834,8 @@ virDomainFSDefFormat(virBufferPtr buf, } } - virBufferAsprintf(buf, " <target dir='%s'/>\n", - def->dst); + virBufferEscapeString(buf, " <target dir='%s'/>\n", + def->dst); if (def->readonly) virBufferAddLit(buf, " <readonly/>\n"); @@ -9637,7 +9637,7 @@ virDomainGraphicsListenDefFormat(virBufferPtr buf, if (def->network && (def->type == VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK)) { - virBufferAsprintf(buf, " network='%s'", def->network); + virBufferEscapeString(buf, " network='%s'", def->network); } virBufferAddLit(buf, "/>\n"); @@ -11084,15 +11084,15 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, flags |= VIR_DOMAIN_XML_INACTIVE; virBufferAddLit(&buf, "<domainsnapshot>\n"); - virBufferAsprintf(&buf, " <name>%s</name>\n", def->name); + virBufferEscapeString(&buf, " <name>%s</name>\n", def->name); if (def->description) - virBufferAsprintf(&buf, " <description>%s</description>\n", - def->description); + virBufferEscapeString(&buf, " <description>%s</description>\n", + def->description); virBufferAsprintf(&buf, " <state>%s</state>\n", virDomainStateTypeToString(def->state)); if (def->parent) { virBufferAddLit(&buf, " <parent>\n"); - virBufferAsprintf(&buf, " <name>%s</name>\n", def->parent); + virBufferEscapeString(&buf, " <name>%s</name>\n", def->parent); virBufferAddLit(&buf, " </parent>\n"); } virBufferAsprintf(&buf, " <creationTime>%lld</creationTime>\n", -- 1.7.4.4

* docs/schemas/domain.rng: Move guts... * docs/schemas/domaincommon.rng: ...to new file. * docs/schemas/domainsnapshot.rng: Allow new xml. * docs/schemas/Makefile.am (schema_DATA): Distribute new file. * tests/domainsnapshotxml2xmlout/full_domain.xml: New test. --- I've compressed this email by eliding the code motion; see git for the full patch. docs/schemas/Makefile.am | 1 + docs/schemas/domain.rng | 2555 +----------------------- docs/schemas/{domain.rng => domaincommon.rng} | 8 +- docs/schemas/domainsnapshot.rng | 14 +- tests/domainsnapshotxml2xmlout/full_domain.xml | 35 + 5 files changed, 49 insertions(+), 2564 deletions(-) copy docs/schemas/{domain.rng => domaincommon.rng} (99%) create mode 100644 tests/domainsnapshotxml2xmlout/full_domain.xml diff --git a/docs/schemas/Makefile.am b/docs/schemas/Makefile.am index 596c207..4413d9e 100644 --- a/docs/schemas/Makefile.am +++ b/docs/schemas/Makefile.am @@ -6,6 +6,7 @@ schema_DATA = \ basictypes.rng \ capability.rng \ domain.rng \ + domaincommon.rng \ domainsnapshot.rng \ interface.rng \ network.rng \ diff --git a/docs/schemas/domain.rng b/docs/schemas/domain.rng index dd8c41a..cf0be68 100644 --- a/docs/schemas/domain.rng +++ b/docs/schemas/domain.rng @@ -5,2558 +5,5 @@ <ref name="domain"/> </start> - <include href='basictypes.rng'/> - <include href='storageencryption.rng'/> - <include href='networkcommon.rng'/> - - <!-- - description element, maybe placed anywhere under the root ... - <define name="filter-param-value"> - <data type="string"> - <param name="pattern">[a-zA-Z0-9_\.:]+</param> - </data> - </define> + <include href='domaincommon.rng'/> </grammar> diff --git a/docs/schemas/domain.rng b/docs/schemas/domaincommon.rng similarity index 99% copy from docs/schemas/domain.rng copy to docs/schemas/domaincommon.rng index dd8c41a..756e892 100644 --- a/docs/schemas/domain.rng +++ b/docs/schemas/domaincommon.rng @@ -1,16 +1,12 @@ <?xml version="1.0"?> <grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> - <!-- We handle only document defining a domain --> - <start> - <ref name="domain"/> - </start> - + <!-- domain-related definitions used in multiple grammars --> <include href='basictypes.rng'/> <include href='storageencryption.rng'/> <include href='networkcommon.rng'/> <!-- - description element, maybe placed anywhere under the root + description element, may be placed anywhere under the root --> <define name="description"> <element name="description"> diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 410833f..a16d731 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -1,9 +1,12 @@ +<?xml version="1.0"?> <!-- A Relax NG schema for the libvirt domain snapshot properties XML format --> <grammar xmlns="http://relaxng.org/ns/structure/1.0"> <start> <ref name='domainsnapshot'/> </start> + <include href='domaincommon.rng'/> + <define name='domainsnapshot'> <element name='domainsnapshot'> <interleave> @@ -36,11 +39,14 @@ </element> </optional> <optional> - <element name='domain'> - <element name='uuid'> - <text/> + <choice> + <element name='domain'> + <element name='uuid'> + <ref name="UUID"/> + </element> </element> - </element> + <ref name='domain'/> + </choice> </optional> <optional> <element name='parent'> diff --git a/tests/domainsnapshotxml2xmlout/full_domain.xml b/tests/domainsnapshotxml2xmlout/full_domain.xml new file mode 100644 index 0000000..942bd7f --- /dev/null +++ b/tests/domainsnapshotxml2xmlout/full_domain.xml @@ -0,0 +1,35 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <parent> + <name>earlier_snap</name> + </parent> + <state>running</state> + <creationTime>1272917631</creationTime> +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory>219100</memory> + <currentMemory>219100</currentMemory> + <vcpu cpuset='1-4,8-20,525'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' unit='0'/> + </disk> + <controller type='ide' index='0'/> + <memballoon model='virtio'/> + </devices> +</domain> + <active>1</active> +</domainsnapshot> -- 1.7.4.4

When reverting to a snapshot, the inactive domain configuration has to be rolled back to what it was at the time of the snapshot. Additionally, if the VM is active and the snapshot was active, this now adds a failure if the two configurations are ABI incompatible, rather than risking qemu confusion. A future patch will add a VIR_DOMAIN_SNAPSHOT_FORCE flag, which will be required for three risky code paths - reverting to an older snapshot that lacked full domain information, reverting from running to a live snapshot that requires starting a new qemu process, and any reverting that stops a running vm. * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Copy out domain. (qemuDomainRevertToSnapshot): Perform ABI compatibility checks. --- src/qemu/qemu_driver.c | 64 +++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 7cf945a..d46baba 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8659,12 +8659,14 @@ cleanup: return ret; } -static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, - const char *xmlDesc, - unsigned int flags) +static virDomainSnapshotPtr +qemuDomainSnapshotCreateXML(virDomainPtr domain, + const char *xmlDesc, + unsigned int flags) { struct qemud_driver *driver = domain->conn->privateData; virDomainObjPtr vm = NULL; + char *xml = NULL; virDomainSnapshotObjPtr snap = NULL; virDomainSnapshotPtr snapshot = NULL; char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -8681,6 +8683,18 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, goto cleanup; } + if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1, NULL, 0, 0))) + goto cleanup; + + /* Easiest way to clone inactive portion of vm->def is via + * conversion in and back out of xml. */ + if (!(xml = virDomainDefFormat(vm->def, (VIR_DOMAIN_XML_INACTIVE | + VIR_DOMAIN_XML_SECURE))) || + !(def->dom = virDomainDefParseString(driver->caps, xml, + QEMU_EXPECTED_VIRT_TYPES, + VIR_DOMAIN_XML_INACTIVE))) + goto cleanup; + /* in a perfect world, we would allow qemu to tell us this. The problem * is that qemu only does this check device-by-device; so if you had a * domain that booted from a large qcow2 device, but had a secondary raw @@ -8691,9 +8705,6 @@ static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, if (!qemuDomainSnapshotIsAllowed(vm)) goto cleanup; - if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1, NULL, 0, 0))) - goto cleanup; - if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) goto cleanup; def = NULL; @@ -8742,6 +8753,7 @@ cleanup: virDomainObjUnlock(vm); } virDomainSnapshotDefFree(def); + VIR_FREE(xml); qemuDriverUnlock(driver); return snapshot; } @@ -9003,6 +9015,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, virDomainEventPtr event = NULL; qemuDomainObjPrivatePtr priv; int rc; + virDomainDefPtr config = NULL; virCheckFlags(0, -1); @@ -9033,7 +9046,30 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, * in the failure cases where we know there was no change? */ } + /* Prepare to copy the snapshot inactive xml as the config of this + * domain. Easiest way is by a round trip through xml. + * + * XXX Should domain snapshots track live xml rather + * than inactive xml? */ snap->def->current = true; + if (snap->def->dom) { + char *xml; + if (!(xml = virDomainDefFormat(snap->def->dom, + (VIR_DOMAIN_XML_INACTIVE | + VIR_DOMAIN_XML_SECURE)))) + goto cleanup; + config = virDomainDefParseString(driver->caps, xml, + QEMU_EXPECTED_VIRT_TYPES, + VIR_DOMAIN_XML_INACTIVE); + VIR_FREE(xml); + if (!config) + goto cleanup; + } else { + /* XXX Fail if VIR_DOMAIN_REVERT_FORCE is not set, rather than + * blindly hoping for the best. */ + VIR_WARN("snapshot is lacking rollback information for domain '%s'", + snap->def->name); + } if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; @@ -9046,6 +9082,15 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, * that is paused or running. We always pause before loadvm, * to have finer control. */ if (virDomainObjIsActive(vm)) { + + /* Check for ABI compatibility. */ + if (config && !virDomainDefCheckABIStability(vm->def, config)) { + /* XXX Add VIR_DOMAIN_REVERT_FORCE to permit killing + * and restarting a new qemu, since loadvm monitor + * command won't work. */ + goto endjob; + } + priv = vm->privateData; if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { if (qemuProcessStopCPUs(driver, vm, @@ -9073,7 +9118,12 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, * failed loadvm attempt? */ goto endjob; } + if (config) + virDomainObjAssignDef(vm, config, false); } else { + if (config) + virDomainObjAssignDef(vm, config, false); + rc = qemuProcessStart(snapshot->domain->conn, driver, vm, NULL, true, false, -1, NULL, snap, VIR_VM_OP_CREATE); @@ -9130,6 +9180,8 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, if (qemuDomainSnapshotRevertInactive(driver, vm, snap) < 0) goto endjob; + if (config) + virDomainObjAssignDef(vm, config, false); } ret = 0; -- 1.7.4.4

As discussed here: https://www.redhat.com/archives/libvir-list/2011-August/msg00361.html https://www.redhat.com/archives/libvir-list/2011-August/msg00552.html Adds snapshot attribute and transient sub-element: <devices> <disk type=... snapshot='no|internal|external'> ... <transient/> </disk> </devices> * docs/schemas/domaincommon.rng (snapshot): New define. (disk): Add snapshot and persistent attributes. * docs/formatdomain.html.in: Document them. * src/conf/domain_conf.h (virDomainDiskSnapshot): New enum. (_virDomainDiskDef): New fields. * tests/qemuxml2argvdata/qemuxml2argv-disk-transient.xml: New test of rng, no args counterpart until qemu support is complete. * tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.args: New file, snapshot attribute does not affect args. * tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.xml: Likewise. * tests/qemuxml2argvtest.c (mymain): Run new test. --- docs/formatdomain.html.in | 40 ++++++++++++++++++-- docs/schemas/domaincommon.rng | 17 ++++++++ src/conf/domain_conf.c | 35 ++++++++++++++++- src/conf/domain_conf.h | 12 ++++++ src/libvirt_private.syms | 2 + .../qemuxml2argv-disk-snapshot.args | 7 +++ .../qemuxml2argv-disk-snapshot.xml | 39 +++++++++++++++++++ .../qemuxml2argv-disk-transient.xml | 27 +++++++++++++ tests/qemuxml2argvtest.c | 2 + 9 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-transient.xml diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index f46771d..911dee5 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -889,7 +889,7 @@ <pre> ... <devices> - <disk type='file'> + <disk type='file' snapshot='external'> <driver name="tap" type="aio" cache="default"/> <source file='/var/lib/xen/images/fv0'/> <target dev='hda' bus='ide'/> @@ -910,8 +910,14 @@ </source> <target dev="hdb" bus="ide"/> <boot order='1'/> + <transient/> <address type='drive' controller='0' bus='1' unit='0'/> </disk> + <disk type='block' device='cdrom'> + <driver name='qemu' type='raw'/> + <target def='hdc' bus='ide'/> + <readonly/> + </disk> </devices> ...</pre> @@ -923,9 +929,23 @@ and refers to the underlying source for the disk. The optional <code>device</code> attribute indicates how the disk is to be exposed to the guest OS. Possible values for this attribute are "floppy", "disk" - and "cdrom", defaulting to "disk". - <span class="since">Since 0.0.3; "device" attribute since 0.1.4; - "network" attribute since 0.8.7</span></dd> + and "cdrom", defaulting to "disk". The + optional <code>snapshot</code> attribute indicates the default + behavior of the disk during disk snapshots: "internal" + requires a file format such as qcow2 that can store both the + snapshot and the data changes since the snapshot; + "external" will separate the snapshot from the live data; and + "no" means the disk will not participate in snapshots. + Read-only disks default to "no", while the default for other + disks depends on the hypervisor's capabilities. Some + hypervisors allow a per-snapshot choice as well, + during <a href="formatsnapshot.html">domain snapshot + creation</a>. Not all snapshot modes are supported; + for example, <code>snapshot='yes'</code> with a transient disk + generally does not make sense. <span class="since">Since 0.0.3; + "device" attribute since 0.1.4; + "network" attribute since 0.8.7; "snapshot" since + 0.9.5</span></dd> <dt><code>source</code></dt> <dd>If the disk <code>type</code> is "file", then the <code>file</code> attribute specifies the fully-qualified @@ -1032,11 +1052,23 @@ the <a href="formatstorageencryption.html">Storage Encryption</a> page for more information. </dd> + <dt><code>readonly</code></dt> + <dd>If present, this indicates the device cannot be modified by + the guest. For now, this is the default for disks with + attribute <code>type='cdrom'</code>. + </dd> <dt><code>shareable</code></dt> <dd>If present, this indicates the device is expected to be shared between domains (assuming the hypervisor and OS support this), which means that caching should be deactivated for that device. </dd> + <dt><code>transient</code></dt> + <dd>If present, this indicates that changes to the device + contents should be reverted automatically when the guest + exits. With some hypervisors, marking a disk transient + prevents the domain from participating in migration or + snapshots. <span class="since">Since 0.9.5</span> + </dd> <dt><code>serial</code></dt> <dd>If present, this specify serial number of virtual hard drive. For example, it may look diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index 756e892..0af9e0f 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -617,6 +617,11 @@ </element> </optional> <optional> + <element name="transient"> + <empty/> + </element> + </optional> + <optional> <element name="serial"> <ref name="diskSerial"/> </element> @@ -628,6 +633,15 @@ <ref name="address"/> </optional> </define> + <define name="snapshot"> + <attribute name="snapshot"> + <choice> + <value>no</value> + <value>internal</value> + <value>external</value> + </choice> + </attribute> + </define> <define name="lease"> <element name="lease"> @@ -667,6 +681,9 @@ </choice> </attribute> </optional> + <optional> + <ref name="snapshot"/> + </optional> <choice> <group> <attribute name="type"> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 7e8da15..df0e921 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -187,6 +187,12 @@ VIR_ENUM_IMPL(virDomainVirtioEventIdx, VIR_DOMAIN_VIRTIO_EVENT_IDX_LAST, "on", "off") +VIR_ENUM_IMPL(virDomainDiskSnapshot, VIR_DOMAIN_DISK_SNAPSHOT_LAST, + "default", + "no", + "internal", + "external") + VIR_ENUM_IMPL(virDomainController, VIR_DOMAIN_CONTROLLER_TYPE_LAST, "ide", "fdc", @@ -2073,6 +2079,7 @@ virDomainDiskDefParseXML(virCapsPtr caps, xmlNodePtr cur, host; char *type = NULL; char *device = NULL; + char *snapshot = NULL; char *driverName = NULL; char *driverType = NULL; char *source = NULL; @@ -2106,6 +2113,8 @@ virDomainDiskDefParseXML(virCapsPtr caps, def->type = VIR_DOMAIN_DISK_TYPE_FILE; } + snapshot = virXMLPropString(node, "snapshot"); + cur = node->children; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE) { @@ -2207,6 +2216,8 @@ virDomainDiskDefParseXML(virCapsPtr caps, def->readonly = 1; } else if (xmlStrEqual(cur->name, BAD_CAST "shareable")) { def->shared = 1; + } else if (xmlStrEqual(cur->name, BAD_CAST "transient")) { + def->transient = 1; } else if ((flags & VIR_DOMAIN_XML_INTERNAL_STATUS) && xmlStrEqual(cur->name, BAD_CAST "state")) { /* Legacy back-compat. Don't add any more attributes here */ @@ -2278,6 +2289,18 @@ virDomainDiskDefParseXML(virCapsPtr caps, goto error; } + if (snapshot) { + def->snapshot = virDomainDiskSnapshotTypeFromString(snapshot); + if (def->snapshot <= 0) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown disk snapshot setting '%s'"), + snapshot); + goto error; + } + } else if (def->readonly) { + def->snapshot = VIR_DOMAIN_DISK_SNAPSHOT_NO; + } + if (bus) { if ((def->bus = virDomainDiskBusTypeFromString(bus)) < 0) { virDomainReportError(VIR_ERR_INTERNAL_ERROR, @@ -2423,6 +2446,7 @@ virDomainDiskDefParseXML(virCapsPtr caps, cleanup: VIR_FREE(bus); VIR_FREE(type); + VIR_FREE(snapshot); VIR_FREE(target); VIR_FREE(source); while (nhosts > 0) { @@ -2448,7 +2472,7 @@ cleanup: no_memory: virReportOOMError(); - error: +error: virDomainDiskDefFree(def); def = NULL; goto cleanup; @@ -8636,8 +8660,13 @@ virDomainDiskDefFormat(virBufferPtr buf, } virBufferAsprintf(buf, - " <disk type='%s' device='%s'>\n", + " <disk type='%s' device='%s'", type, device); + if (def->snapshot && + !(def->snapshot == VIR_DOMAIN_DISK_SNAPSHOT_NO && def->readonly)) + virBufferAsprintf(buf, " snapshot='%s'", + virDomainDiskSnapshotTypeToString(def->snapshot)); + virBufferAddLit(buf, ">\n"); if (def->driverName || def->driverType || def->cachemode || def->ioeventfd || def->event_idx) { @@ -8711,6 +8740,8 @@ virDomainDiskDefFormat(virBufferPtr buf, virBufferAddLit(buf, " <readonly/>\n"); if (def->shared) virBufferAddLit(buf, " <shareable/>\n"); + if (def->transient) + virBufferAddLit(buf, " <transient/>\n"); if (def->serial) virBufferEscapeString(buf, " <serial>%s</serial>\n", def->serial); diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 93bb7c8..7dbf353 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -217,6 +217,15 @@ enum virDomainVirtioEventIdx { VIR_DOMAIN_VIRTIO_EVENT_IDX_LAST }; +enum virDomainDiskSnapshot { + VIR_DOMAIN_DISK_SNAPSHOT_DEFAULT = 0, + VIR_DOMAIN_DISK_SNAPSHOT_NO, + VIR_DOMAIN_DISK_SNAPSHOT_INTERNAL, + VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL, + + VIR_DOMAIN_DISK_SNAPSHOT_LAST +}; + /* Stores the virtual disk configuration */ typedef struct _virDomainDiskDef virDomainDiskDef; typedef virDomainDiskDef *virDomainDiskDefPtr; @@ -238,8 +247,10 @@ struct _virDomainDiskDef { int iomode; int ioeventfd; int event_idx; + int snapshot; /* enum virDomainDiskSnapshot */ unsigned int readonly : 1; unsigned int shared : 1; + unsigned int transient : 1; virDomainDeviceInfo info; virStorageEncryptionPtr encryption; }; @@ -1689,6 +1700,7 @@ VIR_ENUM_DECL(virDomainDiskCache) VIR_ENUM_DECL(virDomainDiskErrorPolicy) VIR_ENUM_DECL(virDomainDiskProtocol) VIR_ENUM_DECL(virDomainDiskIo) +VIR_ENUM_DECL(virDomainDiskSnapshot) VIR_ENUM_DECL(virDomainIoEventFd) VIR_ENUM_DECL(virDomainVirtioEventIdx) VIR_ENUM_DECL(virDomainController) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 74c5ff4..9530567 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -286,6 +286,8 @@ virDomainDiskIoTypeFromString; virDomainDiskIoTypeToString; virDomainDiskRemove; virDomainDiskRemoveByName; +virDomainDiskSnapshotTypeFromString; +virDomainDiskSnapshotTypeToString; virDomainDiskTypeFromString; virDomainDiskTypeToString; virDomainFSDefFree; diff --git a/tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.args b/tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.args new file mode 100644 index 0000000..7e62942 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.args @@ -0,0 +1,7 @@ +LC_ALL=C PATH=/bin HOME=/home/test USER=test LOGNAME=test /usr/bin/qemu -S -M \ +pc -m 214 -smp 1 -nographic -monitor unix:/tmp/test-monitor,server,nowait \ +-no-acpi -boot c \ +-drive file=/dev/HostVG/QEMUGuest1,if=ide,bus=0,unit=0,format=qcow2,cache=none \ +-drive file=/dev/HostVG/QEMUGuest3,if=ide,bus=2,unit=0,format=qcow2,cache=none \ +-drive file=/dev/HostVG/QEMUGuest2,if=ide,media=cdrom,bus=1,unit=0,format=raw \ +-net none -serial none -parallel none -usb diff --git a/tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.xml b/tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.xml new file mode 100644 index 0000000..aeb2315 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-disk-snapshot.xml @@ -0,0 +1,39 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory>219100</memory> + <currentMemory>219100</currentMemory> + <vcpu>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk' snapshot='internal'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' unit='0'/> + </disk> + <disk type='block' device='cdrom' snapshot='no'> + <driver name='qemu' type='raw'/> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='hdc' bus='ide'/> + <readonly/> + <address type='drive' controller='0' bus='1' unit='0'/> + </disk> + <disk type='block' device='disk' snapshot='external'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest3'/> + <target dev='hdb' bus='ide'/> + <address type='drive' controller='0' bus='2' unit='0'/> + </disk> + <controller type='ide' index='0'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxml2argvdata/qemuxml2argv-disk-transient.xml b/tests/qemuxml2argvdata/qemuxml2argv-disk-transient.xml new file mode 100644 index 0000000..df49c48 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-disk-transient.xml @@ -0,0 +1,27 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory>219100</memory> + <currentMemory>219100</currentMemory> + <vcpu>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <driver name='qemu' type='qcow2' cache='none'/> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <transient/> + <address type='drive' controller='0' bus='0' unit='0'/> + </disk> + <controller type='ide' index='0'/> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 6e8da5e..c7b1707 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -359,6 +359,8 @@ mymain(void) DO_TEST("disk-ioeventfd", false, QEMU_CAPS_DRIVE, QEMU_CAPS_VIRTIO_IOEVENTFD, QEMU_CAPS_VIRTIO_TX_ALG, QEMU_CAPS_DEVICE); + DO_TEST("disk-snapshot", false, + QEMU_CAPS_DRIVE, QEMU_CAPS_DRIVE_CACHE_V2, QEMU_CAPS_DRIVE_FORMAT); DO_TEST("event_idx", false, QEMU_CAPS_DRIVE, QEMU_CAPS_VIRTIO_BLK_EVENT_IDX, -- 1.7.4.4

The previous patch introduced new config, but if a hypervisor does not support that new config, someone can write XML that does not behave as documented. This prevents some of those cases by explicitly rejecting transient disks for several hypervisors. Disk snapshots will require a new flag to actually affect a snapshot creation, so there's not much to reject there. * src/qemu/qemu_command.c (qemuBuildDriveStr): Reject transient disks for now. * src/libxl/libxl_conf.c (libxlMakeDisk): Likewise. * src/xenxs/xen_sxpr.c (xenFormatSxprDisk): Likewise. * src/xenxs/xen_xm.c (xenFormatXMDisk): Likewise. --- src/libxl/libxl_conf.c | 5 +++++ src/qemu/qemu_command.c | 5 +++++ src/xenxs/xen_sxpr.c | 5 +++++ src/xenxs/xen_xm.c | 5 +++++ 4 files changed, 20 insertions(+), 0 deletions(-) diff --git a/src/libxl/libxl_conf.c b/src/libxl/libxl_conf.c index 09f3be8..b9bce14 100644 --- a/src/libxl/libxl_conf.c +++ b/src/libxl/libxl_conf.c @@ -537,6 +537,11 @@ libxlMakeDisk(virDomainDefPtr def, virDomainDiskDefPtr l_disk, x_disk->unpluggable = 1; x_disk->readwrite = !l_disk->readonly; x_disk->is_cdrom = l_disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM ? 1 : 0; + if (l_disk->transient) { + libxlError(VIR_ERR_INTERNAL_ERROR, "%s", + _("libxenlight does not support transient disks")); + return -1; + } x_disk->domid = def->id; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 61fc2c7..4011c21 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1498,6 +1498,11 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, if (disk->readonly && qemuCapsGet(qemuCaps, QEMU_CAPS_DRIVE_READONLY)) virBufferAddLit(&opt, ",readonly=on"); + if (disk->transient) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("transient disks not supported yet")); + goto error; + } if (disk->driverType && *disk->driverType != '\0' && disk->type != VIR_DOMAIN_DISK_TYPE_DIR && qemuCapsGet(qemuCaps, QEMU_CAPS_DRIVE_FORMAT)) diff --git a/src/xenxs/xen_sxpr.c b/src/xenxs/xen_sxpr.c index 1a8a541..5e278ae 100644 --- a/src/xenxs/xen_sxpr.c +++ b/src/xenxs/xen_sxpr.c @@ -1710,6 +1710,11 @@ xenFormatSxprDisk(virDomainDiskDefPtr def, virBufferAddLit(buf, "(mode 'w!')"); else virBufferAddLit(buf, "(mode 'w')"); + if (def->transient) { + XENXS_ERROR(VIR_ERR_CONFIG_UNSUPPORTED, + _("transient disks not supported yet")); + return -1; + } if (!isAttach) virBufferAddLit(buf, ")"); diff --git a/src/xenxs/xen_xm.c b/src/xenxs/xen_xm.c index cb31226..03857c8 100644 --- a/src/xenxs/xen_xm.c +++ b/src/xenxs/xen_xm.c @@ -1164,6 +1164,11 @@ static int xenFormatXMDisk(virConfValuePtr list, virBufferAddLit(&buf, ",!"); else virBufferAddLit(&buf, ",w"); + if (disk->transient) { + XENXS_ERROR(VIR_ERR_CONFIG_UNSUPPORTED, + _("transient disks not supported yet")); + return -1; + } if (virBufferError(&buf)) { virReportOOMError(); -- 1.7.4.4

New flag bits are worth exposing via virsh. Additionally, even though I recently added 'virsh snapshot-parent', doing it one snapshot at a time is painful, so make it possible to expand the snapshot-list table at once. In the case of snapshot-list --roots, it's possible to emulate this even when talking to an older server that lacks the bit; whereas --metadata requires a newer server. * tools/virsh.c (cmdSnapshotDumpXML, cmdSnapshotCurrent): Add --security-info. (cmdSnapshotList): Add --parent, --roots, --metadata. * tools/virsh.pod (snapshot-dumpxml, snapshot-current) (snapshot-list): Document these. --- tools/virsh.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++----- tools/virsh.pod | 22 +++++++++++--- 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 8069a8c..db6c71e 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12303,6 +12303,8 @@ static const vshCmdInfo info_snapshot_current[] = { static const vshCmdOptDef opts_snapshot_current[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"name", VSH_OT_BOOL, 0, N_("list the name, rather than the full xml")}, + {"security-info", VSH_OT_BOOL, 0, + N_("include security sensitive information in XML dump")}, {NULL, 0, 0, NULL} }; @@ -12314,6 +12316,10 @@ cmdSnapshotCurrent(vshControl *ctl, const vshCmd *cmd) int current; virDomainSnapshotPtr snapshot = NULL; char *xml = NULL; + int flags = 0; + + if (vshCommandOptBool(cmd, "security-info")) + flags |= VIR_DOMAIN_XML_SECURE; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12331,7 +12337,7 @@ cmdSnapshotCurrent(vshControl *ctl, const vshCmd *cmd) if (!(snapshot = virDomainSnapshotCurrent(dom, 0))) goto cleanup; - xml = virDomainSnapshotGetXMLDesc(snapshot, 0); + xml = virDomainSnapshotGetXMLDesc(snapshot, flags); if (!xml) goto cleanup; @@ -12377,6 +12383,10 @@ static const vshCmdInfo info_snapshot_list[] = { static const vshCmdOptDef opts_snapshot_list[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"parent", VSH_OT_BOOL, 0, N_("add a column showing parent snapshot")}, + {"roots", VSH_OT_BOOL, 0, N_("list only snapshots without parents")}, + {"metadata", VSH_OT_BOOL, 0, + N_("list only snapshots that have metadata that would prevent undefine")}, {NULL, 0, 0, NULL} }; @@ -12385,6 +12395,9 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom = NULL; bool ret = false; + int flags = 0; + int parent_filter = 0; /* -1 for roots filtering, 0 for no parent + information needed, 1 for parent column */ int numsnaps; char **names = NULL; int actual = 0; @@ -12394,11 +12407,27 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) char *doc = NULL; virDomainSnapshotPtr snapshot = NULL; char *state = NULL; + char *parent = NULL; long long creation_longlong; time_t creation_time_t; char timestr[100]; struct tm time_info; + if (vshCommandOptBool(cmd, "parent")) { + if (vshCommandOptBool(cmd, "roots")) { + vshError(ctl, "%s", + _("--parent and --roots are mutually exlusive")); + return false; + } + parent_filter = 1; + } else if (vshCommandOptBool(cmd, "roots")) { + flags |= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS; + } + + if (vshCommandOptBool(cmd, "metadata")) { + flags |= VIR_DOMAIN_SNAPSHOT_LIST_METADATA; + } + if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12406,19 +12435,35 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) if (dom == NULL) goto cleanup; - numsnaps = virDomainSnapshotNum(dom, 0); + numsnaps = virDomainSnapshotNum(dom, flags); + + /* Fall back to simulation if --roots was unsupported. */ + if (numsnaps < 0 && last_error->code == VIR_ERR_INVALID_ARG && + (flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS)) { + virFreeError(last_error); + last_error = NULL; + parent_filter = -1; + flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_ROOTS; + numsnaps = virDomainSnapshotNum(dom, flags); + } if (numsnaps < 0) goto cleanup; - vshPrintExtra(ctl, " %-20s %-25s %s\n", _("Name"), _("Creation Time"), _("State")); - vshPrintExtra(ctl, "---------------------------------------------------\n"); + if (parent_filter > 0) + vshPrintExtra(ctl, " %-20s %-25s %-15s %s", + _("Name"), _("Creation Time"), _("State"), _("Parent")); + else + vshPrintExtra(ctl, " %-20s %-25s %s", + _("Name"), _("Creation Time"), _("State")); + vshPrintExtra(ctl, "\n\ +------------------------------------------------------------\n"); if (numsnaps) { if (VIR_ALLOC_N(names, numsnaps) < 0) goto cleanup; - actual = virDomainSnapshotListNames(dom, names, numsnaps, 0); + actual = virDomainSnapshotListNames(dom, names, numsnaps, flags); if (actual < 0) goto cleanup; @@ -12426,6 +12471,7 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) for (i = 0; i < actual; i++) { /* free up memory from previous iterations of the loop */ + VIR_FREE(parent); VIR_FREE(state); if (snapshot) virDomainSnapshotFree(snapshot); @@ -12445,6 +12491,13 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) if (!xml) continue; + if (parent_filter) { + parent = virXPathString("string(/domainsnapshot/parent/name)", + ctxt); + if (!parent && parent_filter < 0) + continue; + } + state = virXPathString("string(/domainsnapshot/state)", ctxt); if (state == NULL) continue; @@ -12457,9 +12510,14 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) continue; } localtime_r(&creation_time_t, &time_info); - strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z", &time_info); + strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z", + &time_info); - vshPrint(ctl, " %-20s %-25s %s\n", names[i], timestr, state); + if (parent) + vshPrint(ctl, " %-20s %-25s %-15s %s\n", + names[i], timestr, state, parent); + else + vshPrint(ctl, " %-20s %-25s %s\n", names[i], timestr, state); } } @@ -12467,6 +12525,7 @@ cmdSnapshotList(vshControl *ctl, const vshCmd *cmd) cleanup: /* this frees up memory from the last iteration of the loop */ + VIR_FREE(parent); VIR_FREE(state); if (snapshot) virDomainSnapshotFree(snapshot); @@ -12494,6 +12553,8 @@ static const vshCmdInfo info_snapshot_dumpxml[] = { static const vshCmdOptDef opts_snapshot_dumpxml[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")}, + {"security-info", VSH_OT_BOOL, 0, + N_("include security sensitive information in XML dump")}, {NULL, 0, 0, NULL} }; @@ -12505,6 +12566,10 @@ cmdSnapshotDumpXML(vshControl *ctl, const vshCmd *cmd) const char *name = NULL; virDomainSnapshotPtr snapshot = NULL; char *xml = NULL; + int flags = 0; + + if (vshCommandOptBool(cmd, "security-info")) + flags |= VIR_DOMAIN_XML_SECURE; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12520,7 +12585,7 @@ cmdSnapshotDumpXML(vshControl *ctl, const vshCmd *cmd) if (snapshot == NULL) goto cleanup; - xml = virDomainSnapshotGetXMLDesc(snapshot, 0); + xml = virDomainSnapshotGetXMLDesc(snapshot, flags); if (!xml) goto cleanup; diff --git a/tools/virsh.pod b/tools/virsh.pod index 337fcf4..0b319ee 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -527,7 +527,7 @@ Output the domain information as an XML dump to stdout, this format can be used by the B<create> command. Additional options affecting the XML dump may be used. I<--inactive> tells virsh to dump domain configuration that will be used on next start of the domain as opposed to the current domain configuration. -Using I<--security-info> security sensitive information will also be included +Using I<--security-info> will also include security sensitive information in the XML dump. I<--update-cpu> updates domain CPU requirements according to host CPU. @@ -1607,19 +1607,31 @@ Create a snapshot for domain I<domain> with the given <name> and value. If I<--print-xml> is specified, then XML appropriate for I<snapshot-create> is output, rather than actually creating a snapshot. -=item B<snapshot-current> I<domain> [I<--name>] +=item B<snapshot-current> I<domain> [I<--name>] [I<--security-info>] Output the snapshot XML for the domain's current snapshot (if any). If I<--name> is specified, just list the snapshot name instead of the -full xml. +full xml. Otherwise, using I<--security-info> will also include security +sensitive information. -=item B<snapshot-list> I<domain> +=item B<snapshot-list> I<domain> [{I<--parent> | I<--roots>}] [I<--metadata>] List all of the available snapshots for the given domain. -=item B<snapshot-dumpxml> I<domain> I<snapshot> +If I<--parent> is specified, add a column to the output table giving +the name of the parent of each snapshot. + +If I<--roots> is specified, the list will be filtered to just snapshots +that have no parents; this option is not compatible with I<--parent>. + +If I<--metadata> is specified, the list will be filtered to just +snapshots that involve libvirt metadata, and thus would interfere in +using the B<undefine> or B<destroy> commands on that domain. + +=item B<snapshot-dumpxml> I<domain> I<snapshot> [I<--security-info>] Output the snapshot XML for the domain's snapshot named I<snapshot>. +Using I<--security-info> will also include security sensitive information. =item B<snapshot-parent> I<domain> I<snapshot> -- 1.7.4.4

Reverting to a state prior to an external snapshot risks corrupting any other branches in the snapshot hierarchy that were using the snapshot as a read-only backing file. So disk snapshot code will default to preventing reverting to a snapshot that has any children, meaning that deleting just the children of a snapshot becomes a useful operation in preparing that snapshot for being a future reversion target. The code for the new flag is simple - it's one less deletion. * include/libvirt/libvirt.h.in (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY): New flag. * src/libvirt.c (virDomainSnapshotDelete): Document it, and enforce mutual exclusion. * src/qemu/qemu_driver.c (qemuDomainSnapshotDelete): Implement it. --- include/libvirt/libvirt.h.in | 1 + src/libvirt.c | 32 +++++++++++++++++++++++--------- src/qemu/qemu_driver.c | 11 ++++++++--- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 36f1b34..49fe6b3 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2607,6 +2607,7 @@ int virDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, typedef enum { VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), /* Also delete children */ VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY = (1 << 1), /* Delete just metadata */ + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY = (1 << 2), /* Delete just children */ } virDomainSnapshotDeleteFlags; int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, diff --git a/src/libvirt.c b/src/libvirt.c index fac2047..2c84e7e 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15857,16 +15857,22 @@ error: * * Delete the snapshot. * - * If @flags is 0, then just this snapshot is deleted, and changes from - * this snapshot are automatically merged into children snapshots. If - * @flags includes VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, then this snapshot - * and any children snapshots are deleted. If @flags includes - * VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, then any snapshot metadata - * tracked by libvirt is removed while keeping the snapshot contents - * intact; if a hypervisor does not require any libvirt metadata to - * track snapshots, then this flag is silently ignored. + * If @flags is 0, then just this snapshot is deleted, and changes + * from this snapshot are automatically merged into children + * snapshots. If @flags includes VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN, + * then this snapshot and any descendant snapshots are deleted. If + * @flags includes VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, then any + * descendant snapshots are deleted, but this snapshot remains. These + * two flags are mutually exclusive. * - * Returns 0 if the snapshot was successfully deleted, -1 on error. + * If @flags includes VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, then + * any snapshot metadata tracked by libvirt is removed while keeping + * the snapshot contents intact; if a hypervisor does not require any + * libvirt metadata to track snapshots, then this flag is silently + * ignored. + * + * Returns 0 if the selected snapshot(s) were successfully deleted, + * -1 on error. */ int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, @@ -15891,6 +15897,14 @@ virDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto error; } + if ((flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) && + (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) { + virLibDomainError(VIR_ERR_INVALID_ARG, + _("children and children_only flags are " + "mutually exclusive")); + goto error; + } + if (conn->driver->domainSnapshotDelete) { int ret = conn->driver->domainSnapshotDelete(snapshot, flags); if (ret < 0) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index d46baba..a8cd9f8 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -9257,7 +9257,8 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY); virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | - VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY, -1); + VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY, -1); qemuDriverLock(driver); virUUIDFormat(snapshot->domain->uuid, uuidstr); @@ -9279,7 +9280,8 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; - if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) { + if (flags & (VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | + VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)) { rem.driver = driver; rem.vm = vm; rem.metadata_only = metadata_only; @@ -9302,7 +9304,10 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto endjob; } - ret = qemuDomainSnapshotDiscard(driver, vm, snap, metadata_only); + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) + ret = 0; + else + ret = qemuDomainSnapshotDiscard(driver, vm, snap, metadata_only); endjob: if (qemuDomainObjEndJob(driver, vm) == 0) -- 1.7.4.4

It would technically be possible to have virsh compute the list of descendants of a given snapshot, then delete those one at a time. But it's complex, and not worth writing for a first cut at implementing the new flags. * tools/virsh.c (cmdSnapshotDelete): Add --children-only, --metadata. * tools/virsh.pod (snapshot-delete): Document them. --- tools/virsh.c | 15 ++++++++++++++- tools/virsh.pod | 12 ++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index db6c71e..c01edc1 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12737,6 +12737,9 @@ static const vshCmdOptDef opts_snapshot_delete[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")}, {"children", VSH_OT_BOOL, 0, N_("delete snapshot and all children")}, + {"children-only", VSH_OT_BOOL, 0, N_("delete children but not snapshot")}, + {"metadata", VSH_OT_BOOL, 0, + N_("delete only libvirt metadata, leaving snapshot contents behind")}, {NULL, 0, 0, NULL} }; @@ -12761,13 +12764,23 @@ cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool(cmd, "children")) flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN; + if (vshCommandOptBool(cmd, "children-only")) + flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY; + if (vshCommandOptBool(cmd, "metadata")) + flags |= VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY; snapshot = virDomainSnapshotLookupByName(dom, name, 0); if (snapshot == NULL) goto cleanup; + /* XXX If we wanted, we could emulate DELETE_CHILDREN_ONLY even on + * older servers that reject the flag, by manually computing the + * list of descendants. But that's a lot of code to maintain. */ if (virDomainSnapshotDelete(snapshot, flags) == 0) { - vshPrint(ctl, _("Domain snapshot %s deleted\n"), name); + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) + vshPrint(ctl, _("Domain snapshot %s children deleted\n"), name); + else + vshPrint(ctl, _("Domain snapshot %s deleted\n"), name); } else { vshError(ctl, _("Failed to delete snapshot %s"), name); goto cleanup; diff --git a/tools/virsh.pod b/tools/virsh.pod index 0b319ee..1cbe5c3 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1645,12 +1645,20 @@ snapshot was taken will be lost. Also note that the state of the domain after snapshot-revert is complete will be the state of the domain at the time the original snapshot was taken. -=item B<snapshot-delete> I<domain> I<snapshot> I<--children> +=item B<snapshot-delete> I<domain> I<snapshot> [I<--metadata>] +[{I<--children> | I<--children-only>}] Delete the snapshot for the domain named I<snapshot>. If this snapshot has child snapshots, changes from this snapshot will be merged into the children. If I<--children> is passed, then delete this snapshot and any -children of this snapshot. +children of this snapshot. If I<--children-only> is passed, then delete +any children of this snapshot, but leave this snapshot intact. These +two flags are mutually exclusive. + +If I<--metadata> is specified, then only delete the snapshot metadata +maintained by libvirt, while leaving the snapshot contents intact for +access by external tools; otherwise deleting a snapshot also removes +the data contents from that point in time. =back -- 1.7.4.4

Since a snapshot is fully recoverable, it is useful to have a snapshot as a means of hibernating a guest, then reverting to the snapshot to wake the guest up. This mode of usage is similar to 'virsh save/virsh restore', except that virsh save uses an external file while virsh snapshot keeps the vm state internal to a qcow2 file. In the usage pattern of snapshot/revert for hibernating a guest, there is no need to keep the guest running between the two points in time, especially since that would generate runtime state that would just be discarded. Add a flag to make it possible to stop the domain after the snapshot has completed. * include/libvirt/libvirt.h.in (VIR_DOMAIN_SNAPSHOT_CREATE_HALT): New flag. * src/libvirt.c (virDomainSnapshotCreateXML): Document it. * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML) (qemuDomainSnapshotCreateActive): Implement it. --- include/libvirt/libvirt.h.in | 5 +++++ src/libvirt.c | 15 ++++++++++++++- src/qemu/qemu_driver.c | 33 +++++++++++++++++++++++++++++---- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 49fe6b3..e07dc20 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2559,6 +2559,11 @@ typedef struct _virDomainSnapshot virDomainSnapshot; */ typedef virDomainSnapshot *virDomainSnapshotPtr; +typedef enum { + VIR_DOMAIN_SNAPSHOT_CREATE_HALT = (1 << 0), /* Stop running guest after + snapshot is complete */ +} virDomainSnapshotCreateFlags; + /* Take a snapshot of the current VM state */ virDomainSnapshotPtr virDomainSnapshotCreateXML(virDomainPtr domain, const char *xmlDesc, diff --git a/src/libvirt.c b/src/libvirt.c index 2c84e7e..ffd27bc 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15464,11 +15464,24 @@ error: * virDomainSnapshotCreateXML: * @domain: a domain object * @xmlDesc: string containing an XML description of the domain - * @flags: unused flag parameters; callers should pass 0 + * @flags: bitwise-OR of virDomainSnapshotCreateFlags * * Creates a new snapshot of a domain based on the snapshot xml * contained in xmlDesc. * + * If @flags is 0, the domain can be active, in which case the + * snapshot will be a system checkpoint (both disk state and runtime + * VM state such as RAM contents), where reverting to the snapshot is + * the same as resuming from hibernation (TCP connections may have + * timed out, but everything else picks up where it left off); or + * the domain can be inactive, in which case the snapshot includes + * just the disk state prior to booting. + * + * If @flags includes VIR_DOMAIN_SNAPSHOT_CREATE_HALT, then the domain + * will be inactive after the snapshot completes, regardless of whether + * it was active before; otherwise, a running domain will still be + * running after the snapshot. This flag is invalid on transient domains. + * * Returns an (opaque) virDomainSnapshotPtr on success, NULL on failure. */ virDomainSnapshotPtr diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index a8cd9f8..ae63a02 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8608,7 +8608,8 @@ static int qemuDomainSnapshotCreateActive(virConnectPtr conn, struct qemud_driver *driver, virDomainObjPtr *vmptr, - virDomainSnapshotObjPtr snap) + virDomainSnapshotObjPtr snap, + unsigned int flags) { virDomainObjPtr vm = *vmptr; qemuDomainObjPrivatePtr priv = vm->privateData; @@ -8638,6 +8639,24 @@ qemuDomainSnapshotCreateActive(virConnectPtr conn, qemuDomainObjEnterMonitorWithDriver(driver, vm); ret = qemuMonitorCreateSnapshot(priv->mon, snap->def->name); qemuDomainObjExitMonitorWithDriver(driver, vm); + if (ret < 0) + goto cleanup; + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { + virDomainEventPtr event; + + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + qemuProcessStop(driver, vm, 0, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT); + virDomainAuditStop(vm, "from-snapshot"); + /* We already filtered the _HALT flag for persistent domains + * only, so this end job never drops the last reference. */ + ignore_value(qemuDomainObjEndJob(driver, vm)); + resume = false; + vm = NULL; + if (event) + qemuDomainEventQueue(driver, event); + } cleanup: if (resume && virDomainObjIsActive(vm) && @@ -8649,7 +8668,7 @@ cleanup: _("resuming after snapshot failed")); } - if (qemuDomainObjEndJob(driver, vm) == 0) { + if (vm && qemuDomainObjEndJob(driver, vm) == 0) { /* Only possible if a transient vm quit while our locks were down, * in which case we don't want to save snapshot metadata. */ *vmptr = NULL; @@ -8672,7 +8691,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, char uuidstr[VIR_UUID_STRING_BUFLEN]; virDomainSnapshotDefPtr def = NULL; - virCheckFlags(0, NULL); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_HALT, NULL); qemuDriverLock(driver); virUUIDFormat(domain->uuid, uuidstr); @@ -8683,6 +8702,12 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, goto cleanup; } + if (!vm->persistent && (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("cannot halt after transient domain snapshot")); + goto cleanup; + } + if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1, NULL, 0, 0))) goto cleanup; @@ -8730,7 +8755,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, goto cleanup; } else { if (qemuDomainSnapshotCreateActive(domain->conn, driver, - &vm, snap) < 0) + &vm, snap, flags) < 0) goto cleanup; } -- 1.7.4.4

The next patch will make snapshot creation more complex, so it's better to avoid repetition of the complexity. * tools/virsh.c (vshSnapshotCreate): New helper function. (cmdSnapshotCreate, cmdSnapshotCreateAs): Use it. --- tools/virsh.c | 119 ++++++++++++++++++++++++--------------------------------- 1 files changed, 50 insertions(+), 69 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index c01edc1..861300d 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12098,6 +12098,54 @@ cmdQuit(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED) return true; } +/* Helper for snapshot-create and snapshot-create-as */ +static bool +vshSnapshotCreate(vshControl *ctl, virDomainPtr dom, const char *buffer, + unsigned int flags, const char *from) +{ + bool ret = false; + virDomainSnapshotPtr snapshot; + char *doc = NULL; + xmlDocPtr xml = NULL; + xmlXPathContextPtr ctxt = NULL; + char *name = NULL; + + snapshot = virDomainSnapshotCreateXML(dom, buffer, flags); + if (snapshot == NULL) + goto cleanup; + + doc = virDomainSnapshotGetXMLDesc(snapshot, 0); + if (!doc) + goto cleanup; + + xml = virXMLParseStringCtxt(doc, "domainsnapshot.xml", &ctxt); + if (!xml) + goto cleanup; + + name = virXPathString("string(/domainsnapshot/name)", ctxt); + if (!name) { + vshError(ctl, "%s", + _("Could not find 'name' element in domain snapshot XML")); + goto cleanup; + } + + if (from) + vshPrint(ctl, _("Domain snapshot %s created from '%s'"), name, from); + else + vshPrint(ctl, _("Domain snapshot %s created"), name); + + ret = true; + +cleanup: + VIR_FREE(name); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + if (snapshot) + virDomainSnapshotFree(snapshot); + VIR_FREE(doc); + return ret; +} + /* * "snapshot-create" command */ @@ -12120,11 +12168,6 @@ cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd) bool ret = false; const char *from = NULL; char *buffer = NULL; - virDomainSnapshotPtr snapshot = NULL; - xmlDocPtr xml = NULL; - xmlXPathContextPtr ctxt = NULL; - char *doc = NULL; - char *name = NULL; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12150,39 +12193,9 @@ cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd) goto cleanup; } - snapshot = virDomainSnapshotCreateXML(dom, buffer, 0); - if (snapshot == NULL) - goto cleanup; - - doc = virDomainSnapshotGetXMLDesc(snapshot, 0); - if (!doc) - goto cleanup; - - xml = virXMLParseStringCtxt(doc, "domainsnapshot.xml", &ctxt); - if (!xml) - goto cleanup; - - name = virXPathString("string(/domainsnapshot/name)", ctxt); - if (!name) { - vshError(ctl, "%s", - _("Could not find 'name' element in domain snapshot XML")); - goto cleanup; - } - - vshPrint(ctl, _("Domain snapshot %s created"), name); - if (from) - vshPrint(ctl, _(" from '%s'"), from); - vshPrint(ctl, "\n"); - - ret = true; + ret = vshSnapshotCreate(ctl, dom, buffer, 0, from); cleanup: - VIR_FREE(name); - xmlXPathFreeContext(ctxt); - xmlFreeDoc(xml); - if (snapshot) - virDomainSnapshotFree(snapshot); - VIR_FREE(doc); VIR_FREE(buffer); if (dom) virDomainFree(dom); @@ -12213,13 +12226,8 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) virDomainPtr dom = NULL; bool ret = false; char *buffer = NULL; - virDomainSnapshotPtr snapshot = NULL; - xmlDocPtr xml = NULL; - xmlXPathContextPtr ctxt = NULL; - char *doc = NULL; const char *name = NULL; const char *desc = NULL; - char *parsed_name = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; if (!vshConnectionUsability(ctl, ctl->conn)) @@ -12254,36 +12262,9 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) goto cleanup; } - snapshot = virDomainSnapshotCreateXML(dom, buffer, 0); - if (snapshot == NULL) - goto cleanup; - - doc = virDomainSnapshotGetXMLDesc(snapshot, 0); - if (!doc) - goto cleanup; - - xml = virXMLParseStringCtxt(doc, "domainsnapshot.xml", &ctxt); - if (!xml) - goto cleanup; - - parsed_name = virXPathString("string(/domainsnapshot/name)", ctxt); - if (!parsed_name) { - vshError(ctl, "%s", - _("Could not find 'name' element in domain snapshot XML")); - goto cleanup; - } - - vshPrint(ctl, _("Domain snapshot %s created\n"), name ? name : parsed_name); - - ret = true; + ret = vshSnapshotCreate(ctl, dom, buffer, 0, NULL); cleanup: - VIR_FREE(parsed_name); - xmlXPathFreeContext(ctxt); - xmlFreeDoc(xml); - if (snapshot) - virDomainSnapshotFree(snapshot); - VIR_FREE(doc); VIR_FREE(buffer); if (dom) virDomainFree(dom); -- 1.7.4.4

Easy enough to emulate even with older servers. * tools/virsh.c (cmdSnapshotCreate, cmdSnapshotCreateAs): Add --halt flag. (vshSnapshotCreate): Emulate halt when flag is unsupported. * tools/virsh.pod (snapshot-create, snapshot-create-as): Document it. --- tools/virsh.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- tools/virsh.pod | 9 +++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 861300d..d4cbbf7 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12105,15 +12105,45 @@ vshSnapshotCreate(vshControl *ctl, virDomainPtr dom, const char *buffer, { bool ret = false; virDomainSnapshotPtr snapshot; + bool halt = false; char *doc = NULL; xmlDocPtr xml = NULL; xmlXPathContextPtr ctxt = NULL; char *name = NULL; snapshot = virDomainSnapshotCreateXML(dom, buffer, flags); + + /* Emulate --halt on older servers. */ + if (!snapshot && last_error->code == VIR_ERR_INVALID_ARG && + (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) { + int persistent; + + virFreeError(last_error); + last_error = NULL; + persistent = virDomainIsPersistent(dom); + if (persistent < 0) { + virshReportError(ctl); + goto cleanup; + } + if (!persistent) { + vshError(ctl, "%s", + _("cannot halt after snapshot of transient domain")); + goto cleanup; + } + if (virDomainIsActive(dom) == 1) + halt = true; + flags &= ~VIR_DOMAIN_SNAPSHOT_CREATE_HALT; + snapshot = virDomainSnapshotCreateXML(dom, buffer, flags); + } + if (snapshot == NULL) goto cleanup; + if (halt && virDomainDestroy(dom) < 0) { + virshReportError(ctl); + goto cleanup; + } + doc = virDomainSnapshotGetXMLDesc(snapshot, 0); if (!doc) goto cleanup; @@ -12158,6 +12188,7 @@ static const vshCmdInfo info_snapshot_create[] = { static const vshCmdOptDef opts_snapshot_create[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"xmlfile", VSH_OT_DATA, 0, N_("domain snapshot XML")}, + {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")}, {NULL, 0, 0, NULL} }; @@ -12168,6 +12199,10 @@ cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd) bool ret = false; const char *from = NULL; char *buffer = NULL; + int flags = 0; + + if (vshCommandOptBool(cmd, "halt")) + flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12193,7 +12228,7 @@ cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd) goto cleanup; } - ret = vshSnapshotCreate(ctl, dom, buffer, 0, from); + ret = vshSnapshotCreate(ctl, dom, buffer, flags, from); cleanup: VIR_FREE(buffer); @@ -12217,6 +12252,7 @@ static const vshCmdOptDef opts_snapshot_create_as[] = { {"name", VSH_OT_DATA, 0, N_("name of snapshot")}, {"description", VSH_OT_DATA, 0, N_("description of snapshot")}, {"print-xml", VSH_OT_BOOL, 0, N_("print XML document rather than create")}, + {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")}, {NULL, 0, 0, NULL} }; @@ -12229,6 +12265,10 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) const char *name = NULL; const char *desc = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; + int flags = 0; + + if (vshCommandOptBool(cmd, "halt")) + flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12257,12 +12297,17 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) } if (vshCommandOptBool(cmd, "print-xml")) { + if (vshCommandOptBool(cmd, "halt")) { + vshError(ctl, "%s", + _("--print-xml and --halt are mutually exclusive")); + goto cleanup; + } vshPrint(ctl, "%s\n", buffer); ret = true; goto cleanup; } - ret = vshSnapshotCreate(ctl, dom, buffer, 0, NULL); + ret = vshSnapshotCreate(ctl, dom, buffer, flags, NULL); cleanup: VIR_FREE(buffer); diff --git a/tools/virsh.pod b/tools/virsh.pod index 1cbe5c3..c77b1e4 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1591,7 +1591,7 @@ used to represent properties of snapshots. =over 4 -=item B<snapshot-create> I<domain> [I<xmlfile>] +=item B<snapshot-create> I<domain> [I<xmlfile>] [I<--halt>] Create a snapshot for domain I<domain> with the properties specified in I<xmlfile>. The only properties settable for a domain snapshot are the @@ -1599,13 +1599,18 @@ I<xmlfile>. The only properties settable for a domain snapshot are the automatically filled in by libvirt. If I<xmlfile> is completely omitted, then libvirt will choose a value for all fields. -=item B<snapshot-create-as> I<domain> [I<--print-xml>] +If I<--halt> is specified, the domain will be left in an inactive state +after the snapshot is created. + +=item B<snapshot-create-as> I<domain> [{I<--print-xml> | I<--halt>}] [I<name>] [I<description>] Create a snapshot for domain I<domain> with the given <name> and <description>; if either value is omitted, libvirt will choose a value. If I<--print-xml> is specified, then XML appropriate for I<snapshot-create> is output, rather than actually creating a snapshot. +Otherwise, if I<--halt> is specified, the domain will be left in an +inactive state after the snapshot is created. =item B<snapshot-current> I<domain> [I<--name>] [I<--security-info>] -- 1.7.4.4

No one uses this yet, but it will be important once virDomainSnapshotCreateXML learns a VIR_DOMAIN_SNAPSHOT_DISK_ONLY flag, and the xml allows passing in the new file names. * src/qemu/qemu_monitor.h (qemuMonitorDiskSnapshot): New prototype. * src/qemu/qemu_monitor_text.h (qemuMonitorTextDiskSnapshot): Likewise. * src/qemu/qemu_monitor_json.h (qemuMonitorJSONDiskSnapshot): Likewise. * src/qemu/qemu_monitor.c (qemuMonitorDiskSnapshot): New function. * src/qemu/qemu_monitor_json.c (qemuMonitorJSONDiskSnapshot): Likewise. --- src/qemu/qemu_monitor.c | 24 ++++++++++++++++++++++++ src/qemu/qemu_monitor.h | 4 ++++ src/qemu/qemu_monitor_json.c | 33 +++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_json.h | 4 ++++ src/qemu/qemu_monitor_text.c | 40 ++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_monitor_text.h | 4 ++++ 6 files changed, 109 insertions(+), 0 deletions(-) diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index db6107c..efc49c4 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -2391,6 +2391,30 @@ int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name) return ret; } +/* Use the snapshot_blkdev command to convert the existing file for + * device into a read-only backing file of a new qcow2 image located + * at file. */ +int +qemuMonitorDiskSnapshot(qemuMonitorPtr mon, const char *device, + const char *file) +{ + int ret; + + VIR_DEBUG("mon=%p, device=%s, file=%s", mon, device, file); + + if (!mon) { + qemuReportError(VIR_ERR_INVALID_ARG, "%s", + _("monitor must not be NULL")); + return -1; + } + + if (mon->json) + ret = qemuMonitorJSONDiskSnapshot(mon, device, file); + else + ret = qemuMonitorTextDiskSnapshot(mon, device, file); + return ret; +} + int qemuMonitorArbitraryCommand(qemuMonitorPtr mon, const char *cmd, char **reply, diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h index f241c9e..b988a72 100644 --- a/src/qemu/qemu_monitor.h +++ b/src/qemu/qemu_monitor.h @@ -447,6 +447,10 @@ int qemuMonitorCreateSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorLoadSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorDeleteSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorDiskSnapshot(qemuMonitorPtr mon, + const char *device, + const char *file); + int qemuMonitorArbitraryCommand(qemuMonitorPtr mon, const char *cmd, char **reply, diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index 715b26e..f8a1696 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -2703,6 +2703,39 @@ cleanup: return ret; } +int +qemuMonitorJSONDiskSnapshot(qemuMonitorPtr mon, const char *device, + const char *file) +{ + int ret; + virJSONValuePtr cmd; + virJSONValuePtr reply = NULL; + + cmd = qemuMonitorJSONMakeCommand("snapshot-blkdev-sync", + "s:device", device, + "s:snapshot-file", file, + NULL); + if (!cmd) + return -1; + + if ((ret = qemuMonitorJSONCommand(mon, cmd, &reply)) < 0) + goto cleanup; + + if (qemuMonitorJSONHasError(reply, "CommandNotFound") && + qemuMonitorCheckHMP(mon, "snapshot_blkdev")) { + VIR_DEBUG("snapshot-blkdev-sync command not found, trying HMP"); + ret = qemuMonitorTextDiskSnapshot(mon, device, file); + goto cleanup; + } + + ret = qemuMonitorJSONCheckError(cmd, reply); + +cleanup: + virJSONValueFree(cmd); + virJSONValueFree(reply); + return ret; +} + int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon, const char *cmd_str, char **reply_str, diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h index 9512793..a538e9f 100644 --- a/src/qemu/qemu_monitor_json.h +++ b/src/qemu/qemu_monitor_json.h @@ -210,6 +210,10 @@ int qemuMonitorJSONCreateSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorJSONLoadSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorJSONDeleteSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorJSONDiskSnapshot(qemuMonitorPtr mon, + const char *device, + const char *file); + int qemuMonitorJSONArbitraryCommand(qemuMonitorPtr mon, const char *cmd_str, char **reply_str, diff --git a/src/qemu/qemu_monitor_text.c b/src/qemu/qemu_monitor_text.c index 54541ce..3742cdf 100644 --- a/src/qemu/qemu_monitor_text.c +++ b/src/qemu/qemu_monitor_text.c @@ -2791,6 +2791,46 @@ cleanup: return ret; } +int +qemuMonitorTextDiskSnapshot(qemuMonitorPtr mon, const char *device, + const char *file) +{ + char *cmd = NULL; + char *reply = NULL; + int ret = -1; + char *safename; + + if (!(safename = qemuMonitorEscapeArg(file)) || + virAsprintf(&cmd, "snapshot_blkdev %s \"%s\"", device, safename) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (qemuMonitorHMPCommand(mon, cmd, &reply)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("failed to take snapshot using command '%s'"), cmd); + goto cleanup; + } + + if (strstr(reply, "error while creating qcow2") != NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + _("Failed to take snapshot: %s"), reply); + goto cleanup; + } + + /* XXX Should we scrape 'info block' output for + * 'device:... file=name backing_file=oldname' to make sure the + * command succeeded? */ + + ret = 0; + +cleanup: + VIR_FREE(safename); + VIR_FREE(cmd); + VIR_FREE(reply); + return ret; +} + int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd, char **reply) { diff --git a/src/qemu/qemu_monitor_text.h b/src/qemu/qemu_monitor_text.h index b250738..55f78b4 100644 --- a/src/qemu/qemu_monitor_text.h +++ b/src/qemu/qemu_monitor_text.h @@ -206,6 +206,10 @@ int qemuMonitorTextCreateSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorTextLoadSnapshot(qemuMonitorPtr mon, const char *name); int qemuMonitorTextDeleteSnapshot(qemuMonitorPtr mon, const char *name); +int qemuMonitorTextDiskSnapshot(qemuMonitorPtr mon, + const char *device, + const char *file); + int qemuMonitorTextArbitraryCommand(qemuMonitorPtr mon, const char *cmd, char **reply); -- 1.7.4.4

In order to distinguish disk snapshots from system checkpoints, a new state value that is only valid for snapshots is helpful. * include/libvirt/libvirt.h.in (VIR_DOMAIN_LAST): New placeholder. * src/conf/domain_conf.h (virDomainSnapshotState): New enum mapping. (VIR_DOMAIN_DISK_SNAPSHOT): New internal enum value. * src/conf/domain_conf.c (virDomainState): Use placeholder. (virDomainSnapshotState): Extend mapping by one for use in snapshot. (virDomainSnapshotDefParseString, virDomainSnapshotDefFormat): Handle new state. (virDomainObjSetState, virDomainStateReasonToString) (virDomainStateReasonFromString): Avoid compiler warnings. * tools/virsh.c (vshDomainState, vshDomainStateReasonToString): Likewise. * src/libvirt_private.syms (domain_conf.h): Export new functions. * docs/schemas/domainsnapshot.rng: Tighten state definition. * docs/formatsnapshot.html.in: Document it. * tests/domainsnapshotxml2xmlout/disk_snapshot.xml: New test. --- docs/formatsnapshot.html.in | 87 +++++++++++++++++++--- docs/schemas/domainsnapshot.rng | 15 ++++- include/libvirt/libvirt.h.in | 14 +++- src/conf/domain_conf.c | 26 +++++-- src/conf/domain_conf.h | 8 ++- src/libvirt_private.syms | 2 + tests/domainsnapshotxml2xmlout/disk_snapshot.xml | 35 +++++++++ tools/virsh.c | 6 ++ 8 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 tests/domainsnapshotxml2xmlout/disk_snapshot.xml diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index edf20e8..4158a63 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -7,6 +7,61 @@ <h2><a name="SnapshotAttributes">Snapshot XML</a></h2> <p> + There are several types of snapshots: + </p> + <dl> + <dt>disk snapshot</dt> + <dd>Contents of disks (whether a subset or all disks associated + with the domain) are saved at a given point of time, and can + be restored back to that state. On a running guest, a disk + snapshot is likely to be only crash-consistent rather than + clean (that is, it represents the state of the disk on a + sudden power outage, and may need fsck or journal replays to + be made consistent); on an inactive guest, a disk snapshot is + clean if the disks were clean when the guest was last shut + down.</dd> + <dt>VM state</dt> + <dd>Tracks only the state of RAM and all other resources in use + by the VM. If the disks are unmodified between the time a VM + state snapshot is taken and restored, then the guest will + resume in a consistent state; but if the disks are modified + externally in the meantime, this is likely to lead to data + corruption.</dd> + <dt>system checkpoint</dt> + <dd>A combination of disk snapshots for all disks as well as VM + state, which can be used to resume the guest from where it + left off with symptoms similar to hibernation (that is, TCP + connections in the guest may have timed out, but no files or + processes are lost).</dd> + </dl> + + <p> + Libvirt can manage all three types of snapshots. For now, VM + state snapshots are created only by + the <code>virDomainSave()</code>, <code>virDomainSaveFlags</code>, + and <code>virDomainManagedSave()</code> functions, and restored + via the <code>virDomainRestore()</code>, + <code>virDomainRestoreFlags()</code>, <code>virDomainCreate()</code>, + and <code>virDomainCreateWithFlags()</code> functions (as well + as via domain autostart). With managed snapshots, libvirt + tracks all information internally; with save images, the user + tracks the snapshot file, but libvirt provides functions such + as <code>virDomainSaveImageGetXMLDesc()</code> to work with + those files. + </p> + <p>System checkpoints are created + by <code>virDomainSnapshotCreateXML()</code> with no flags, and + disk snapshots are created by the same function with + the <code>VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY</code> flag; in + both cases, they are restored by + the <code>virDomainRevertToSnapshot()</code> function. For + these types of snapshots, libvirt tracks each snapshot as a + separate <code>virDomainSnapshotPtr</code> object, and maintains + a tree relationship of which snapshots descended from an earlier + point in time. + </p> + + <p> Attributes of libvirt snapshots are stored as child elements of the <code>domainsnapshot</code> element. At snapshot creation time, only the <code>name</code> and <code>description</code> @@ -21,9 +76,10 @@ <dl> <dt><code>name</code></dt> <dd>The name for this snapshot. If the name is specified when - initially creating the snapshot, then the snapshot will have - that particular name. If the name is omitted when initially - creating the snapshot, then libvirt will make up a name for the snapshot. + initially creating the snapshot, then the snapshot will have + that particular name. If the name is omitted when initially + creating the snapshot, then libvirt will make up a name for the + snapshot, based on the time when it was created. </dd> <dt><code>description</code></dt> <dd>A human-readable description of the snapshot. If the @@ -32,18 +88,25 @@ </dd> <dt><code>creationTime</code></dt> <dd>The time this snapshot was created. The time is specified - in seconds since the Epoch, UTC (i.e. Unix time). Readonly. + in seconds since the Epoch, UTC (i.e. Unix time). Readonly. </dd> <dt><code>state</code></dt> - <dd>The state of the domain at the time this snapshot was - taken. When the domain is reverted to this snapshot, the domain's state - will be set to whatever is in this field. Readonly. + <dd>The state of the domain at the time this snapshot was taken. + If the snapshot was created as a system checkpoint, then this + is the state of the domain at that time; when the domain is + reverted to this snapshot, the domain's state will default to + whatever is in this field unless additional flags are passed + to <code>virDomainRevertToSnapshot()</code>. Additionally, + this field can be the value "disk-snapshot" + (<span class="since">since 0.9.5</span>) when it represents + only a disk snapshot (no VM state), and reverting to this + snapshot will default to an inactive guest. Readonly. </dd> <dt><code>parent</code></dt> - <dd>The parent of this snapshot. This element contains exactly - one child element, name. This specifies the name of the parent - snapshot of this snapshot, and is used to represent trees of - snapshots. Readonly. + <dd>The parent of this snapshot. If present, this element + contains exactly one child element, name. This specifies the + name of the parent snapshot of this snapshot, and is used to + represent trees of snapshots. Readonly. </dd> <dt><code>domain</code></dt> <dd>The domain that this snapshot was taken against. Older @@ -53,7 +116,7 @@ created in, and requires the use of the <code>VIR_DOMAIN_SNAPSHOT_REVERT_FORCE</code> flag in <code>virDomainRevertToSnapshot()</code>. Newer versions - of libvirt store the entire + of libvirt (<span class="since">since 0.9.5</span>) store the entire inactive <a href="formatdomain.html">domain configuration</a> at the time of the snapshot. Readonly. </dd> diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index a16d731..130dad9 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -22,7 +22,7 @@ </optional> <optional> <element name='state'> - <text/> + <ref name='state'/> </element> </optional> <optional> @@ -59,4 +59,17 @@ </element> </define> + <define name='state'> + <choice> + <value>nostate</value> + <value>running</value> + <value>blocked</value> + <value>paused</value> + <value>shutdown</value> + <value>shutoff</value> + <value>crashed</value> + <value>disk-snapshot</value> + </choice> + </define> + </grammar> diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index e07dc20..c62577d 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -86,7 +86,14 @@ typedef enum { VIR_DOMAIN_PAUSED = 3, /* the domain is paused by user */ VIR_DOMAIN_SHUTDOWN= 4, /* the domain is being shut down */ VIR_DOMAIN_SHUTOFF = 5, /* the domain is shut off */ - VIR_DOMAIN_CRASHED = 6 /* the domain is crashed */ + VIR_DOMAIN_CRASHED = 6, /* the domain is crashed */ + + /* + * NB: this enum value will increase over time as new events are + * added to the libvirt API. It reflects the last state supported + * by this version of the libvirt API. + */ + VIR_DOMAIN_LAST } virDomainState; typedef enum { @@ -1893,6 +1900,11 @@ typedef enum { VIR_KEYCODE_SET_USB = 7, VIR_KEYCODE_SET_WIN32 = 8, + /* + * NB: this enum value will increase over time as new events are + * added to the libvirt API. It reflects the last keycode set supported + * by this version of the libvirt API. + */ VIR_KEYCODE_SET_LAST, } virKeycodeSet; diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index df0e921..0713a25 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -420,7 +420,7 @@ VIR_ENUM_IMPL(virDomainHostdevSubsys, VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST, "usb", "pci") -VIR_ENUM_IMPL(virDomainState, VIR_DOMAIN_CRASHED+1, +VIR_ENUM_IMPL(virDomainState, VIR_DOMAIN_LAST, "nostate", "running", "blocked", @@ -429,6 +429,17 @@ VIR_ENUM_IMPL(virDomainState, VIR_DOMAIN_CRASHED+1, "shutoff", "crashed") +/* virDomainSnapshotState is really virDomainState plus one extra state */ +VIR_ENUM_IMPL(virDomainSnapshotState, VIR_DOMAIN_DISK_SNAPSHOT+1, + "nostate", + "running", + "blocked", + "paused", + "shutdown", + "shutoff", + "crashed", + "disk-snapshot") + #define VIR_DOMAIN_NOSTATE_LAST (VIR_DOMAIN_NOSTATE_UNKNOWN + 1) VIR_ENUM_IMPL(virDomainNostateReason, VIR_DOMAIN_NOSTATE_LAST, "unknown") @@ -11052,7 +11063,7 @@ virDomainSnapshotDefParseString(const char *xmlStr, _("missing state from existing snapshot")); goto cleanup; } - def->state = virDomainStateTypeFromString(state); + def->state = virDomainSnapshotStateTypeFromString(state); if (def->state < 0) { virDomainReportError(VIR_ERR_INTERNAL_ERROR, _("Invalid state '%s' in domain snapshot XML"), @@ -11120,7 +11131,7 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, virBufferEscapeString(&buf, " <description>%s</description>\n", def->description); virBufferAsprintf(&buf, " <state>%s</state>\n", - virDomainStateTypeToString(def->state)); + virDomainSnapshotStateTypeToString(def->state)); if (def->parent) { virBufferAddLit(&buf, " <parent>\n"); virBufferEscapeString(&buf, " <name>%s</name>\n", def->parent); @@ -11712,6 +11723,7 @@ virDomainObjSetState(virDomainObjPtr dom, virDomainState state, int reason) case VIR_DOMAIN_SHUTDOWN: last = VIR_DOMAIN_SHUTDOWN_LAST; break; case VIR_DOMAIN_SHUTOFF: last = VIR_DOMAIN_SHUTOFF_LAST; break; case VIR_DOMAIN_CRASHED: last = VIR_DOMAIN_CRASHED_LAST; break; + default: last = -1; } if (last < 0) { @@ -11745,9 +11757,9 @@ virDomainStateReasonToString(virDomainState state, int reason) return virDomainShutoffReasonTypeToString(reason); case VIR_DOMAIN_CRASHED: return virDomainCrashedReasonTypeToString(reason); + default: + return NULL; } - - return NULL; } @@ -11769,9 +11781,9 @@ virDomainStateReasonFromString(virDomainState state, const char *reason) return virDomainShutoffReasonTypeFromString(reason); case VIR_DOMAIN_CRASHED: return virDomainCrashedReasonTypeFromString(reason); + default: + return -1; } - - return -1; } diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 7dbf353..ea8194a 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -226,6 +226,11 @@ enum virDomainDiskSnapshot { VIR_DOMAIN_DISK_SNAPSHOT_LAST }; +enum virDomainSnapshotState { + /* Inherit the VIR_DOMAIN_* states from virDomainState. */ + VIR_DOMAIN_DISK_SNAPSHOT = VIR_DOMAIN_LAST, +}; + /* Stores the virtual disk configuration */ typedef struct _virDomainDiskDef virDomainDiskDef; typedef virDomainDiskDef *virDomainDiskDefPtr; @@ -1312,7 +1317,7 @@ struct _virDomainSnapshotDef { char *description; char *parent; long long creationTime; /* in seconds */ - int state; + int state; /* enum virDomainSnapshotState */ virDomainDefPtr dom; /* Internal use. */ @@ -1739,6 +1744,7 @@ VIR_ENUM_DECL(virDomainGraphicsSpicePlaybackCompression) VIR_ENUM_DECL(virDomainGraphicsSpiceStreamingMode) VIR_ENUM_DECL(virDomainGraphicsSpiceClipboardCopypaste) VIR_ENUM_DECL(virDomainNumatuneMemMode) +VIR_ENUM_DECL(virDomainSnapshotState) /* from libvirt.h */ VIR_ENUM_DECL(virDomainState) VIR_ENUM_DECL(virDomainNostateReason) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9530567..034443c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -392,6 +392,8 @@ virDomainSnapshotHasChildren; virDomainSnapshotObjListGetNames; virDomainSnapshotObjListNum; virDomainSnapshotObjListRemove; +virDomainSnapshotStateTypeFromString; +virDomainSnapshotStateTypeToString; virDomainSoundDefFree; virDomainSoundModelTypeFromString; virDomainSoundModelTypeToString; diff --git a/tests/domainsnapshotxml2xmlout/disk_snapshot.xml b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml new file mode 100644 index 0000000..391bb57 --- /dev/null +++ b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml @@ -0,0 +1,35 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <parent> + <name>earlier_snap</name> + </parent> + <state>disk-snapshot</state> + <creationTime>1272917631</creationTime> +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory>219100</memory> + <currentMemory>219100</currentMemory> + <vcpu cpuset='1-4,8-20,525'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' unit='0'/> + </disk> + <controller type='ide' index='0'/> + <memballoon model='virtio'/> + </devices> +</domain> + <active>1</active> +</domainsnapshot> diff --git a/tools/virsh.c b/tools/virsh.c index d4cbbf7..d4581f7 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -14537,6 +14537,8 @@ vshDomainState(vshControl *ctl, virDomainPtr dom, int *reason) static const char * vshDomainStateToString(int state) { + /* Can't use virDomainStateTypeToString, because we want to mark + * strings for translation. */ switch ((virDomainState) state) { case VIR_DOMAIN_RUNNING: return N_("running"); @@ -14551,6 +14553,7 @@ vshDomainStateToString(int state) case VIR_DOMAIN_CRASHED: return N_("crashed"); case VIR_DOMAIN_NOSTATE: + default: ;/*FALLTHROUGH*/ } return N_("no state"); /* = dom0 state */ @@ -14652,6 +14655,9 @@ vshDomainStateReasonToString(int state, int reason) ; } break; + + default: + ; } return N_("unknown"); -- 1.7.4.4

Adds an optional element to <domainsnapshot>, which will be used to give user control over external snapshot filenames on input, and specify generated filenames on output. <domainsnapshot> ... <disks> <disk name='vda' snapshot='no'/> <disk name='vdb' snapshot='internal'/> <disk name='vdc' snapshot='external'> <driver type='qcow2'/> <source file='/path/to/new'/> </disk> </disks> <domain> ... <devices> <disk ...> <driver name='qemu' type='raw'/> <target dev='vdc'/> <source file='/path/to/old'/> </disk> </devices> </domain> </domainsnapshot> * src/conf/domain_conf.h (_virDomainSnapshotDiskDef): New type. (_virDomainSnapshotDef): Add new elements. (virDomainSnapshotAlignDisks): New prototype. * src/conf/domain_conf.c (virDomainSnapshotDiskDefClear) (virDomainSnapshotDiskDefParseXML, disksorter) (virDomainSnapshotAlignDisks): New functions. (virDomainSnapshotDefParseString): Parse new fields. (virDomainSnapshotDefFree): Clean them up. (virDomainSnapshotDefFormat): Output them. * src/libvirt_private.syms (domain_conf.h): Export new function. * docs/schemas/domainsnapshot.rng (domainsnapshot, disksnapshot): Add more xml. * docs/formatsnapshot.html.in: Document it. * tests/domainsnapshotxml2xmlin/disk_snapshot.xml: New test. * tests/domainsnapshotxml2xmlout/disk_snapshot.xml: Update. --- docs/formatsnapshot.html.in | 126 +++++++++- docs/schemas/domainsnapshot.rng | 52 ++++ src/conf/domain_conf.c | 274 ++++++++++++++++++++++ src/conf/domain_conf.h | 22 ++- src/libvirt_private.syms | 1 + tests/domainsnapshotxml2xmlin/disk_snapshot.xml | 16 ++ tests/domainsnapshotxml2xmlout/disk_snapshot.xml | 42 ++++ 7 files changed, 522 insertions(+), 11 deletions(-) create mode 100644 tests/domainsnapshotxml2xmlin/disk_snapshot.xml diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index 4158a63..953272c 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -64,10 +64,10 @@ <p> Attributes of libvirt snapshots are stored as child elements of the <code>domainsnapshot</code> element. At snapshot creation - time, only the <code>name</code> and <code>description</code> - elements are settable; the rest of the fields are informational - (and readonly) and will be filled in by libvirt when the - snapshot is created. + time, only the <code>name</code>, <code>description</code>, + and <code>disks</code> elements are settable; the rest of the + fields are informational (and readonly) and will be filled in by + libvirt when the snapshot is created. </p> <p> The top-level <code>domainsnapshot</code> element may contain @@ -86,6 +86,58 @@ description is omitted when initially creating the snapshot, then this field will be empty. </dd> + <dt><code>disks</code></dt> + <dd>On input, this is an optional listing of specific + instructions for disk snapshots; it is needed when making a + snapshot of only a subset of the disks associated with a + domain, or when overriding the domain defaults for how to + snapshot each disk, or for providing specific control over + what file name is created in an external snapshot. On output, + this is fully populated to show the state of each disk in the + snapshot, including any properties that were generated by the + hypervisor defaults. For system checkpoints, this field is + ignored on input and omitted on output (a system checkpoint + implies that all disks participate in the snapshot process, + and since the current implementation only does internal system + checkpoints, there are no extra details to add); a future + release may allow the use of <code>disks</code> with a system + checkpoint. This element has a list of <code>disk</code> + sub-elements, describing anywhere from zero to all of the + disks associated with the domain. <span class="since">Since + 0.9.5</span> + <dl> + <dt><code>disk</code></dt> + <dd>This sub-element describes the snapshot properties of a + specific disk. The attribute <code>name</code> is + mandatory, and must match the <code><target + dev='name'/></code> of one of + the <a href="formatdomain.html#elementsDisks">disk + devices</a> specified for the domain at the time of the + snapshot. The attribute <code>snapshot</code> is + optional, and has the same values of the disk device + element for a domain + (<code>no</code>, <code>internal</code>, + or <code>external</code>). Some hypervisors like ESX + require that if specified, the snapshot mode must not + override any snapshot mode attached to the corresponding + domain disk, while others like qemu allow this field to + override the domain default. If the snapshot mode is + external (whether specified or inherited), then there is + an optional sub-element <code>source</code>, with an + attribute <code>file</code> giving the name, and an + optional sub-element <code>driver</code>, with an + attribute <code>type</code> giving the driver type (such + as qcow2), of the new file created by the external + snapshot of the new file. If <code>source</code> is not + given, a file name is generated that consists of the + existing file name with anything after the trailing dot + replaced by the snapshot name. Remember that with external + snapshots, the original file name becomes the read-only + snapshot, and the new file name contains the read-write + delta of all disk changes since the snapshot. + </dd> + </dl> + </dd> <dt><code>creationTime</code></dt> <dd>The time this snapshot was created. The time is specified in seconds since the Epoch, UTC (i.e. Unix time). Readonly. @@ -124,14 +176,21 @@ <h2><a name="example">Examples</a></h2> - <p>Using this XML on creation:</p> + <p>Using this XML to create a disk snapshot of just vda on a qemu + domain with two disks:</p> <pre> - <domainsnapshot> - <description>Snapshot of OS install and updates</description> - </domainsnapshot></pre> +<domainsnapshot> + <description>Snapshot of OS install and updates</description> + <disks> + <disk name='vda'> + <source file='/path/to/new'/> + </disk> + <disk name='vdb' snapshot='no'/> + </disks> +</domainsnapshot></pre> - <p>Will result in XML similar to this from - virDomainSnapshotGetXMLDesc:</p> + <p>will result in XML similar to this from + <code>virDomainSnapshotGetXMLDesc()</code>:</p> <pre> <domainsnapshot> <name>1270477159</name> @@ -141,14 +200,61 @@ <parent> <name>bare-os-install</name> </parent> + <disks> + <disk name='vda' snapshot='external'> + <driver type='qcow2'/> + <b><source file='/path/to/new'/></b> + </disk> + <disk name='vdb' snapshot='no'/> + </disks> <domain> <name>fedora</name> <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid> <memory>1048576</memory> ... + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='raw'/> + <b><source file='/path/to/old'/></b> + <target dev='vda' bus='virtio'/> + </disk> + <disk type='file' device='disk' snapshot='external'> + <driver name='qemu' type='raw'/> + <source file='/path/to/old2'/> + <target dev='vdb' bus='virtio'/> + </disk> + ... </devices> </domain> </domainsnapshot></pre> + <p>With that snapshot created, <code>/path/to/old</code> is the + read-only backing file to the new active + file <code>/path/to/new</code>. The <code><domain></code> + element within the snapshot xml records the state of the domain + just before the snapshot; a call + to <code>virDomainGetXMLDesc()</code> will show that the domain + has been changed to reflect the snapshot: + </p> + <pre> +<domain> + <name>fedora</name> + <uuid>93a5c045-6457-2c09-e56c-927cdf34e178</uuid> + <memory>1048576</memory> + ... + <devices> + <disk type='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <b><source file='/path/to/new'/></b> + <target dev='vda' bus='virtio'/> + </disk> + <disk type='file' device='disk' snapshot='external'> + <driver name='qemu' type='raw'/> + <source file='/path/to/old2'/> + <target dev='vdb' bus='virtio'/> + </disk> + ... + </devices> +</domain></pre> </body> </html> diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 130dad9..671fbe0 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -31,6 +31,13 @@ </element> </optional> <optional> + <element name='disks'> + <zeroOrMore> + <ref name='disksnapshot'/> + </zeroOrMore> + </element> + </optional> + <optional> <element name='active'> <choice> <value>0</value> @@ -72,4 +79,49 @@ </choice> </define> + <define name='disksnapshot'> + <element name='disk'> + <attribute name='name'> + <ref name='deviceName'/> + </attribute> + <choice> + <attribute name='snapshot'> + <value>no</value> + </attribute> + <attribute name='snapshot'> + <value>internal</value> + </attribute> + <group> + <optional> + <attribute name='snapshot'> + <value>external</value> + </attribute> + </optional> + <interleave> + <optional> + <element name='driver'> + <optional> + <attribute name='type'> + <ref name='genericName'/> + </attribute> + </optional> + <empty/> + </element> + </optional> + <optional> + <element name='source'> + <optional> + <attribute name='file'> + <ref name='absFilePath'/> + </attribute> + </optional> + <empty/> + </element> + </optional> + </interleave> + </group> + </choice> + </element> + </define> + </grammar> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 0713a25..d663a01 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -10987,18 +10987,82 @@ cleanup: } /* Snapshot Def functions */ +static void +virDomainSnapshotDiskDefClear(virDomainSnapshotDiskDefPtr disk) +{ + VIR_FREE(disk->name); + VIR_FREE(disk->file); + VIR_FREE(disk->driverType); +} + void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) { + int i; + if (!def) return; VIR_FREE(def->name); VIR_FREE(def->description); VIR_FREE(def->parent); + for (i = 0; i < def->ndisks; i++) + virDomainSnapshotDiskDefClear(&def->disks[i]); + VIR_FREE(def->disks); virDomainDefFree(def->dom); VIR_FREE(def); } +static int +virDomainSnapshotDiskDefParseXML(xmlNodePtr node, + virDomainSnapshotDiskDefPtr def) +{ + int ret = -1; + char *snapshot = NULL; + xmlNodePtr cur; + + def->name = virXMLPropString(node, "name"); + if (!def->name) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing name from disk snapshot element")); + goto cleanup; + } + + snapshot = virXMLPropString(node, "snapshot"); + if (snapshot) { + def->snapshot = virDomainDiskSnapshotTypeFromString(snapshot); + if (def->snapshot <= 0) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown disk snapshot setting '%s'"), + snapshot); + goto cleanup; + } + } + + cur = node->children; + while (cur) { + if (cur->type == XML_ELEMENT_NODE) { + if (!def->file && + xmlStrEqual(cur->name, BAD_CAST "source")) { + def->file = virXMLPropString(cur, "file"); + } else if (!def->driverType && + xmlStrEqual(cur->name, BAD_CAST "driver")) { + def->driverType = virXMLPropString(cur, "type"); + } + } + cur = cur->next; + } + + if (!def->snapshot && (def->file || def->driverType)) + def->snapshot = VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL; + + ret = 0; +cleanup: + VIR_FREE(snapshot); + if (ret < 0) + virDomainSnapshotDiskDefClear(def); + return ret; +} + /* If newSnapshot is true, caps, expectedVirtTypes, and flags are ignored. */ virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, @@ -11011,6 +11075,8 @@ virDomainSnapshotDefParseString(const char *xmlStr, xmlDocPtr xml = NULL; virDomainSnapshotDefPtr def = NULL; virDomainSnapshotDefPtr ret = NULL; + xmlNodePtr *nodes = NULL; + int i; char *creation = NULL, *state = NULL; struct timeval tv; int active; @@ -11044,6 +11110,19 @@ virDomainSnapshotDefParseString(const char *xmlStr, def->description = virXPathString("string(./description)", ctxt); + if ((i = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + goto cleanup; + def->ndisks = i; + if (def->ndisks && VIR_ALLOC_N(def->disks, def->ndisks) < 0) { + virReportOOMError(); + goto cleanup; + } + for (i = 0; i < def->ndisks; i++) { + if (virDomainSnapshotDiskDefParseXML(nodes[i], &def->disks[i]) < 0) + goto cleanup; + } + VIR_FREE(nodes); + if (!newSnapshot) { if (virXPathLongLong("string(./creationTime)", ctxt, &def->creationTime) < 0) { @@ -11106,6 +11185,7 @@ virDomainSnapshotDefParseString(const char *xmlStr, cleanup: VIR_FREE(creation); VIR_FREE(state); + VIR_FREE(nodes); xmlXPathFreeContext(ctxt); if (ret == NULL) virDomainSnapshotDefFree(def); @@ -11114,12 +11194,178 @@ cleanup: return ret; } +static int +disksorter(const void *a, const void *b) +{ + const virDomainSnapshotDiskDef *diska = a; + const virDomainSnapshotDiskDef *diskb = b; + + /* Integer overflow shouldn't be a problem here. */ + return diska->index - diskb->index; +} + +/* Align def->disks to def->domain. Sort the list of def->disks, + * filling in any missing disks or snapshot state defaults given by + * the domain, with a fallback to a passed in default. Issue an error + * and return -1 if any def->disks[n]->name appears more than once or + * does not map to dom->disks. If require_match, also require that + * existing def->disks snapshot states do not override explicit + * def->dom settings. */ +int +virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def, + int default_snapshot, + bool require_match) +{ + int ret = -1; + virBitmapPtr map = NULL; + int i; + int ndisks; + bool inuse; + + if (!def->dom) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in snapshot")); + goto cleanup; + } + + if (def->ndisks > def->dom->ndisks) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("too many disk snapshot requests for domain")); + goto cleanup; + } + + /* Unlikely to have a guest without disks but technically possible. */ + if (!def->dom->ndisks) { + ret = 0; + goto cleanup; + } + + if (!(map = virBitmapAlloc(def->dom->ndisks))) { + virReportOOMError(); + goto cleanup; + } + + /* Double check requested disks. */ + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + int idx = virDomainDiskIndexByName(def->dom, disk->name); + int disk_snapshot; + + if (idx < 0) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("no disk named '%s'"), disk->name); + goto cleanup; + } + disk_snapshot = def->dom->disks[idx]->snapshot; + + if (virBitmapGetBit(map, idx, &inuse) < 0 || inuse) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' specified twice"), + disk->name); + goto cleanup; + } + ignore_value(virBitmapSetBit(map, idx)); + disk->index = idx; + if (!disk_snapshot) + disk_snapshot = default_snapshot; + if (!disk->snapshot) { + disk->snapshot = disk_snapshot; + } else if (disk_snapshot && require_match && + disk->snapshot != disk_snapshot) { + const char *tmp = virDomainDiskSnapshotTypeToString(disk_snapshot); + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("disk '%s' must use snapshot mode '%s'"), + disk->name, tmp); + goto cleanup; + } + if (disk->file && + disk->snapshot != VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("file '%s' for disk '%s' requires " + "use of external snapshot mode"), + disk->file, disk->name); + goto cleanup; + } + } + + /* Provide defaults for all remaining disks. */ + ndisks = def->ndisks; + if (VIR_EXPAND_N(def->disks, def->ndisks, + def->dom->ndisks - def->ndisks) < 0) { + virReportOOMError(); + goto cleanup; + } + + for (i = 0; i < def->dom->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk; + + ignore_value(virBitmapGetBit(map, i, &inuse)); + if (inuse) + continue; + disk = &def->disks[ndisks++]; + if (!(disk->name = strdup(def->dom->disks[i]->dst))) { + virReportOOMError(); + goto cleanup; + } + disk->index = i; + disk->snapshot = def->dom->disks[i]->snapshot; + if (!disk->snapshot) + disk->snapshot = default_snapshot; + } + + qsort(&def->disks[0], def->ndisks, sizeof(def->disks[0]), disksorter); + + /* Generate any default external file names. */ + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + + if (disk->snapshot == VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL && + !disk->file) { + const char *original = def->dom->disks[i]->src; + const char *tmp; + + if (!original) { + virDomainReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("cannot generate external backup name " + "for disk '%s' without source"), + disk->name); + goto cleanup; + } + tmp = strrchr(original, '.'); + if (!tmp || strchr(tmp, '/')) { + ignore_value(virAsprintf(&disk->file, "%s.%s", + original, def->name)); + } else { + if ((tmp - original) > INT_MAX) { + virDomainReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("integer overflow")); + goto cleanup; + } + ignore_value(virAsprintf(&disk->file, "%.*s.%s", + (int) (tmp - original), original, + def->name)); + } + if (!disk->file) { + virReportOOMError(); + goto cleanup; + } + } + } + + ret = 0; + +cleanup: + virBitmapFree(map); + return ret; +} + char *virDomainSnapshotDefFormat(char *domain_uuid, virDomainSnapshotDefPtr def, unsigned int flags, int internal) { virBuffer buf = VIR_BUFFER_INITIALIZER; + int i; virCheckFlags(VIR_DOMAIN_XML_SECURE, NULL); @@ -11139,6 +11385,34 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, } virBufferAsprintf(&buf, " <creationTime>%lld</creationTime>\n", def->creationTime); + /* For now, only output <disks> on disk-snapshot */ + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT) { + virBufferAddLit(&buf, " <disks>\n"); + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + + if (!disk->name) + continue; + + virBufferEscapeString(&buf, " <disk name='%s'", disk->name); + if (disk->snapshot) + virBufferAsprintf(&buf, " snapshot='%s'", + virDomainDiskSnapshotTypeToString(disk->snapshot)); + if (disk->file || disk->driverType) { + virBufferAddLit(&buf, ">\n"); + if (disk->file) + virBufferEscapeString(&buf, " <source file='%s'/>\n", + disk->file); + if (disk->driverType) + virBufferEscapeString(&buf, " <driver type='%s'/>\n", + disk->driverType); + virBufferAddLit(&buf, " </disk>\n"); + } else { + virBufferAddLit(&buf, "/>\n"); + } + } + virBufferAddLit(&buf, " </disks>\n"); + } if (def->dom) { virDomainDefFormatInternal(def->dom, flags, &buf); } else { diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index ea8194a..d835f96 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1308,7 +1308,20 @@ enum virDomainTaintFlags { VIR_DOMAIN_TAINT_LAST }; -/* Snapshot state */ +/* Items related to snapshot state */ + +/* Stores disk-snapshot information */ +typedef struct _virDomainSnapshotDiskDef virDomainSnapshotDiskDef; +typedef virDomainSnapshotDiskDef *virDomainSnapshotDiskDefPtr; +struct _virDomainSnapshotDiskDef { + char *name; /* name matching the <target dev='...' of the domain */ + int index; /* index within snapshot->dom->disks that matches name */ + int snapshot; /* enum virDomainDiskSnapshot */ + char *file; /* new source file when snapshot is external */ + char *driverType; /* file format type of new file */ +}; + +/* Stores the complete snapshot metadata */ typedef struct _virDomainSnapshotDef virDomainSnapshotDef; typedef virDomainSnapshotDef *virDomainSnapshotDefPtr; struct _virDomainSnapshotDef { @@ -1318,6 +1331,10 @@ struct _virDomainSnapshotDef { char *parent; long long creationTime; /* in seconds */ int state; /* enum virDomainSnapshotState */ + + size_t ndisks; /* should not exceed dom->ndisks */ + virDomainSnapshotDiskDef *disks; + virDomainDefPtr dom; /* Internal use. */ @@ -1351,6 +1368,9 @@ char *virDomainSnapshotDefFormat(char *domain_uuid, virDomainSnapshotDefPtr def, unsigned int flags, int internal); +int virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr snapshot, + int default_snapshot, + bool require_match); virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, const virDomainSnapshotDefPtr def); diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 034443c..a953326 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -381,6 +381,7 @@ virDomainSmartcardDefForeach; virDomainSmartcardDefFree; virDomainSmartcardTypeFromString; virDomainSmartcardTypeToString; +virDomainSnapshotAlignDisks; virDomainSnapshotAssignDef; virDomainSnapshotDefFormat; virDomainSnapshotDefFree; diff --git a/tests/domainsnapshotxml2xmlin/disk_snapshot.xml b/tests/domainsnapshotxml2xmlin/disk_snapshot.xml new file mode 100644 index 0000000..1f0beb6 --- /dev/null +++ b/tests/domainsnapshotxml2xmlin/disk_snapshot.xml @@ -0,0 +1,16 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <disks> + <disk name='hda'/> + <disk name='hdb' snapshot='no'/> + <disk name='hdc' snapshot='internal'/> + <disk name='hdd' snapshot='external'> + <source/> + <driver type='qed'/> + </disk> + <disk name='hde' snapshot='external'> + <source file='/path/to/new'/> + </disk> + </disks> +</domainsnapshot> diff --git a/tests/domainsnapshotxml2xmlout/disk_snapshot.xml b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml index 391bb57..e0414a1 100644 --- a/tests/domainsnapshotxml2xmlout/disk_snapshot.xml +++ b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml @@ -6,6 +6,23 @@ </parent> <state>disk-snapshot</state> <creationTime>1272917631</creationTime> + <disks> + <disk name='hda' snapshot='no'/> + <disk name='hdb' snapshot='no'/> + <disk name='hdc' snapshot='internal'/> + <disk name='hdd' snapshot='external'> + <driver type='qed'/> + <source file='/path/to/generated4'/> + </disk> + <disk name='hde' snapshot='external'> + <driver type='qcow2'/> + <source file='/path/to/new'/> + </disk> + <disk name='hdf' snapshot='external'> + <driver type='qcow2'/> + <source file='/path/to/generated5'/> + </disk> + </disks> <domain type='qemu'> <name>QEMUGuest1</name> <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> @@ -27,6 +44,31 @@ <target dev='hda' bus='ide'/> <address type='drive' controller='0' bus='0' unit='0'/> </disk> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='hdb' bus='ide'/> + <address type='drive' controller='0' bus='1' unit='0'/> + </disk> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest3'/> + <target dev='hdc' bus='ide'/> + <address type='drive' controller='0' bus='2' unit='0'/> + </disk> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest4'/> + <target dev='hdd' bus='ide'/> + <address type='drive' controller='0' bus='3' unit='0'/> + </disk> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest5'/> + <target dev='hde' bus='ide'/> + <address type='drive' controller='0' bus='4' unit='0'/> + </disk> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest6'/> + <target dev='hdf' bus='ide'/> + <address type='drive' controller='0' bus='5' unit='0'/> + </disk> <controller type='ide' index='0'/> <memballoon model='virtio'/> </devices> -- 1.7.4.4

I got confused when 'virsh domblkinfo dom disk' required the path to a disk (which can be ambiguous, since a single file can back multiple disks), rather than the unambiguous target device name that I was using in disk snapshots. So, in true developer fashion, I went for the best of both worlds - all interfaces that operate on a disk (aka block) now accept either the target name or the unambiguous path to the backing file used by the disk. * src/conf/domain_conf.h (virDomainDiskIndexByName): Add parameter. (virDomainDiskPathByName): New prototype. * src/libvirt_private.syms (domain_conf.h): Export it. * src/conf/domain_conf.c (virDomainDiskIndexByName): Also allow searching by path, and decide whether ambiguity is okay. (virDomainDiskPathByName): New function. (virDomainDiskRemoveByName, virDomainSnapshotAlignDisks): Update callers. * src/qemu/qemu_driver.c (qemudDomainBlockPeek) (qemuDomainAttachDeviceConfig, qemuDomainUpdateDeviceConfig) (qemuDomainGetBlockInfo, qemuDiskPathToAlias): Likewise. * src/qemu/qemu_process.c (qemuProcessFindDomainDiskByPath): Likewise. * src/libxl/libxl_driver.c (libxlDomainAttachDeviceDiskLive) (libxlDomainDetachDeviceDiskLive, libxlDomainAttachDeviceConfig) (libxlDomainUpdateDeviceConfig): Likewise. * src/uml/uml_driver.c (umlDomainBlockPeek): Likewise. * src/xen/xend_internal.c (xenDaemonDomainBlockPeek): Likewise. * docs/formatsnapshot.html.in: Update documentation. * tools/virsh.pod (domblkstat, domblkinfo): Likewise. * docs/schemas/domaincommon.rng (diskTarget): Tighten pattern on disk targets. * docs/schemas/domainsnapshot.rng (disksnapshot): Update to match. * tests/domainsnapshotxml2xmlin/disk_snapshot.xml: Update test. --- docs/formatsnapshot.html.in | 7 +- docs/schemas/domaincommon.rng | 7 ++- docs/schemas/domainsnapshot.rng | 5 +- src/conf/domain_conf.c | 57 ++++++++++--- src/conf/domain_conf.h | 4 +- src/libvirt_private.syms | 1 + src/libxl/libxl_driver.c | 11 ++- src/qemu/qemu_driver.c | 102 ++++++++++------------ src/qemu/qemu_process.c | 11 +-- src/uml/uml_driver.c | 56 ++++++------- src/xen/xend_internal.c | 12 +-- tests/domainsnapshotxml2xmlin/disk_snapshot.xml | 2 +- tools/virsh.pod | 8 ++- 13 files changed, 154 insertions(+), 129 deletions(-) diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index 953272c..e1a43e7 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -109,8 +109,9 @@ <dt><code>disk</code></dt> <dd>This sub-element describes the snapshot properties of a specific disk. The attribute <code>name</code> is - mandatory, and must match the <code><target - dev='name'/></code> of one of + mandatory, and must match either the <code><target + dev='name'/></code> or an unambiguous <code><source + file='name'/></code> of one of the <a href="formatdomain.html#elementsDisks">disk devices</a> specified for the domain at the time of the snapshot. The attribute <code>snapshot</code> is @@ -182,7 +183,7 @@ <domainsnapshot> <description>Snapshot of OS install and updates</description> <disks> - <disk name='vda'> + <disk name='/path/to/old'> <source file='/path/to/new'/> </disk> <disk name='vdb' snapshot='no'/> diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index 0af9e0f..e1c6a95 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -770,10 +770,15 @@ </choice> </element> </define> + <define name="diskTarget"> + <data type="string"> + <param name="pattern">(ioemu:)?(fd|hd|sd|vd|xvd|ubd)[a-zA-Z0-9_]+</param> + </data> + </define> <define name="target"> <element name="target"> <attribute name="dev"> - <ref name="deviceName"/> + <ref name="diskTarget"/> </attribute> <optional> <attribute name="bus"> diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 671fbe0..0ef0631 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -82,7 +82,10 @@ <define name='disksnapshot'> <element name='disk'> <attribute name='name'> - <ref name='deviceName'/> + <choice> + <ref name='diskTarget'/> + <ref name='absFilePath'/> + </choice> </attribute> <choice> <attribute name='snapshot'> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index d663a01..effc2a3 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -5516,17 +5516,44 @@ virDomainChrTargetTypeToString(int deviceType, return type; } -int virDomainDiskIndexByName(virDomainDefPtr def, const char *name) +int +virDomainDiskIndexByName(virDomainDefPtr def, const char *name, + bool allow_ambiguous) { virDomainDiskDefPtr vdisk; int i; + int candidate = -1; + /* We prefer the <target dev='name'/> name (it's shorter, required + * for all disks, and should be unambiguous), but also support + * <source file='name'/> (if unambiguous). Assume dst if there is + * no leading slash, source name otherwise. */ for (i = 0; i < def->ndisks; i++) { vdisk = def->disks[i]; - if (STREQ(vdisk->dst, name)) - return i; + if (*name != '/') { + if (STREQ(vdisk->dst, name)) + return i; + } else if (vdisk->src && + STREQ(vdisk->src, name)) { + if (allow_ambiguous) + return i; + if (candidate >= 0) + return -1; + candidate = i; + } } - return -1; + return candidate; +} + +/* Return the path to a disk image if a string identifies at least one + * disk belonging to the domain (both device strings 'vda' and paths + * '/path/to/file' are converted into '/path/to/file'). */ +const char * +virDomainDiskPathByName(virDomainDefPtr def, const char *name) +{ + int i = virDomainDiskIndexByName(def, name, true); + + return i < 0 ? NULL : def->disks[i]->src; } int virDomainDiskInsert(virDomainDefPtr def, @@ -5602,7 +5629,7 @@ void virDomainDiskRemove(virDomainDefPtr def, size_t i) int virDomainDiskRemoveByName(virDomainDefPtr def, const char *name) { - int i = virDomainDiskIndexByName(def, name); + int i = virDomainDiskIndexByName(def, name, false); if (i < 0) return -1; virDomainDiskRemove(def, i); @@ -11206,11 +11233,12 @@ disksorter(const void *a, const void *b) /* Align def->disks to def->domain. Sort the list of def->disks, * filling in any missing disks or snapshot state defaults given by - * the domain, with a fallback to a passed in default. Issue an error - * and return -1 if any def->disks[n]->name appears more than once or - * does not map to dom->disks. If require_match, also require that - * existing def->disks snapshot states do not override explicit - * def->dom settings. */ + * the domain, with a fallback to a passed in default. Convert paths + * to disk targets for uniformity. Issue an error and return -1 if + * any def->disks[n]->name appears more than once or does not map to + * dom->disks. If require_match, also require that existing + * def->disks snapshot states do not override explicit def->dom + * settings. */ int virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def, int default_snapshot, @@ -11248,7 +11276,7 @@ virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def, /* Double check requested disks. */ for (i = 0; i < def->ndisks; i++) { virDomainSnapshotDiskDefPtr disk = &def->disks[i]; - int idx = virDomainDiskIndexByName(def->dom, disk->name); + int idx = virDomainDiskIndexByName(def->dom, disk->name, false); int disk_snapshot; if (idx < 0) { @@ -11286,6 +11314,13 @@ virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def, disk->file, disk->name); goto cleanup; } + if (STRNEQ(disk->name, def->dom->disks[idx]->dst)) { + VIR_FREE(disk->name); + if (!(disk->name = strdup(def->dom->disks[idx]->dst))) { + virReportOOMError(); + goto cleanup; + } + } } /* Provide defaults for all remaining disks. */ diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index d835f96..c240fe3 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1557,7 +1557,9 @@ int virDomainVcpuPinAdd(virDomainDefPtr def, int virDomainVcpuPinDel(virDomainDefPtr def, int vcpu); -int virDomainDiskIndexByName(virDomainDefPtr def, const char *name); +int virDomainDiskIndexByName(virDomainDefPtr def, const char *name, + bool allow_ambiguous); +const char *virDomainDiskPathByName(virDomainDefPtr, const char *name); int virDomainDiskInsert(virDomainDefPtr def, virDomainDiskDefPtr disk); void virDomainDiskInsertPreAlloced(virDomainDefPtr def, diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index a953326..5e8f30c 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -284,6 +284,7 @@ virDomainDiskInsert; virDomainDiskInsertPreAlloced; virDomainDiskIoTypeFromString; virDomainDiskIoTypeToString; +virDomainDiskPathByName; virDomainDiskRemove; virDomainDiskRemoveByName; virDomainDiskSnapshotTypeFromString; diff --git a/src/libxl/libxl_driver.c b/src/libxl/libxl_driver.c index d6e0c28..fe141bc 100644 --- a/src/libxl/libxl_driver.c +++ b/src/libxl/libxl_driver.c @@ -2929,7 +2929,7 @@ libxlDomainAttachDeviceDiskLive(libxlDomainObjPrivatePtr priv, break; case VIR_DOMAIN_DISK_DEVICE_DISK: if (l_disk->bus == VIR_DOMAIN_DISK_BUS_XEN) { - if (virDomainDiskIndexByName(vm->def, l_disk->dst) >= 0) { + if (virDomainDiskIndexByName(vm->def, l_disk->dst, true) >= 0) { libxlError(VIR_ERR_OPERATION_FAILED, _("target %s already exists"), l_disk->dst); goto cleanup; @@ -2991,7 +2991,8 @@ libxlDomainDetachDeviceDiskLive(libxlDomainObjPrivatePtr priv, if (dev->data.disk->bus == VIR_DOMAIN_DISK_BUS_XEN) { if ((i = virDomainDiskIndexByName(vm->def, - dev->data.disk->dst)) < 0) { + dev->data.disk->dst, + false)) < 0) { libxlError(VIR_ERR_OPERATION_FAILED, _("disk %s not found"), dev->data.disk->dst); goto cleanup; @@ -3061,7 +3062,7 @@ libxlDomainAttachDeviceConfig(virDomainDefPtr vmdef, virDomainDeviceDefPtr dev) switch (dev->type) { case VIR_DOMAIN_DEVICE_DISK: disk = dev->data.disk; - if (virDomainDiskIndexByName(vmdef, disk->dst) >= 0) { + if (virDomainDiskIndexByName(vmdef, disk->dst, true) >= 0) { libxlError(VIR_ERR_INVALID_ARG, _("target %s already exists."), disk->dst); return -1; @@ -3172,9 +3173,9 @@ libxlDomainUpdateDeviceConfig(virDomainDefPtr vmdef, virDomainDeviceDefPtr dev) switch (dev->type) { case VIR_DOMAIN_DEVICE_DISK: disk = dev->data.disk; - if ((i = virDomainDiskIndexByName(vmdef, disk->dst)) < 0) { + if ((i = virDomainDiskIndexByName(vmdef, disk->dst, false)) < 0) { libxlError(VIR_ERR_INVALID_ARG, - _("target %s doesn't exists."), disk->dst); + _("target %s doesn't exist."), disk->dst); goto cleanup; } orig = vmdef->disks[i]; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ae63a02..e60d508 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -5349,7 +5349,7 @@ qemuDomainAttachDeviceConfig(virDomainDefPtr vmdef, switch (dev->type) { case VIR_DOMAIN_DEVICE_DISK: disk = dev->data.disk; - if (virDomainDiskIndexByName(vmdef, disk->dst) >= 0) { + if (virDomainDiskIndexByName(vmdef, disk->dst, true) >= 0) { qemuReportError(VIR_ERR_INVALID_ARG, _("target %s already exists."), disk->dst); return -1; @@ -5467,10 +5467,10 @@ qemuDomainUpdateDeviceConfig(virDomainDefPtr vmdef, switch (dev->type) { case VIR_DOMAIN_DEVICE_DISK: disk = dev->data.disk; - pos = virDomainDiskIndexByName(vmdef, disk->dst); + pos = virDomainDiskIndexByName(vmdef, disk->dst, false); if (pos < 0) { qemuReportError(VIR_ERR_INVALID_ARG, - _("target %s doesn't exists."), disk->dst); + _("target %s doesn't exist."), disk->dst); return -1; } orig = vmdef->disks[pos]; @@ -7209,7 +7209,8 @@ qemudDomainBlockPeek (virDomainPtr dom, { struct qemud_driver *driver = dom->conn->privateData; virDomainObjPtr vm; - int fd = -1, ret = -1, i; + int fd = -1, ret = -1; + const char *actual; virCheckFlags(0, -1); @@ -7231,42 +7232,35 @@ qemudDomainBlockPeek (virDomainPtr dom, goto cleanup; } - /* Check the path belongs to this domain. */ - for (i = 0 ; i < vm->def->ndisks ; i++) { - if (vm->def->disks[i]->src != NULL && - STREQ (vm->def->disks[i]->src, path)) { - ret = 0; - break; - } + /* Check the path belongs to this domain. */ + if (!(actual = virDomainDiskPathByName(vm->def, path))) { + qemuReportError(VIR_ERR_INVALID_ARG, + _("invalid path '%s'"), path); + goto cleanup; } + path = actual; - if (ret == 0) { - ret = -1; - /* The path is correct, now try to open it and get its size. */ - fd = open (path, O_RDONLY); - if (fd == -1) { - virReportSystemError(errno, - _("%s: failed to open"), path); - goto cleanup; - } - - /* Seek and read. */ - /* NB. Because we configure with AC_SYS_LARGEFILE, off_t should - * be 64 bits on all platforms. - */ - if (lseek (fd, offset, SEEK_SET) == (off_t) -1 || - saferead (fd, buffer, size) == (ssize_t) -1) { - virReportSystemError(errno, - _("%s: failed to seek or read"), path); - goto cleanup; - } + /* The path is correct, now try to open it and get its size. */ + fd = open (path, O_RDONLY); + if (fd == -1) { + virReportSystemError(errno, + _("%s: failed to open"), path); + goto cleanup; + } - ret = 0; - } else { - qemuReportError(VIR_ERR_INVALID_ARG, - "%s", _("invalid path")); + /* Seek and read. */ + /* NB. Because we configure with AC_SYS_LARGEFILE, off_t should + * be 64 bits on all platforms. + */ + if (lseek (fd, offset, SEEK_SET) == (off_t) -1 || + saferead (fd, buffer, size) == (ssize_t) -1) { + virReportSystemError(errno, + _("%s: failed to seek or read"), path); + goto cleanup; } + ret = 0; + cleanup: VIR_FORCE_CLOSE(fd); if (vm) @@ -7381,8 +7375,8 @@ static int qemuDomainGetBlockInfo(virDomainPtr dom, virStorageFileMetadata *meta = NULL; virDomainDiskDefPtr disk = NULL; struct stat sb; - int i; int format; + const char *actual; virCheckFlags(0, -1); @@ -7404,19 +7398,12 @@ static int qemuDomainGetBlockInfo(virDomainPtr dom, } /* Check the path belongs to this domain. */ - for (i = 0 ; i < vm->def->ndisks ; i++) { - if (vm->def->disks[i]->src != NULL && - STREQ (vm->def->disks[i]->src, path)) { - disk = vm->def->disks[i]; - break; - } - } - - if (!disk) { + if (!(actual = virDomainDiskPathByName(vm->def, path))) { qemuReportError(VIR_ERR_INVALID_ARG, _("invalid path %s not assigned to domain"), path); goto cleanup; } + path = actual; /* The path is correct, now try to open it and get its size. */ fd = open (path, O_RDONLY); @@ -9561,23 +9548,26 @@ static const char * qemuDiskPathToAlias(virDomainObjPtr vm, const char *path) { int i; char *ret = NULL; + virDomainDiskDefPtr disk; - for (i = 0 ; i < vm->def->ndisks ; i++) { - virDomainDiskDefPtr disk = vm->def->disks[i]; + i = virDomainDiskIndexByName(vm->def, path, true); + if (i < 0) + goto cleanup; - if (disk->type != VIR_DOMAIN_DISK_TYPE_BLOCK && - disk->type != VIR_DOMAIN_DISK_TYPE_FILE) - continue; + disk = vm->def->disks[i]; - if (disk->src != NULL && STREQ(disk->src, path)) { - if (virAsprintf(&ret, "drive-%s", disk->info.alias) < 0) { - virReportOOMError(); - return NULL; - } - break; + if (disk->type != VIR_DOMAIN_DISK_TYPE_BLOCK && + disk->type != VIR_DOMAIN_DISK_TYPE_FILE) + goto cleanup; + + if (disk->src) { + if (virAsprintf(&ret, "drive-%s", disk->info.alias) < 0) { + virReportOOMError(); + return NULL; } } +cleanup: if (!ret) { qemuReportError(VIR_ERR_INVALID_ARG, "%s", _("No device found for specified path")); diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index f691bbb..be190d0 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -192,15 +192,10 @@ static virDomainDiskDefPtr qemuProcessFindDomainDiskByPath(virDomainObjPtr vm, const char *path) { - int i; - - for (i = 0; i < vm->def->ndisks; i++) { - virDomainDiskDefPtr disk; + int i = virDomainDiskIndexByName(vm->def, path, true); - disk = vm->def->disks[i]; - if (disk->src != NULL && STREQ(disk->src, path)) - return disk; - } + if (i >= 0) + return vm->def->disks[i]; qemuReportError(VIR_ERR_INTERNAL_ERROR, _("no disk found with path %s"), diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 19b6c55..75c8f27 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -2175,7 +2175,8 @@ umlDomainBlockPeek(virDomainPtr dom, { struct uml_driver *driver = dom->conn->privateData; virDomainObjPtr vm; - int fd = -1, ret = -1, i; + int fd = -1, ret = -1; + const char *actual; virCheckFlags(0, -1); @@ -2196,41 +2197,34 @@ umlDomainBlockPeek(virDomainPtr dom, } /* Check the path belongs to this domain. */ - for (i = 0 ; i < vm->def->ndisks ; i++) { - if (vm->def->disks[i]->src != NULL && - STREQ (vm->def->disks[i]->src, path)) { - ret = 0; - break; - } + if (!(actual = virDomainDiskPathByName(vm->def, path))) { + umlReportError(VIR_ERR_INVALID_ARG, + _("invalid path '%s'"), path); + goto cleanup; } + path = actual; - if (ret == 0) { - ret = -1; - /* The path is correct, now try to open it and get its size. */ - fd = open (path, O_RDONLY); - if (fd == -1) { - virReportSystemError(errno, - _("cannot open %s"), path); - goto cleanup; - } - - /* Seek and read. */ - /* NB. Because we configure with AC_SYS_LARGEFILE, off_t should - * be 64 bits on all platforms. - */ - if (lseek (fd, offset, SEEK_SET) == (off_t) -1 || - saferead (fd, buffer, size) == (ssize_t) -1) { - virReportSystemError(errno, - _("cannot read %s"), path); - goto cleanup; - } + /* The path is correct, now try to open it and get its size. */ + fd = open (path, O_RDONLY); + if (fd == -1) { + virReportSystemError(errno, + _("cannot open %s"), path); + goto cleanup; + } - ret = 0; - } else { - umlReportError(VIR_ERR_INVALID_ARG, "%s", - _("invalid path")); + /* Seek and read. */ + /* NB. Because we configure with AC_SYS_LARGEFILE, off_t should + * be 64 bits on all platforms. + */ + if (lseek (fd, offset, SEEK_SET) == (off_t) -1 || + saferead (fd, buffer, size) == (ssize_t) -1) { + virReportSystemError(errno, + _("cannot read %s"), path); + goto cleanup; } + ret = 0; + cleanup: VIR_FORCE_CLOSE(fd); if (vm) diff --git a/src/xen/xend_internal.c b/src/xen/xend_internal.c index f44d674..cb6b8d1 100644 --- a/src/xen/xend_internal.c +++ b/src/xen/xend_internal.c @@ -3851,11 +3851,11 @@ xenDaemonDomainBlockPeek (virDomainPtr domain, const char *path, xenUnifiedPrivatePtr priv; struct sexpr *root = NULL; int fd = -1, ret = -1; - int found = 0, i; virDomainDefPtr def; int id; char * tty; int vncport; + const char *actual; priv = (xenUnifiedPrivatePtr) domain->conn->privateData; @@ -3891,18 +3891,12 @@ xenDaemonDomainBlockPeek (virDomainPtr domain, const char *path, vncport))) goto cleanup; - for (i = 0 ; i < def->ndisks ; i++) { - if (def->disks[i]->src && - STREQ(def->disks[i]->src, path)) { - found = 1; - break; - } - } - if (!found) { + if (!(actual = virDomainDiskPathByName(def, path))) { virXendError(VIR_ERR_INVALID_ARG, _("%s: invalid path"), path); goto cleanup; } + path = actual; /* The path is correct, now try to open it and get its size. */ fd = open (path, O_RDONLY); diff --git a/tests/domainsnapshotxml2xmlin/disk_snapshot.xml b/tests/domainsnapshotxml2xmlin/disk_snapshot.xml index 1f0beb6..ee6b46a 100644 --- a/tests/domainsnapshotxml2xmlin/disk_snapshot.xml +++ b/tests/domainsnapshotxml2xmlin/disk_snapshot.xml @@ -2,7 +2,7 @@ <name>my snap name</name> <description>!@#$%^</description> <disks> - <disk name='hda'/> + <disk name='/dev/HostVG/QEMUGuest1'/> <disk name='hdb' snapshot='no'/> <disk name='hdc' snapshot='internal'/> <disk name='hdd' snapshot='external'> diff --git a/tools/virsh.pod b/tools/virsh.pod index c77b1e4..c37c9e4 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -429,7 +429,9 @@ the domain is persistent, these flags are ignored. =item B<domblkstat> I<domain> I<block-device> -Get device block stats for a running domain. +Get device block stats for a running domain. A I<block-device> corresponds +to a unique target name (<target dev='name'/>) or source file (<source +file='name'/>) for one of the disk devices attached to I<domain>. =item B<domifstat> I<domain> I<interface-device> @@ -441,7 +443,9 @@ Get memory stats for a running domain. =item B<domblkinfo> I<domain> I<block-device> -Get block device size info for a domain. +Get block device size info for a domain. A I<block-device> corresponds +to a unique target name (<target dev='name'/>) or source file (<source +file='name'/>) for one of the disk devices attached to I<domain>. =item B<blockpull> I<domain> I<path> [I<bandwidth>] -- 1.7.4.4

This adds a convenience function to virsh that parses out block information from the domain xml, making it much easier to see what strings can be used in all other contexts that demand a specific block name, especially when given the previous patch that allows using either target or unique source name. As an example on a domain with one disk and an empty cdrom drive: Target Source ------------------------------------------- vda /var/lib/libvirt/images/fedora_12.img hdc - * tools/virsh.c (cmdDomblklist): New function. * tools/virsh.pod (domblklist): Document it. --- tools/virsh.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 10 +++++++ 2 files changed, 91 insertions(+), 0 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index d4581f7..fa80a52 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1277,6 +1277,86 @@ cmdDomblkinfo(vshControl *ctl, const vshCmd *cmd) } /* + * "domblklist" command + */ +static const vshCmdInfo info_domblklist[] = { + {"help", N_("list all domain blocks")}, + {"desc", N_("Get the names of block devices for a domain.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_domblklist[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"inactive", VSH_OT_BOOL, 0, + N_("get inactive rather than running configuration")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdDomblklist(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + bool ret = false; + int flags = 0; + char *xml = NULL; + xmlDocPtr xmldoc = NULL; + xmlXPathContextPtr ctxt = NULL; + int ndisks; + xmlNodePtr *disks = NULL; + int i; + + if (vshCommandOptBool(cmd, "inactive")) + flags |= VIR_DOMAIN_XML_INACTIVE; + + if (!vshConnectionUsability(ctl, ctl->conn)) + return false; + + if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) + return false; + + xml = virDomainGetXMLDesc(dom, flags); + if (!xml) + goto cleanup; + + xmldoc = virXMLParseStringCtxt(xml, "domain.xml", &ctxt); + if (!xmldoc) + goto cleanup; + + ndisks = virXPathNodeSet("./devices/disk", ctxt, &disks); + if (ndisks < 0) + goto cleanup; + + vshPrint(ctl, "%-10s %s\n", _("Target"), _("Source")); + vshPrint(ctl, "------------------------------------------------\n"); + + for (i = 0; i < ndisks; i++) { + char *target; + char *source; + + ctxt->node = disks[i]; + target = virXPathString("string(./target/@dev)", ctxt); + if (!target) { + vshError(ctl, "unable to query block list"); + goto cleanup; + } + source = virXPathString("string(./source/@file" + "|./source/@dev" + "|./source/@dir" + "|./source/@name)", ctxt); + vshPrint(ctl, "%-10s %s\n", target, source ? source : "-"); + VIR_FREE(target); + VIR_FREE(source); + } + + ret = 0; + +cleanup: + VIR_FREE(disks); + virDomainFree(dom); + return ret; +} + +/* * "suspend" command */ static const vshCmdInfo info_suspend[] = { @@ -13015,6 +13095,7 @@ static const vshCmdDef domManagementCmds[] = { static const vshCmdDef domMonitoringCmds[] = { {"domblkinfo", cmdDomblkinfo, opts_domblkinfo, info_domblkinfo, 0}, + {"domblklist", cmdDomblklist, opts_domblklist, info_domblklist, 0}, {"domblkstat", cmdDomblkstat, opts_domblkstat, info_domblkstat, 0}, {"domcontrol", cmdDomControl, opts_domcontrol, info_domcontrol, 0}, {"domifstat", cmdDomIfstat, opts_domifstat, info_domifstat, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index c37c9e4..b357330 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -447,6 +447,16 @@ Get block device size info for a domain. A I<block-device> corresponds to a unique target name (<target dev='name'/>) or source file (<source file='name'/>) for one of the disk devices attached to I<domain>. +=item B<domblklist> I<domain> [I<--inactive>] + +Print a table showing the names of all block devices associated with +I<domain>, as well as the path to the source of each device. If +I<--inactive> is specified, query the block devices that will be used +on the next boot, rather than those currently in use by a running +domain. Other contexts that require a block device name (such as +I<domblkinfo> or I<snapshot-create> for disk snapshots) will accept +either target or unique source names printed by this command. + =item B<blockpull> I<domain> I<path> [I<bandwidth>] Populate a disk from its backing image. Once all data from its backing -- 1.7.4.4

Prior to this patch, <domainsnapshot>/<disks> was ignored. This changes it to be an error unless an explicit disk snapshot is requested (a future patch may relax things if it turns out to be useful to have a <disks> specification alongside a system checkpoint). * include/libvirt/libvirt.h.in (VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY): New flag. * src/libvirt.c (virDomainSnapshotCreateXML): Document it. * src/esx/esx_driver.c (esxDomainSnapshotCreateXML): Disk snapshots not supported yet. * src/vbox/vbox_tmpl.c (vboxDomainSnapshotCreateXML): Likewise. * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Likewise. --- include/libvirt/libvirt.h.in | 2 ++ src/esx/esx_driver.c | 6 ++++++ src/libvirt.c | 6 ++++++ src/qemu/qemu_driver.c | 6 ++++++ src/vbox/vbox_tmpl.c | 6 ++++++ 5 files changed, 26 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index c62577d..440af92 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -2574,6 +2574,8 @@ typedef virDomainSnapshot *virDomainSnapshotPtr; typedef enum { VIR_DOMAIN_SNAPSHOT_CREATE_HALT = (1 << 0), /* Stop running guest after snapshot is complete */ + VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY = (1 << 1), /* disk snapshot, not + system checkpoint */ } virDomainSnapshotCreateFlags; /* Take a snapshot of the current VM state */ diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index 8fbbe8a..14b58c9 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -4229,6 +4229,12 @@ esxDomainSnapshotCreateXML(virDomainPtr domain, const char *xmlDesc, return NULL; } + if (def->ndisks) { + ESX_ERROR(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk snapshots not supported yet")); + return NULL; + } + if (esxVI_LookupVirtualMachineByUuidAndPrepareForTask (priv->primary, domain->uuid, NULL, &virtualMachine, priv->parsedUri->autoAnswer) < 0 || diff --git a/src/libvirt.c b/src/libvirt.c index ffd27bc..c0a17bb 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -15482,6 +15482,12 @@ error: * it was active before; otherwise, a running domain will still be * running after the snapshot. This flag is invalid on transient domains. * + * If @flags includes VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY, then the + * snapshot will be limited to the disks described in @xmlDesc, and no + * VM state will be saved. For an active guest, the disk image may be + * inconsistent (as if power had been pulled), and specifying this + * with the VIR_DOMAIN_SNAPSHOT_CREATE_HALT flag risks data loss. + * * Returns an (opaque) virDomainSnapshotPtr on success, NULL on failure. */ virDomainSnapshotPtr diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index e60d508..aeb8ad9 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8717,6 +8717,12 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, if (!qemuDomainSnapshotIsAllowed(vm)) goto cleanup; + if (def->ndisks) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk snapshots not supported yet")); + goto cleanup; + } + if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) goto cleanup; def = NULL; diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 0dd5efa..1715432 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -5664,6 +5664,12 @@ vboxDomainSnapshotCreateXML(virDomainPtr dom, if (!(def = virDomainSnapshotDefParseString(xmlDesc, 1, NULL, 0, 0))) goto cleanup; + if (def->ndisks) { + vboxError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk snapshots not supported yet")); + goto cleanup; + } + vboxIIDFromUUID(&domiid, dom->uuid); rc = VBOX_OBJECT_GET_MACHINE(domiid.value, &machine); if (NS_FAILED(rc)) { -- 1.7.4.4

My RFC for snapshot support [1] proposes several rules for when it is safe to delete or revert to an external snapshot, predicated on the existence of new API flags. These will be incrementally added in future patches, but until then, blindly mishandling a disk snapshot risks corrupting internal state, so it is better to outright reject the attempts until the other pieces are in place, thus incrementally relaxing the restrictions added in this patch. [1] https://www.redhat.com/archives/libvir-list/2011-August/msg00361.html * src/qemu/qemu_driver.c (qemuDomainSnapshotCountExternal): New function. (qemuDomainDestroyFlags, qemuDomainUndefineFlags) (qemuDomainSnapshotDelete): Use it to add safety valve. (qemuDomainRevertToSnapshot): Add safety valve. --- src/qemu/qemu_driver.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 59 insertions(+), 0 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index aeb8ad9..5652e62 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -1777,6 +1777,19 @@ qemuDomainSnapshotDiscardAll(void *payload, curr->err = err; } +/* Count how many snapshots in a set have external disk snapshots. */ +static void +qemuDomainSnapshotCountExternal(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr snap = payload; + int *count = data; + + if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) + (*count)++; +} + static int qemuDomainDestroyFlags(virDomainPtr dom, unsigned int flags) @@ -1787,6 +1800,7 @@ qemuDomainDestroyFlags(virDomainPtr dom, virDomainEventPtr event = NULL; qemuDomainObjPrivatePtr priv; int nsnapshots; + int external = 0; virCheckFlags(VIR_DOMAIN_DESTROY_SNAPSHOTS_METADATA | VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL, -1); @@ -1814,6 +1828,16 @@ qemuDomainDestroyFlags(virDomainPtr dom, goto cleanup; } + if ((flags & VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL) && + virHashForEach(vm->snapshots.objs, qemuDomainSnapshotCountExternal, + &external) && + external) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("deletion of %d external disk snapshots not " + "supported yet"), external); + goto cleanup; + } + rem.driver = driver; rem.vm = vm; rem.metadata_only = !(flags & VIR_DOMAIN_DESTROY_SNAPSHOTS_FULL); @@ -4943,6 +4967,7 @@ qemuDomainUndefineFlags(virDomainPtr dom, char *name = NULL; int ret = -1; int nsnapshots; + int external = 0; virCheckFlags(VIR_DOMAIN_UNDEFINE_MANAGED_SAVE | VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA | @@ -4972,6 +4997,16 @@ qemuDomainUndefineFlags(virDomainPtr dom, goto cleanup; } + if ((flags & VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL) && + virHashForEach(vm->snapshots.objs, qemuDomainSnapshotCountExternal, + &external) && + external) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("deletion of %d external disk snapshots not " + "supported yet"), external); + goto cleanup; + } + rem.driver = driver; rem.vm = vm; rem.metadata_only = !(flags & VIR_DOMAIN_UNDEFINE_SNAPSHOTS_FULL); @@ -9054,6 +9089,13 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto cleanup; } + if (snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("revert to external disk snapshot not supported " + "yet")); + goto cleanup; + } + if (vm->current_snapshot) { vm->current_snapshot->def->current = false; if (qemuDomainSnapshotWriteMetadata(vm, vm->current_snapshot, @@ -9273,6 +9315,7 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, struct snap_remove rem; struct snap_reparent rep; bool metadata_only = !!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY); + int external = 0; virCheckFlags(VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN | VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY | @@ -9295,6 +9338,22 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto cleanup; } + if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)) { + if (!(flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY) && + snap->def->state == VIR_DOMAIN_DISK_SNAPSHOT) + external++; + if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN) + virDomainSnapshotForEachDescendant(&vm->snapshots, snap, + qemuDomainSnapshotCountExternal, + &external); + if (external) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("deletion of %d external disk snapshots not " + "supported yet"), external); + goto cleanup; + } + } + if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; -- 1.7.4.4

Snapshots alter the set of disk image files opened by qemu, so they must be audited. But they don't involve a full disk definition structure, just the new filename. Make the next patch easier by refactoring the audit routines to just operate on file name. * src/conf/domain_audit.h (virDomainAuditDisk): Update prototype. * src/conf/domain_audit.c (virDomainAuditDisk): Act on strings, not definition structures. (virDomainAuditStart): Update caller. * src/qemu/qemu_hotplug.c (qemuDomainChangeEjectableMedia) (qemuDomainAttachPciDiskDevice, qemuDomainAttachSCSIDisk) (qemuDomainAttachUsbMassstorageDevice) (qemuDomainDetachPciDiskDevice, qemuDomainDetachDiskDevice): Likewise. --- src/conf/domain_audit.c | 12 ++++-------- src/conf/domain_audit.h | 4 ++-- src/qemu/qemu_hotplug.c | 18 +++++++++--------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/conf/domain_audit.c b/src/conf/domain_audit.c index 9d89c94..7d3eb5b 100644 --- a/src/conf/domain_audit.c +++ b/src/conf/domain_audit.c @@ -60,7 +60,7 @@ virDomainAuditGetRdev(const char *path ATTRIBUTE_UNUSED) void virDomainAuditDisk(virDomainObjPtr vm, - virDomainDiskDefPtr oldDef, virDomainDiskDefPtr newDef, + const char *oldDef, const char *newDef, const char *reason, bool success) { char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -80,15 +80,11 @@ virDomainAuditDisk(virDomainObjPtr vm, virt = "?"; } - if (!(oldsrc = virAuditEncode("old-disk", - oldDef && oldDef->src ? - oldDef->src : "?"))) { + if (!(oldsrc = virAuditEncode("old-disk", VIR_AUDIT_STR(oldDef)))) { VIR_WARN("OOM while encoding audit message"); goto cleanup; } - if (!(newsrc = virAuditEncode("new-disk", - newDef && newDef->src ? - newDef->src : "?"))) { + if (!(newsrc = virAuditEncode("new-disk", VIR_AUDIT_STR(newDef)))) { VIR_WARN("OOM while encoding audit message"); goto cleanup; } @@ -520,7 +516,7 @@ virDomainAuditStart(virDomainObjPtr vm, const char *reason, bool success) for (i = 0 ; i < vm->def->ndisks ; i++) { virDomainDiskDefPtr disk = vm->def->disks[i]; if (disk->src) /* Skips CDROM without media initially inserted */ - virDomainAuditDisk(vm, NULL, disk, "start", true); + virDomainAuditDisk(vm, NULL, disk->src, "start", true); } for (i = 0 ; i < vm->def->nfss ; i++) { diff --git a/src/conf/domain_audit.h b/src/conf/domain_audit.h index 0e88fd3..da9e257 100644 --- a/src/conf/domain_audit.h +++ b/src/conf/domain_audit.h @@ -35,8 +35,8 @@ void virDomainAuditStop(virDomainObjPtr vm, const char *reason) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); void virDomainAuditDisk(virDomainObjPtr vm, - virDomainDiskDefPtr oldDef, - virDomainDiskDefPtr newDef, + const char *oldDef, + const char *newDef, const char *reason, bool success) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(4); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index b7fdfa0..cd59283 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -114,7 +114,7 @@ int qemuDomainChangeEjectableMedia(struct qemud_driver *driver, } qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, origdisk, disk, "update", ret >= 0); + virDomainAuditDisk(vm, origdisk->src, disk->src, "update", ret >= 0); if (ret < 0) goto error; @@ -224,7 +224,7 @@ int qemuDomainAttachPciDiskDevice(struct qemud_driver *driver, } qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, NULL, disk, "attach", ret >= 0); + virDomainAuditDisk(vm, NULL, disk->src, "attach", ret >= 0); if (ret < 0) goto error; @@ -468,7 +468,7 @@ int qemuDomainAttachSCSIDisk(struct qemud_driver *driver, } qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, NULL, disk, "attach", ret >= 0); + virDomainAuditDisk(vm, NULL, disk->src, "attach", ret >= 0); if (ret < 0) goto error; @@ -560,7 +560,7 @@ int qemuDomainAttachUsbMassstorageDevice(struct qemud_driver *driver, } qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, NULL, disk, "attach", ret >= 0); + virDomainAuditDisk(vm, NULL, disk->src, "attach", ret >= 0); if (ret < 0) goto error; @@ -1277,14 +1277,14 @@ int qemuDomainDetachPciDiskDevice(struct qemud_driver *driver, if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE)) { if (qemuMonitorDelDevice(priv->mon, detach->info.alias) < 0) { qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, detach, NULL, "detach", false); + virDomainAuditDisk(vm, detach->src, NULL, "detach", false); goto cleanup; } } else { if (qemuMonitorRemovePCIDevice(priv->mon, &detach->info.addr.pci) < 0) { qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, detach, NULL, "detach", false); + virDomainAuditDisk(vm, detach->src, NULL, "detach", false); goto cleanup; } } @@ -1294,7 +1294,7 @@ int qemuDomainDetachPciDiskDevice(struct qemud_driver *driver, qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, detach, NULL, "detach", true); + virDomainAuditDisk(vm, detach->src, NULL, "detach", true); if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE) && qemuDomainPCIAddressReleaseSlot(priv->pciaddrs, @@ -1372,7 +1372,7 @@ int qemuDomainDetachDiskDevice(struct qemud_driver *driver, qemuDomainObjEnterMonitorWithDriver(driver, vm); if (qemuMonitorDelDevice(priv->mon, detach->info.alias) < 0) { qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, detach, NULL, "detach", false); + virDomainAuditDisk(vm, detach->src, NULL, "detach", false); goto cleanup; } @@ -1381,7 +1381,7 @@ int qemuDomainDetachDiskDevice(struct qemud_driver *driver, qemuDomainObjExitMonitorWithDriver(driver, vm); - virDomainAuditDisk(vm, detach, NULL, "detach", true); + virDomainAuditDisk(vm, detach->src, NULL, "detach", true); virDomainDiskRemove(vm->def, i); -- 1.7.4.4

I still need a good solution for snapshot-create-as to list multiple disk options. But this patch is sufficient to test the creation of disk snapshots using only default <disks> for snapshot-create-as, and snapshot-create provides full access to the snapshot process. * tools/virsh.c (cmdSnapshotCreate, cmdSnapshotCreateAs): Add --disk-only. * tools/virsh.pod (snapshot-create, snapshot-create-as): Document it. --- tools/virsh.c | 12 ++++++++++-- tools/virsh.pod | 20 +++++++++++++++----- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index fa80a52..8b522fc 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12269,6 +12269,7 @@ static const vshCmdOptDef opts_snapshot_create[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"xmlfile", VSH_OT_DATA, 0, N_("domain snapshot XML")}, {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")}, + {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")}, {NULL, 0, 0, NULL} }; @@ -12284,6 +12285,9 @@ cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool(cmd, "halt")) flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT; + if (vshCommandOptBool(cmd, "disk-only")) + flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; + if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12333,6 +12337,7 @@ static const vshCmdOptDef opts_snapshot_create_as[] = { {"description", VSH_OT_DATA, 0, N_("description of snapshot")}, {"print-xml", VSH_OT_BOOL, 0, N_("print XML document rather than create")}, {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")}, + {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")}, {NULL, 0, 0, NULL} }; @@ -12350,6 +12355,9 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool(cmd, "halt")) flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT; + if (vshCommandOptBool(cmd, "disk-only")) + flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; + if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12377,9 +12385,9 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) } if (vshCommandOptBool(cmd, "print-xml")) { - if (vshCommandOptBool(cmd, "halt")) { + if (flags) { vshError(ctl, "%s", - _("--print-xml and --halt are mutually exclusive")); + _("--print-xml does not work with --halt or --disk-only")); goto cleanup; } vshPrint(ctl, "%s\n", buffer); diff --git a/tools/virsh.pod b/tools/virsh.pod index b357330..a702520 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1605,26 +1605,36 @@ used to represent properties of snapshots. =over 4 -=item B<snapshot-create> I<domain> [I<xmlfile>] [I<--halt>] +=item B<snapshot-create> I<domain> [I<xmlfile>] [I<--halt>] [I<--disk-only>] Create a snapshot for domain I<domain> with the properties specified in I<xmlfile>. The only properties settable for a domain snapshot are the -<name> and <description>; the rest of the fields are ignored, and +<name> and <description>, as well as <disks> if I<--disk-only> is given; +the rest of the fields are ignored, and automatically filled in by libvirt. If I<xmlfile> is completely omitted, then libvirt will choose a value for all fields. If I<--halt> is specified, the domain will be left in an inactive state after the snapshot is created. -=item B<snapshot-create-as> I<domain> [{I<--print-xml> | I<--halt>}] -[I<name>] [I<description>] +If I<--disk-only> is specified, the snapshot will only include disk +state rather than the usual system checkpoint with vm state. Disk +snapshots are faster than full system checkpoints, but reverting to a +disk snapshot may require fsck or journal replays, since it is like +the disk state at the point when the power cord is abruptly pulled; +and mixing I<--halt> and I<--disk-only> loses any data that was not +flushed to disk at the time. + +=item B<snapshot-create-as> I<domain> {[I<--print-xml>] | [I<--halt>] +[I<--disk-only>]} [I<name>] [I<description>] Create a snapshot for domain I<domain> with the given <name> and <description>; if either value is omitted, libvirt will choose a value. If I<--print-xml> is specified, then XML appropriate for I<snapshot-create> is output, rather than actually creating a snapshot. Otherwise, if I<--halt> is specified, the domain will be left in an -inactive state after the snapshot is created. +inactive state after the snapshot is created, and if I<--disk-only> +is specified, the snapshot will not include vm state. =item B<snapshot-current> I<domain> [I<--name>] [I<--security-info>] -- 1.7.4.4

Expose the disk-only flag through virsh. Additionally, make virsh snapshot-create-as take an arbitrary number of diskspecs, which can be used to build up the xml for <domainsnapshot>. * tools/virsh.c (cmdSnapshotCreate): Add --disk-only. (cmdSnapshotCreateAs): Likewise, and add argv diskspec. (vshParseSnapshotDiskspec): New helper function. (vshCmddefGetOption): Allow naming of argv field. * tools/virsh.pod (snapshot-create, snapshot-create-as): Document them. * tests/virsh-optparse: Test snapshot-create-as parsing. --- I went ahead and implemented a decent solution to the snapshot-create-as needing to provide multiple disk specs when generating xml. tests/virsh-optparse | 20 +++++++++++ tools/virsh.c | 88 +++++++++++++++++++++++++++++++++++++++++++------ tools/virsh.pod | 28 ++++++++++++++-- 3 files changed, 122 insertions(+), 14 deletions(-) diff --git a/tests/virsh-optparse b/tests/virsh-optparse index 7b3a25d..cd5e3eb 100755 --- a/tests/virsh-optparse +++ b/tests/virsh-optparse @@ -67,6 +67,26 @@ for args in \ virsh -d0 -c $test_url setvcpus $args >out 2>>err || fail=1 LC_ALL=C sort out | compare - exp-out || fail=1 done + +# Another complex parsing example +cat <<\EOF > exp-out || framework_failure +<domainsnapshot> + <description>1<2</description> + <disks> + <disk name='vda' snapshot='external'> + <source file='a&b,c'/> + </disk> + <disk name='vdb'/> + </disks> +</domainsnapshot> + + +EOF +virsh -c $test_url snapshot-create-as --print-xml test \ + --diskspec 'vda,file=a&b,,c,snapshot=external' --description '1<2' \ + --diskspec vdb >out 2>>err || fail=1 +compare out exp-out || fail=1 + test -s err && fail=1 (exit $fail); exit $fail diff --git a/tools/virsh.c b/tools/virsh.c index 648e62c..c430cfb 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -12153,6 +12153,7 @@ static const vshCmdOptDef opts_snapshot_create[] = { {"current", VSH_OT_BOOL, 0, N_("with redefine, set current snapshot")}, {"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")}, {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")}, + {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")}, {NULL, 0, 0, NULL} }; @@ -12173,6 +12174,8 @@ cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd) flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA; if (vshCommandOptBool(cmd, "halt")) flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT; + if (vshCommandOptBool(cmd, "disk-only")) + flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12211,6 +12214,62 @@ cleanup: /* * "snapshot-create-as" command */ +static int +vshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const char *str) +{ + int ret = -1; + char *name = NULL; + char *snapshot = NULL; + char *driver = NULL; + char *file = NULL; + char *spec = vshStrdup(ctl, str); + char *tmp = spec; + size_t len = strlen(str); + + if (*str == ',') + goto cleanup; + name = tmp; + while ((tmp = strchr(tmp, ','))) { + if (tmp[1] == ',') { + /* Recognize ,, as an escape for a literal comma */ + memmove(&tmp[1], &tmp[2], len - (tmp - spec) + 2); + len--; + tmp++; + continue; + } + /* Terminate previous string, look for next recognized one */ + *tmp++ = '\0'; + if (!snapshot && STRPREFIX(tmp, "snapshot=")) + snapshot = tmp + strlen("snapshot="); + else if (!driver && STRPREFIX(tmp, "driver=")) + driver = tmp + strlen("driver="); + else if (!file && STRPREFIX(tmp, "file=")) + file = tmp + strlen("file="); + else + goto cleanup; + } + + virBufferEscapeString(buf, " <disk name='%s'", name); + if (snapshot) + virBufferAsprintf(buf, " snapshot='%s'", snapshot); + if (driver || file) { + virBufferAddLit(buf, ">\n"); + if (driver) + virBufferAsprintf(buf, " <driver type='%s'/>\n", driver); + if (file) + virBufferEscapeString(buf, " <source file='%s'/>\n", file); + virBufferAddLit(buf, " </disk>\n"); + } else { + virBufferAddLit(buf, "/>\n"); + } + ret = 0; +cleanup: + if (ret < 0) + vshError(ctl, _("unable to parse diskspec: %s"), str); + VIR_FREE(spec); + return ret; +} + static const vshCmdInfo info_snapshot_create_as[] = { {"help", N_("Create a snapshot from a set of args")}, {"desc", N_("Create a snapshot (disk and RAM) from arguments")}, @@ -12224,6 +12283,9 @@ static const vshCmdOptDef opts_snapshot_create_as[] = { {"print-xml", VSH_OT_BOOL, 0, N_("print XML document rather than create")}, {"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")}, {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")}, + {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")}, + {"diskspec", VSH_OT_ARGV, 0, + N_("disk attributes: disk[,snapshot=type][,driver=type][,file=name]")}, {NULL, 0, 0, NULL} }; @@ -12237,11 +12299,14 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) const char *desc = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; unsigned int flags = 0; + const vshCmdOpt *opt = NULL; if (vshCommandOptBool(cmd, "no-metadata")) flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA; if (vshCommandOptBool(cmd, "halt")) flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT; + if (vshCommandOptBool(cmd, "disk-only")) + flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY; if (!vshConnectionUsability(ctl, ctl->conn)) goto cleanup; @@ -12261,6 +12326,16 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) virBufferEscapeString(&buf, " <name>%s</name>\n", name); if (desc) virBufferEscapeString(&buf, " <description>%s</description>\n", desc); + if (vshCommandOptBool(cmd, "diskspec")) { + virBufferAddLit(&buf, " <disks>\n"); + while ((opt = vshCommandOptArgv(cmd, opt))) { + if (vshParseSnapshotDiskspec(ctl, &buf, opt->data) < 0) { + virBufferFreeAndReset(&buf); + goto cleanup; + } + } + virBufferAddLit(&buf, " </disks>\n"); + } virBufferAddLit(&buf, "</domainsnapshot>\n"); buffer = virBufferContentAndReset(&buf); @@ -12270,11 +12345,6 @@ cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd) } if (vshCommandOptBool(cmd, "print-xml")) { - if (vshCommandOptBool(cmd, "halt")) { - vshError(ctl, "%s", - _("--print-xml and --halt are mutually exclusive")); - goto cleanup; - } vshPrint(ctl, "%s\n", buffer); ret = true; goto cleanup; @@ -13327,12 +13397,8 @@ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name, vshError(ctl, _("option --%s already seen"), name); return NULL; } - if (opt->type == VSH_OT_ARGV) { - vshError(ctl, _("variable argument <%s> " - "should not be used with --<%s>"), name, name); - return NULL; - } - *opts_seen |= 1 << i; + if (opt->type != VSH_OT_ARGV) + *opts_seen |= 1 << i; return opt; } } diff --git a/tools/virsh.pod b/tools/virsh.pod index 0d1ddd1..e0b9bef 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1706,11 +1706,12 @@ used to represent properties of snapshots. =over 4 =item B<snapshot-create> I<domain> [I<xmlfile>] {[I<--redefine> [I<--current>]] -| [I<--no-metadata>] [I<--halt>]} +| [I<--no-metadata>] [I<--halt>] [I<--disk-only>]} Create a snapshot for domain I<domain> with the properties specified in I<xmlfile>. Normally, the only properties settable for a domain snapshot -are the <name> and <description> elements; the rest of the fields are +are the <name> and <description> elements, as well as <disks> if +I<--disk-only> is given; the rest of the fields are ignored, and automatically filled in by libvirt. If I<xmlfile> is completely omitted, then libvirt will choose a value for all fields. The new snapshot will become current, as listed by B<snapshot-current>. @@ -1718,6 +1719,14 @@ The new snapshot will become current, as listed by B<snapshot-current>. If I<--halt> is specified, the domain will be left in an inactive state after the snapshot is created. +If I<--disk-only> is specified, the snapshot will only include disk +state rather than the usual system checkpoint with vm state. Disk +snapshots are faster than full system checkpoints, but reverting to a +disk snapshot may require fsck or journal replays, since it is like +the disk state at the point when the power cord is abruptly pulled; +and mixing I<--halt> and I<--disk-only> loses any data that was not +flushed to disk at the time. + If I<--redefine> is specified, then all XML elements produced by B<snapshot-dumpxml> are valid; this can be used to migrate snapshot hierarchy from one machine to another, to recreate hierarchy for the @@ -1741,13 +1750,26 @@ by command such as B<destroy> or by internal guest action). =item B<snapshot-create-as> I<domain> {[I<--print-xml>] | [I<--no-metadata>] [I<--halt>]} [I<name>] [I<description>] +[I<--disk-only> [I<diskspec>]...] Create a snapshot for domain I<domain> with the given <name> and <description>; if either value is omitted, libvirt will choose a value. If I<--print-xml> is specified, then XML appropriate for I<snapshot-create> is output, rather than actually creating a snapshot. Otherwise, if I<--halt> is specified, the domain will be left in an -inactive state after the snapshot is created. +inactive state after the snapshot is created, and if I<--disk-only> +is specified, the snapshot will not include vm state. + +The I<--disk-only> flag is used to request a disk-only snapshot. When +this flag is in use, the command can also take additional I<diskspec> +arguments to add <disk> elements to the xml. Each <diskspec> is in the +form B<disk[,snapshot=type][,driver=type][,file=name]>. To include a +literal comma in B<disk> or in B<file=name>, escape it with a second +comma. For example, a diskspec of "vda,snapshot=external,file=/path/to,,new" +results in the following XML: + <disk name='vda' snapshot='external'> + <source file='/path/to,new'/> + </disk> If I<--no-metadata> is specified, then the snapshot data is created, but any metadata is immediately discarded (that is, libvirt does not -- 1.7.4.4

Lots of earlier patches led up to this point - the qemu snapshot_blkdev monitor command can now be controlled by libvirt! Well, insofar as SELinux doesn't prevent qemu from open(O_CREAT) on the files. There's still some followup work before things work with SELinux enforcing, but this patch is big enough to post now. There's still room for other improvements, too (for example, taking a disk snapshot of an inactive domain, by using qemu-img for both internal and external snapshots; wiring up delete and revert control, including additional flags from my RFC; supporting active QED disk snapshots; supporting per-storage-volume snapshots such as LVM or btrfs snapshots; etc.). But this patch is the one that proves the new XML works! * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateXML): Wire in active disk snapshots. (qemuDomainSnapshotDiskPrepare) (qemuDomainSnapshotCreateDiskActive) (qemuDomainSnapshotCreateSingleDiskActive): New functions. --- src/qemu/qemu_driver.c | 290 +++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 274 insertions(+), 16 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 5652e62..673d751 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -8700,6 +8700,241 @@ cleanup: return ret; } +static int +qemuDomainSnapshotDiskPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def) +{ + int ret = -1; + int i; + bool found = false; + bool active = virDomainObjIsActive(vm); + struct stat st; + + for (i = 0; i < def->ndisks; i++) { + virDomainSnapshotDiskDefPtr disk = &def->disks[i]; + + switch (disk->snapshot) { + case VIR_DOMAIN_DISK_SNAPSHOT_INTERNAL: + if (active) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("active qemu domains require external disk " + "snapshots; disk %s requested internal"), + disk->name); + goto cleanup; + } + if (!vm->def->disks[i]->driverType || + STRNEQ(vm->def->disks[i]->driverType, "qcow2")) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("internal snapshot for disk %s unsupported " + "for storage type %s"), + disk->name, + NULLSTR(vm->def->disks[i]->driverType)); + goto cleanup; + } + found = true; + break; + + case VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL: + if (!disk->driverType) { + if (!(disk->driverType = strdup("qcow2"))) { + virReportOOMError(); + goto cleanup; + } + } else if (STRNEQ(disk->driverType, "qcow2")) { + /* XXX We should also support QED */ + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("external snapshot format for disk %s " + "is unsupported: %s"), + disk->name, disk->driverType); + goto cleanup; + } + if (stat(disk->file, &st) < 0) { + if (errno != ENOENT) { + virReportSystemError(errno, + _("unable to stat for disk %s: %s"), + disk->name, disk->file); + goto cleanup; + } + } else if (!S_ISBLK(st.st_mode)) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("external snapshot file for disk %s already " + "exists and is not a block device: %s"), + disk->name, disk->file); + goto cleanup; + } + found = true; + break; + + case VIR_DOMAIN_DISK_SNAPSHOT_NO: + break; + + case VIR_DOMAIN_DISK_SNAPSHOT_DEFAULT: + default: + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unexpected code path")); + goto cleanup; + } + } + + if (!found) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk snapshots require at least one disk to be " + "selected for snapshot")); + goto cleanup; + } + + ret = 0; + +cleanup: + return ret; +} + +/* The domain is expected to hold monitor lock. */ +static int +qemuDomainSnapshotCreateSingleDiskActive(virDomainObjPtr vm, + virDomainSnapshotDiskDefPtr snap, + virDomainDiskDefPtr disk) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + char *device = NULL; + char *source = NULL; + char *driverType = NULL; + int ret = -1; + + if (snap->snapshot != VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unexpected code path")); + return -1; + } + + if (virAsprintf(&device, "drive-%s", disk->info.alias) < 0 || + !(source = strdup(snap->file)) || + (STRNEQ_NULLABLE(disk->driverType, "qcow2") && + !(driverType = strdup("qcow2")))) { + virReportOOMError(); + goto cleanup; + } + + /* XXX create new file and set selinux labels */ + ret = qemuMonitorDiskSnapshot(priv->mon, device, source); + virDomainAuditDisk(vm, disk->src, source, "snapshot", ret >= 0); + if (ret < 0) + goto cleanup; + + /* Update vm in place to match changes. */ + VIR_FREE(disk->src); + disk->src = source; + source = NULL; + if (driverType) { + VIR_FREE(disk->driverType); + disk->driverType = driverType; + driverType = NULL; + } + + /* XXX Do we also need to update vm->newDef if there are pending + * configuration changes awaiting the next boot? */ + +cleanup: + VIR_FREE(device); + VIR_FREE(source); + VIR_FREE(driverType); + return ret; +} + +/* The domain is expected to be locked and active. */ +static int +qemuDomainSnapshotCreateDiskActive(virConnectPtr conn, + struct qemud_driver *driver, + virDomainObjPtr *vmptr, + virDomainSnapshotObjPtr snap, + unsigned int flags) +{ + virDomainObjPtr vm = *vmptr; + bool resume = false; + int ret = -1; + int i; + + if (qemuDomainObjBeginJobWithDriver(driver, vm, QEMU_JOB_MODIFY) < 0) + return -1; + + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { + /* In qemu, snapshot_blkdev on a single disk will pause cpus, + * but this confuses libvirt since notifications are not given + * when qemu resumes. And for multiple disks, libvirt must + * pause externally to get all snapshots to be at the same + * point in time. For simplicitly, we always pause ourselves + * rather than relying on qemu doing pause. + */ + if (qemuProcessStopCPUs(driver, vm, VIR_DOMAIN_PAUSED_SAVE, + QEMU_ASYNC_JOB_NONE) < 0) + goto cleanup; + + resume = true; + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("guest unexpectedly quit")); + goto cleanup; + } + } + + /* No way to roll back if first disk succeeds but later disks + * fail. Based on earlier qemuDomainSnapshotDiskPrepare, all + * disks in this list are now either SNAPSHOT_NO, or + * SNAPSHOT_EXTERNAL with a valid file name and qcow2 format. */ + qemuDomainObjEnterMonitorWithDriver(driver, vm); + for (i = 0; i < snap->def->ndisks; i++) { + if (snap->def->disks[i].snapshot == VIR_DOMAIN_DISK_SNAPSHOT_NO) + continue; + + ret = qemuDomainSnapshotCreateSingleDiskActive(vm, + &snap->def->disks[i], + vm->def->disks[i]); + if (ret < 0) + break; + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + if (ret < 0) + goto cleanup; + + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT) { + virDomainEventPtr event; + + event = virDomainEventNewFromObj(vm, VIR_DOMAIN_EVENT_STOPPED, + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT); + qemuProcessStop(driver, vm, 0, VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT); + virDomainAuditStop(vm, "from-snapshot"); + /* We already filtered the _HALT flag for persistent domains + * only, so this end job never drops the last reference. */ + ignore_value(qemuDomainObjEndJob(driver, vm)); + resume = false; + vm = NULL; + if (event) + qemuDomainEventQueue(driver, event); + } + +cleanup: + if (resume && virDomainObjIsActive(vm) && + qemuProcessStartCPUs(driver, vm, conn, + VIR_DOMAIN_RUNNING_UNPAUSED, + QEMU_ASYNC_JOB_NONE) < 0 && + virGetLastError() == NULL) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("resuming after snapshot failed")); + } + + if (vm) { + if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0) + ret = -1; + if (qemuDomainObjEndJob(driver, vm) == 0) { + /* Only possible if a transient vm quit while our locks were down, + * in which case we don't want to save snapshot metadata. */ + *vmptr = NULL; + ret = -1; + } + } + + return ret; +} + static virDomainSnapshotPtr qemuDomainSnapshotCreateXML(virDomainPtr domain, const char *xmlDesc, @@ -8713,7 +8948,8 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, char uuidstr[VIR_UUID_STRING_BUFLEN]; virDomainSnapshotDefPtr def = NULL; - virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_HALT, NULL); + virCheckFlags(VIR_DOMAIN_SNAPSHOT_CREATE_HALT | + VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY, NULL); qemuDriverLock(driver); virUUIDFormat(domain->uuid, uuidstr); @@ -8742,27 +8978,39 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, VIR_DOMAIN_XML_INACTIVE))) goto cleanup; - /* in a perfect world, we would allow qemu to tell us this. The problem - * is that qemu only does this check device-by-device; so if you had a - * domain that booted from a large qcow2 device, but had a secondary raw - * device attached, you wouldn't find out that you can't snapshot your - * guest until *after* it had spent the time to snapshot the boot device. - * This is probably a bug in qemu, but we'll work around it here for now. - */ - if (!qemuDomainSnapshotIsAllowed(vm)) - goto cleanup; + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { + if (virDomainSnapshotAlignDisks(def, VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL, + false) < 0) + goto cleanup; + if (qemuDomainSnapshotDiskPrepare(vm, def) < 0) + goto cleanup; + def->state = VIR_DOMAIN_DISK_SNAPSHOT; + } else { + /* In a perfect world, we would allow qemu to tell us this. + * The problem is that qemu only does this check + * device-by-device; so if you had a domain that booted from a + * large qcow2 device, but had a secondary raw device + * attached, you wouldn't find out that you can't snapshot + * your guest until *after* it had spent the time to snapshot + * the boot device. This is probably a bug in qemu, but we'll + * work around it here for now. + */ + if (!qemuDomainSnapshotIsAllowed(vm)) + goto cleanup; - if (def->ndisks) { - qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("disk snapshots not supported yet")); - goto cleanup; + if (def->ndisks) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk snapshots require use of " + "VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY")); + goto cleanup; + } + def->state = virDomainObjGetState(vm, NULL); } if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) goto cleanup; def = NULL; - snap->def->state = virDomainObjGetState(vm, NULL); snap->def->current = true; if (vm->current_snapshot) { snap->def->parent = strdup(vm->current_snapshot->def->name); @@ -8778,7 +9026,17 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, } /* actually do the snapshot */ - if (!virDomainObjIsActive(vm)) { + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { + if (!virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("disk snapshots of inactive domains not " + "implemented yet")); + goto cleanup; + } + if (qemuDomainSnapshotCreateDiskActive(domain->conn, driver, + &vm, snap, flags) < 0) + goto cleanup; + } else if (!virDomainObjIsActive(vm)) { if (qemuDomainSnapshotCreateInactive(driver, vm, snap) < 0) goto cleanup; } else { -- 1.7.4.4

In a SELinux or root-squashing NFS environment, libvirt has to go through some hoops to create a new file that qemu can then open() by name. Snapshots are a case where we want to guarantee an empty file that qemu can open, so refactor some existing code to make it easier to reuse in the next patch. * src/qemu/qemu_driver.c (qemuOpenFile): New function, pulled from... (qemuDomainSaveInternal): ...here. --- src/qemu/qemu_driver.c | 228 ++++++++++++++++++++++++++---------------------- 1 files changed, 123 insertions(+), 105 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 673d751..48313ad 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2450,6 +2450,125 @@ qemuCompressProgramName(int compress) qemudSaveCompressionTypeToString(compress)); } +/* Internal function to properly create or open existing files, with + * ownership affected by qemu driver setup. */ +static int +qemuOpenFile(struct qemud_driver *driver, const char *path, bool *isReg, + bool *bypassSecurityDriver, bool bypassCache) +{ + struct stat sb; + bool is_reg; + bool bypass_security = false; + int fd = -1; + uid_t uid = getuid(); + gid_t gid = getgid(); + int directFlag = 0; + + /* path might be a pre-existing block dev, in which case + * we need to skip the create step, and also avoid unlink + * in the failure case */ + if (stat(path, &sb) < 0) { + /* Avoid throwing an error here, since it is possible + * that with NFS we can't actually stat() the file. + * The subsequent codepaths will still raise an error + * if a truly fatal problem is hit */ + is_reg = true; + } else { + is_reg = !!S_ISREG(sb.st_mode); + /* If the path is regular file which exists + * already and dynamic_ownership is off, we don't + * want to change it's ownership, just open it as-is */ + if (is_reg && !driver->dynamicOwnership) { + uid = sb.st_uid; + gid = sb.st_gid; + } + } + + /* First try creating the file as root */ + if (bypassCache) { + directFlag = virFileDirectFdFlag(); + if (directFlag < 0) { + qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("bypass cache unsupported by this system")); + goto cleanup; + } + } + if (!is_reg) { + fd = open(path, O_WRONLY | O_TRUNC | directFlag); + if (fd < 0) { + virReportSystemError(errno, _("unable to open %s"), path); + goto cleanup; + } + } else { + int oflags = O_CREAT | O_TRUNC | O_WRONLY | directFlag; + if ((fd = virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, + uid, gid, 0)) < 0) { + /* If we failed as root, and the error was permission-denied + (EACCES or EPERM), assume it's on a network-connected share + where root access is restricted (eg, root-squashed NFS). If the + qemu user (driver->user) is non-root, just set a flag to + bypass security driver shenanigans, and retry the operation + after doing setuid to qemu user */ + if ((fd != -EACCES && fd != -EPERM) || + driver->user == getuid()) { + virReportSystemError(-fd, + _("Failed to create file '%s'"), + path); + goto cleanup; + } + + /* On Linux we can also verify the FS-type of the directory. */ + switch (virStorageFileIsSharedFS(path)) { + case 1: + /* it was on a network share, so we'll continue + * as outlined above + */ + break; + + case -1: + virReportSystemError(errno, + _("Failed to create file " + "'%s': couldn't determine fs type"), + path); + goto cleanup; + + case 0: + default: + /* local file - log the error returned by virFileOpenAs */ + virReportSystemError(-fd, + _("Failed to create file '%s'"), + path); + goto cleanup; + } + + /* Retry creating the file as driver->user */ + + if ((fd = virFileOpenAs(path, oflags, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, + driver->user, driver->group, + VIR_FILE_OPEN_AS_UID)) < 0) { + virReportSystemError(-fd, + _("Error from child process creating '%s'"), + path); + goto cleanup; + } + + /* Since we had to setuid to create the file, and the fstype + is NFS, we assume it's a root-squashing NFS share, and that + the security driver stuff would have failed anyway */ + + bypass_security = true; + } + } +cleanup: + if (isReg) + *isReg = is_reg; + if (bypassSecurityDriver) + *bypassSecurityDriver = bypass_security; + + return fd; +} + /* This internal function expects the driver lock to already be held on * entry and the vm must be active + locked. Vm will be unlocked and * potentially free'd after this returns (eg transient VMs are freed @@ -2468,15 +2587,11 @@ qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, int rc; virDomainEventPtr event = NULL; qemuDomainObjPrivatePtr priv; - struct stat sb; bool is_reg = false; size_t len; unsigned long long offset; unsigned long long pad; int fd = -1; - uid_t uid = getuid(); - gid_t gid = getgid(); - int directFlag = 0; virFileDirectFdPtr directFd = NULL; memset(&header, 0, sizeof(header)); @@ -2557,107 +2672,10 @@ qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, header.xml_len = len; /* Obtain the file handle. */ - /* path might be a pre-existing block dev, in which case - * we need to skip the create step, and also avoid unlink - * in the failure case */ - if (stat(path, &sb) < 0) { - /* Avoid throwing an error here, since it is possible - * that with NFS we can't actually stat() the file. - * The subsequent codepaths will still raise an error - * if a truly fatal problem is hit */ - is_reg = true; - } else { - is_reg = !!S_ISREG(sb.st_mode); - /* If the path is regular file which exists - * already and dynamic_ownership is off, we don't - * want to change it's ownership, just open it as-is */ - if (is_reg && !driver->dynamicOwnership) { - uid=sb.st_uid; - gid=sb.st_gid; - } - } - - /* First try creating the file as root */ - if (bypass_cache) { - directFlag = virFileDirectFdFlag(); - if (directFlag < 0) { - qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("bypass cache unsupported by this system")); - goto endjob; - } - } - if (!is_reg) { - fd = open(path, O_WRONLY | O_TRUNC | directFlag); - if (fd < 0) { - virReportSystemError(errno, _("unable to open %s"), path); - goto endjob; - } - } else { - int oflags = O_CREAT | O_TRUNC | O_WRONLY | directFlag; - if ((fd = virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, - uid, gid, 0)) < 0) { - /* If we failed as root, and the error was permission-denied - (EACCES or EPERM), assume it's on a network-connected share - where root access is restricted (eg, root-squashed NFS). If the - qemu user (driver->user) is non-root, just set a flag to - bypass security driver shenanigans, and retry the operation - after doing setuid to qemu user */ - rc = fd; - if (((rc != -EACCES) && (rc != -EPERM)) || - driver->user == getuid()) { - virReportSystemError(-rc, - _("Failed to create domain save file '%s'"), - path); - goto endjob; - } - - /* On Linux we can also verify the FS-type of the directory. */ - switch (virStorageFileIsSharedFS(path)) { - case 1: - /* it was on a network share, so we'll continue - * as outlined above - */ - break; - - case -1: - virReportSystemError(errno, - _("Failed to create domain save file " - "'%s': couldn't determine fs type"), - path); - goto endjob; - break; - - case 0: - default: - /* local file - log the error returned by virFileOpenAs */ - virReportSystemError(-rc, - _("Failed to create domain save file '%s'"), - path); - goto endjob; - break; - - } - - /* Retry creating the file as driver->user */ - - if ((fd = virFileOpenAs(path, oflags, - S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, - driver->user, driver->group, - VIR_FILE_OPEN_AS_UID)) < 0) { - virReportSystemError(-fd, - _("Error from child process creating '%s'"), - path); - goto endjob; - } - - /* Since we had to setuid to create the file, and the fstype - is NFS, we assume it's a root-squashing NFS share, and that - the security driver stuff would have failed anyway */ - - bypassSecurityDriver = true; - } - } - + fd = qemuOpenFile(driver, path, &is_reg, &bypassSecurityDriver, + bypass_cache); + if (fd < 0) + goto endjob; if (bypass_cache && (directFd = virFileDirectFdNew(&fd, path)) == NULL) goto endjob; -- 1.7.4.4

On 08/24/2011 09:22 AM, Eric Blake wrote:
In a SELinux or root-squashing NFS environment, libvirt has to go through some hoops to create a new file that qemu can then open() by name. Snapshots are a case where we want to guarantee an empty file that qemu can open, so refactor some existing code to make it easier to reuse in the next patch.
Redone here: https://www.redhat.com/archives/libvir-list/2011-August/msg01458.html -- Eric Blake eblake@redhat.com +1-801-349-2682 Libvirt virtualization library http://libvirt.org

With this, it is now possible to create external snapshots even when SELinux is enforcing, and to protect the new file with a lock manager. * src/qemu/qemu_driver.c (qemuDomainSnapshotCreateSingleDiskActive): Create and register new file with proper permissions and locks. (qemuDomainSnapshotCreateDiskActive): Update caller. --- src/qemu/qemu_driver.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 48313ad..b049252 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -87,6 +87,7 @@ #include "configmake.h" #include "threadpool.h" #include "locking/lock_manager.h" +#include "locking/domain_lock.h" #include "virkeycode.h" #define VIR_FROM_THIS VIR_FROM_QEMU @@ -8808,7 +8809,8 @@ cleanup: /* The domain is expected to hold monitor lock. */ static int -qemuDomainSnapshotCreateSingleDiskActive(virDomainObjPtr vm, +qemuDomainSnapshotCreateSingleDiskActive(struct qemud_driver *driver, + virDomainObjPtr vm, virDomainSnapshotDiskDefPtr snap, virDomainDiskDefPtr disk) { @@ -8817,6 +8819,9 @@ qemuDomainSnapshotCreateSingleDiskActive(virDomainObjPtr vm, char *source = NULL; char *driverType = NULL; int ret = -1; + int fd = -1; + char *origsrc = NULL; + char *origdriver = NULL; if (snap->snapshot != VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL) { qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s", @@ -8832,7 +8837,33 @@ qemuDomainSnapshotCreateSingleDiskActive(virDomainObjPtr vm, goto cleanup; } - /* XXX create new file and set selinux labels */ + /* create the stub file and set selinux labels; manipulate disk in + * place, in a way that can be reverted on failure. */ + fd = qemuOpenFile(driver, source, NULL, NULL, false); + if (fd < 0) + goto cleanup; + VIR_FORCE_CLOSE(fd); + + origsrc = disk->src; + disk->src = source; + origdriver = disk->driverType; + disk->driverType = driverType; + + if (virDomainLockDiskAttach(driver->lockManager, vm, disk) < 0) + goto cleanup; + if (virSecurityManagerSetImageLabel(driver->securityManager, vm, + disk) < 0) { + if (virDomainLockDiskDetach(driver->lockManager, vm, disk) < 0) + VIR_WARN("Unable to release lock on %s", source); + goto cleanup; + } + + disk->src = origsrc; + origsrc = NULL; + disk->driverType = origdriver; + origdriver = NULL; + + /* create the actual snapshot */ ret = qemuMonitorDiskSnapshot(priv->mon, device, source); virDomainAuditDisk(vm, disk->src, source, "snapshot", ret >= 0); if (ret < 0) @@ -8852,6 +8883,10 @@ qemuDomainSnapshotCreateSingleDiskActive(virDomainObjPtr vm, * configuration changes awaiting the next boot? */ cleanup: + if (origsrc) { + disk->src = origsrc; + disk->driverType = origdriver; + } VIR_FREE(device); VIR_FREE(source); VIR_FREE(driverType); @@ -8903,7 +8938,7 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn, if (snap->def->disks[i].snapshot == VIR_DOMAIN_DISK_SNAPSHOT_NO) continue; - ret = qemuDomainSnapshotCreateSingleDiskActive(vm, + ret = qemuDomainSnapshotCreateSingleDiskActive(driver, vm, &snap->def->disks[i], vm->def->disks[i]); if (ret < 0) -- 1.7.4.4
participants (2)
-
Daniel P. Berrange
-
Eric Blake