[libvirt] [PATCH 0/4] Preparation for external live snapshot

I've determined that it is possible to mix migration to file with disk snapshots on existing qemu 1.1 in such a way to take a live system checkpoint snapshot of a system without the downtime of either 'virsh snapshot-create' or 'virsh save'; without the loss in disk state of 'virsh save'; and without the fsck penalties of 'virsh snapshot-create --disk-only'; basically by combining the best of those three approaches. I'm also quite tired of 'virsh snapshot-create --disk-only' failing for an offline domain, and have upcoming patches to drive qemu-img to do that for an offline image. Unfortunately, qemu 1.2 missed out on adding the 'drive-mirror' or 'block-commit' commands, so I still can't do quite everything I want with snapshots (in particular, I can't preserve the original filename; although you can do a snapshot/blockpull/snapshot to get back to the original filename with twice the work). But I think this series still leaves room for future enhancements as future qemu provides the means. Although I'm still in the middle of polishing the src/qemu patches, I'd at least like to post this series of prep-work to document how I plan to expose it all, and to make sure my design decisions are on track. The first three can be applied now, the fourth should probably not be applied until I actually have later patches using the new XML, hopefully still in time for 0.10.0. Eric Blake (4): snapshot: make virDomainSnapshotObjList opaque snapshot: split snapshot conf code into own file snapshot: rename an enum snapshot: new XML for external system checkpoint docs/formatsnapshot.html.in | 11 + docs/schemas/domainsnapshot.rng | 23 + po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/conf/domain_conf.c | 933 +----------------- src/conf/domain_conf.h | 143 +-- src/conf/snapshot_conf.c | 1021 ++++++++++++++++++++ src/conf/snapshot_conf.h | 160 +++ src/esx/esx_driver.c | 1 + src/libvirt_private.syms | 5 +- src/qemu/qemu_command.c | 1 + src/qemu/qemu_domain.c | 7 +- src/qemu/qemu_domain.h | 3 +- src/qemu/qemu_driver.c | 69 +- src/qemu/qemu_migration.c | 2 +- src/vbox/vbox_tmpl.c | 1 + tests/domainsnapshotxml2xmlin/external_vm.xml | 10 + tests/domainsnapshotxml2xmlin/noparent.xml | 9 + tests/domainsnapshotxml2xmlout/all_parameters.xml | 1 + tests/domainsnapshotxml2xmlout/disk_snapshot.xml | 1 + tests/domainsnapshotxml2xmlout/external_vm.xml | 43 + tests/domainsnapshotxml2xmlout/full_domain.xml | 1 + tests/domainsnapshotxml2xmlout/metadata.xml | 1 + tests/domainsnapshotxml2xmlout/noparent.xml | 1 + .../noparent_nodescription.xml | 1 + .../noparent_nodescription_noactive.xml | 1 + tests/domainsnapshotxml2xmltest.c | 1 + 27 files changed, 1365 insertions(+), 1089 deletions(-) create mode 100644 src/conf/snapshot_conf.c create mode 100644 src/conf/snapshot_conf.h create mode 100644 tests/domainsnapshotxml2xmlin/external_vm.xml create mode 100644 tests/domainsnapshotxml2xmlin/noparent.xml create mode 100644 tests/domainsnapshotxml2xmlout/external_vm.xml -- 1.7.11.2

We were failing to react to allocation failure when initializing a snapshot object list. Changing things to store a pointer instead of a complete object adds one more possible point of allocation failure, but at the same time, will make it easier to react to failure now, as well as making it easier for a future patch to split all virDomainSnapshotPtr handling into a separate file, as I continue to add even more snapshot code. Luckily, there was only one client outside of domain_conf.c that was actually peeking inside the object, and a new wrapper function was easy. * src/conf/domain_conf.h (_virDomainObj): Use a pointer. (virDomainSnapshotObjListInit): Rename. (virDomainSnapshotObjListFree, virDomainSnapshotForEach): New declarations. (_virDomainSnapshotObjList): Move definitions... * src/conf/domain_conf.c: ...here. (virDomainSnapshotObjListInit, virDomainSnapshotObjListDeinit): Rename... (virDomainSnapshotObjListNew, virDomainSnapshotObjListFree): ...to these. (virDomainSnapshotForEach): New function. (virDomainObjDispose, virDomainListPopulate): Adjust callers. * src/qemu/qemu_domain.c (qemuDomainSnapshotDiscard) (qemuDomainSnapshotDiscardAllMetadata): Likewise. * src/qemu/qemu_migration.c (qemuMigrationIsAllowed): Likewise. * src/qemu/qemu_driver.c (qemuDomainSnapshotLoad) (qemuDomainUndefineFlags, qemuDomainSnapshotCreateXML) (qemuDomainSnapshotListNames, qemuDomainSnapshotNum) (qemuDomainListAllSnapshots) (qemuDomainSnapshotListChildrenNames) (qemuDomainSnapshotNumChildren) (qemuDomainSnapshotListAllChildren) (qemuDomainSnapshotLookupByName, qemuDomainSnapshotGetParent) (qemuDomainSnapshotGetXMLDesc, qemuDomainSnapshotIsCurrent) (qemuDomainSnapshotHasMetadata, qemuDomainRevertToSnapshot) (qemuDomainSnapshotDelete): Likewise. * src/libvirt_private.syms (domain_conf.h): Export new function. --- src/conf/domain_conf.c | 72 ++++++++++++++++++++++++++++++++--------------- src/conf/domain_conf.h | 14 ++++----- src/libvirt_private.syms | 1 + src/qemu/qemu_domain.c | 7 +++-- src/qemu/qemu_driver.c | 50 ++++++++++++++++---------------- src/qemu/qemu_migration.c | 2 +- 6 files changed, 87 insertions(+), 59 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 851284a..0c6671c 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -655,6 +655,15 @@ VIR_ENUM_IMPL(virDomainNumatuneMemPlacementMode, #define VIR_DOMAIN_XML_WRITE_FLAGS VIR_DOMAIN_XML_SECURE #define VIR_DOMAIN_XML_READ_FLAGS VIR_DOMAIN_XML_INACTIVE +struct _virDomainSnapshotObjList { + /* name string -> virDomainSnapshotObj mapping + * for O(1), lockless lookup-by-name */ + virHashTable *objs; + + virDomainSnapshotObj metaroot; /* Special parent of all root snapshots */ +}; + + static virClassPtr virDomainObjClass; static void virDomainObjDispose(void *obj); @@ -1644,8 +1653,6 @@ void virDomainDefFree(virDomainDefPtr def) VIR_FREE(def); } -static void virDomainSnapshotObjListDeinit(virDomainSnapshotObjListPtr snapshots); - static void virDomainObjDispose(void *obj) { virDomainObjPtr dom = obj; @@ -1659,7 +1666,7 @@ static void virDomainObjDispose(void *obj) virMutexDestroy(&dom->lock); - virDomainSnapshotObjListDeinit(&dom->snapshots); + virDomainSnapshotObjListFree(dom->snapshots); } @@ -1673,31 +1680,33 @@ virDomainObjPtr virDomainObjNew(virCapsPtr caps) if (!(domain = virObjectNew(virDomainObjClass))) return NULL; - if (caps->privateDataAllocFunc && - !(domain->privateData = (caps->privateDataAllocFunc)())) { - virReportOOMError(); - VIR_FREE(domain); - return NULL; - } - domain->privateDataFreeFunc = caps->privateDataFreeFunc; - if (virMutexInit(&domain->lock) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot initialize mutex")); - if (domain->privateDataFreeFunc) - (domain->privateDataFreeFunc)(domain->privateData); VIR_FREE(domain); return NULL; } + if (caps->privateDataAllocFunc && + !(domain->privateData = (caps->privateDataAllocFunc)())) { + virReportOOMError(); + goto error; + } + domain->privateDataFreeFunc = caps->privateDataFreeFunc; + + if (!(domain->snapshots = virDomainSnapshotObjListNew())) + goto error; + virDomainObjLock(domain); virDomainObjSetState(domain, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_UNKNOWN); - virDomainSnapshotObjListInit(&domain->snapshots); - VIR_DEBUG("obj=%p", domain); return domain; + +error: + virObjectUnref(domain); + return NULL; } void virDomainObjAssignDef(virDomainObjPtr domain, @@ -14332,18 +14341,29 @@ virDomainSnapshotObjListDataFree(void *payload, virDomainSnapshotObjFree(obj); } -int virDomainSnapshotObjListInit(virDomainSnapshotObjListPtr snapshots) +virDomainSnapshotObjListPtr +virDomainSnapshotObjListNew(void) { + virDomainSnapshotObjListPtr snapshots; + if (VIR_ALLOC(snapshots) < 0) { + virReportOOMError(); + return NULL; + } snapshots->objs = virHashCreate(50, virDomainSnapshotObjListDataFree); - if (!snapshots->objs) - return -1; - return 0; + if (!snapshots->objs) { + VIR_FREE(snapshots); + return NULL; + } + return snapshots; } -static void -virDomainSnapshotObjListDeinit(virDomainSnapshotObjListPtr snapshots) +void +virDomainSnapshotObjListFree(virDomainSnapshotObjListPtr snapshots) { + if (!snapshots) + return; virHashFree(snapshots->objs); + VIR_FREE(snapshots); } struct virDomainSnapshotNameData { @@ -14464,6 +14484,14 @@ void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, virHashRemoveEntry(snapshots->objs, snapshot->def->name); } +int +virDomainSnapshotForEach(virDomainSnapshotObjListPtr snapshots, + virHashIterator iter, + void *data) +{ + return virHashForEach(snapshots->objs, iter, data); +} + /* 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. */ @@ -15385,7 +15413,7 @@ virDomainListPopulate(void *payload, /* filter by snapshot existence */ if (MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_SNAPSHOT)) { - int nsnap = virDomainSnapshotObjListNum(&vm->snapshots, NULL, 0); + int nsnap = virDomainSnapshotObjListNum(vm->snapshots, NULL, 0); if (!((MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT) && nsnap > 0) || (MATCH(VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT) && nsnap <= 0))) goto cleanup; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index fd0e89e..78b6bca 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1732,13 +1732,9 @@ struct _virDomainSnapshotObj { typedef struct _virDomainSnapshotObjList virDomainSnapshotObjList; typedef virDomainSnapshotObjList *virDomainSnapshotObjListPtr; -struct _virDomainSnapshotObjList { - /* name string -> virDomainSnapshotObj mapping - * for O(1), lockless lookup-by-name */ - virHashTable *objs; - virDomainSnapshotObj metaroot; /* Special parent of all root snapshots */ -}; +virDomainSnapshotObjListPtr virDomainSnapshotObjListNew(void); +void virDomainSnapshotObjListFree(virDomainSnapshotObjListPtr snapshots); typedef enum { VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE = 1 << 0, @@ -1761,7 +1757,6 @@ int virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr snapshot, virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, const virDomainSnapshotDefPtr def); -int virDomainSnapshotObjListInit(virDomainSnapshotObjListPtr objs); int virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, virDomainSnapshotObjPtr from, char **const names, int maxnames, @@ -1773,6 +1768,9 @@ virDomainSnapshotObjPtr virDomainSnapshotFindByName(const virDomainSnapshotObjLi const char *name); void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, virDomainSnapshotObjPtr snapshot); +int virDomainSnapshotForEach(virDomainSnapshotObjListPtr snapshots, + virHashIterator iter, + void *data); int virDomainSnapshotForEachChild(virDomainSnapshotObjPtr snapshot, virHashIterator iter, void *data); @@ -1806,7 +1804,7 @@ struct _virDomainObj { virDomainDefPtr def; /* The current definition */ virDomainDefPtr newDef; /* New definition to activate at shutdown */ - virDomainSnapshotObjList snapshots; + virDomainSnapshotObjListPtr snapshots; virDomainSnapshotObjPtr current_snapshot; bool hasManagedSave; diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 7539edc..bf37cb3 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -470,6 +470,7 @@ virDomainSnapshotDefFree; virDomainSnapshotDefParseString; virDomainSnapshotDropParent; virDomainSnapshotFindByName; +virDomainSnapshotForEach; virDomainSnapshotForEachChild; virDomainSnapshotForEachDescendant; virDomainSnapshotObjListGetNames; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index c47890b..0ae30b7 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1750,7 +1750,7 @@ qemuDomainSnapshotDiscard(struct qemud_driver *driver, if (snap == vm->current_snapshot) { if (update_current && snap->def->parent) { - parentsnap = virDomainSnapshotFindByName(&vm->snapshots, + parentsnap = virDomainSnapshotFindByName(vm->snapshots, snap->def->parent); if (!parentsnap) { VIR_WARN("missing parent snapshot matching name '%s'", @@ -1771,7 +1771,7 @@ qemuDomainSnapshotDiscard(struct qemud_driver *driver, if (unlink(snapFile) < 0) VIR_WARN("Failed to unlink %s", snapFile); - virDomainSnapshotObjListRemove(&vm->snapshots, snap); + virDomainSnapshotObjListRemove(vm->snapshots, snap); ret = 0; @@ -1808,7 +1808,8 @@ qemuDomainSnapshotDiscardAllMetadata(struct qemud_driver *driver, rem.vm = vm; rem.metadata_only = true; rem.err = 0; - virHashForEach(vm->snapshots.objs, qemuDomainSnapshotDiscardAll, &rem); + virDomainSnapshotForEach(vm->snapshots, qemuDomainSnapshotDiscardAll, + &rem); return rem.err; } diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index bd97008..11e043f 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -412,7 +412,7 @@ static void qemuDomainSnapshotLoad(void *payload, continue; } - snap = virDomainSnapshotAssignDef(&vm->snapshots, def); + snap = virDomainSnapshotAssignDef(vm->snapshots, def); if (snap == NULL) { virDomainSnapshotDefFree(def); } else if (snap->def->current) { @@ -431,7 +431,7 @@ static void qemuDomainSnapshotLoad(void *payload, vm->current_snapshot = NULL; } - if (virDomainSnapshotUpdateRelations(&vm->snapshots) < 0) + if (virDomainSnapshotUpdateRelations(vm->snapshots) < 0) VIR_ERROR(_("Snapshots have inconsistent relations for domain %s"), vm->def->name); @@ -5176,7 +5176,7 @@ qemuDomainUndefineFlags(virDomainPtr dom, } if (!virDomainObjIsActive(vm) && - (nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, NULL, 0))) { + (nsnapshots = virDomainSnapshotObjListNum(vm->snapshots, NULL, 0))) { if (!(flags & VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA)) { virReportError(VIR_ERR_OPERATION_INVALID, _("cannot delete inactive domain with %d " @@ -10583,7 +10583,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, def->name); goto cleanup; } - other = virDomainSnapshotFindByName(&vm->snapshots, def->parent); + other = virDomainSnapshotFindByName(vm->snapshots, def->parent); if (!other) { virReportError(VIR_ERR_INVALID_ARG, _("parent %s for snapshot %s not found"), @@ -10597,7 +10597,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, other->def->name, def->name); goto cleanup; } - other = virDomainSnapshotFindByName(&vm->snapshots, + other = virDomainSnapshotFindByName(vm->snapshots, other->def->parent); if (!other) { VIR_WARN("snapshots are inconsistent for %s", @@ -10615,7 +10615,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, def->name, uuidstr); goto cleanup; } - other = virDomainSnapshotFindByName(&vm->snapshots, def->name); + other = virDomainSnapshotFindByName(vm->snapshots, def->name); if (other) { if ((other->def->state == VIR_DOMAIN_RUNNING || other->def->state == VIR_DOMAIN_PAUSED) != @@ -10704,7 +10704,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, if (snap) snap->def = def; - else if (!(snap = virDomainSnapshotAssignDef(&vm->snapshots, def))) + else if (!(snap = virDomainSnapshotAssignDef(vm->snapshots, def))) goto cleanup; def = NULL; @@ -10761,7 +10761,7 @@ cleanup: } else { if (update_current) vm->current_snapshot = snap; - other = virDomainSnapshotFindByName(&vm->snapshots, + other = virDomainSnapshotFindByName(vm->snapshots, snap->def->parent); snap->parent = other; other->nchildren++; @@ -10769,7 +10769,7 @@ cleanup: other->first_child = snap; } } else if (snap) { - virDomainSnapshotObjListRemove(&vm->snapshots, snap); + virDomainSnapshotObjListRemove(vm->snapshots, snap); } virDomainObjUnlock(vm); } @@ -10800,7 +10800,7 @@ static int qemuDomainSnapshotListNames(virDomainPtr domain, char **names, goto cleanup; } - n = virDomainSnapshotObjListGetNames(&vm->snapshots, NULL, names, nameslen, + n = virDomainSnapshotObjListGetNames(vm->snapshots, NULL, names, nameslen, flags); cleanup: @@ -10830,7 +10830,7 @@ static int qemuDomainSnapshotNum(virDomainPtr domain, goto cleanup; } - n = virDomainSnapshotObjListNum(&vm->snapshots, NULL, flags); + n = virDomainSnapshotObjListNum(vm->snapshots, NULL, flags); cleanup: if (vm) @@ -10860,7 +10860,7 @@ qemuDomainListAllSnapshots(virDomainPtr domain, virDomainSnapshotPtr **snaps, goto cleanup; } - n = virDomainListSnapshots(&vm->snapshots, NULL, domain, snaps, flags); + n = virDomainListSnapshots(vm->snapshots, NULL, domain, snaps, flags); cleanup: if (vm) @@ -10893,7 +10893,7 @@ qemuDomainSnapshotListChildrenNames(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -10901,7 +10901,7 @@ qemuDomainSnapshotListChildrenNames(virDomainSnapshotPtr snapshot, goto cleanup; } - n = virDomainSnapshotObjListGetNames(&vm->snapshots, snap, names, nameslen, + n = virDomainSnapshotObjListGetNames(vm->snapshots, snap, names, nameslen, flags); cleanup: @@ -10933,7 +10933,7 @@ qemuDomainSnapshotNumChildren(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -10941,7 +10941,7 @@ qemuDomainSnapshotNumChildren(virDomainSnapshotPtr snapshot, goto cleanup; } - n = virDomainSnapshotObjListNum(&vm->snapshots, snap, flags); + n = virDomainSnapshotObjListNum(vm->snapshots, snap, flags); cleanup: if (vm) @@ -10973,7 +10973,7 @@ qemuDomainSnapshotListAllChildren(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -10981,7 +10981,7 @@ qemuDomainSnapshotListAllChildren(virDomainSnapshotPtr snapshot, goto cleanup; } - n = virDomainListSnapshots(&vm->snapshots, snap, snapshot->domain, snaps, + n = virDomainListSnapshots(vm->snapshots, snap, snapshot->domain, snaps, flags); cleanup: @@ -11012,7 +11012,7 @@ static virDomainSnapshotPtr qemuDomainSnapshotLookupByName(virDomainPtr domain, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, name); + snap = virDomainSnapshotFindByName(vm->snapshots, name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no snapshot with matching name '%s'"), name); @@ -11077,7 +11077,7 @@ qemuDomainSnapshotGetParent(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -11155,7 +11155,7 @@ static char *qemuDomainSnapshotGetXMLDesc(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -11193,7 +11193,7 @@ qemuDomainSnapshotIsCurrent(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -11233,7 +11233,7 @@ qemuDomainSnapshotHasMetadata(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -11306,7 +11306,7 @@ static int qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), @@ -11674,7 +11674,7 @@ static int qemuDomainSnapshotDelete(virDomainSnapshotPtr snapshot, goto cleanup; } - snap = virDomainSnapshotFindByName(&vm->snapshots, snapshot->name); + snap = virDomainSnapshotFindByName(vm->snapshots, snapshot->name); if (!snap) { virReportError(VIR_ERR_NO_DOMAIN_SNAPSHOT, _("no domain snapshot with matching name '%s'"), diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index f65c81a..1b21ef6 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -807,7 +807,7 @@ qemuMigrationIsAllowed(struct qemud_driver *driver, virDomainObjPtr vm, "%s", _("domain is marked for auto destroy")); return false; } - if ((nsnapshots = virDomainSnapshotObjListNum(&vm->snapshots, NULL, + if ((nsnapshots = virDomainSnapshotObjListNum(vm->snapshots, NULL, 0))) { virReportError(VIR_ERR_OPERATION_INVALID, _("cannot migrate domain with %d snapshots"), -- 1.7.11.2

This has several benefits: 1. Future snapshot-related code has a definite place to go (and I _will_ be adding some) 2. Snapshot errors now use the VIR_FROM_DOMAIN_SNAPSHOT error classification, which has been underutilized (previously only in libvirt.c) * src/conf/domain_conf.h, domain_conf.c: Split... * src/conf/snapshot_conf.h, snapshot_conf.c: ...into new files. * src/Makefile.am (DOMAIN_CONF_SOURCES): Build new files. * po/POTFILES.in: Mark new file for translation. * src/vbox/vbox_tmpl.c: Update caller. * src/esx/esx_driver.c: Likewise. * src/qemu/qemu_command.c: Likewise. * src/qemu/qemu_domain.h: Likewise. --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/conf/domain_conf.c | 921 +------------------------------------------- src/conf/domain_conf.h | 139 +------ src/conf/snapshot_conf.c | 970 +++++++++++++++++++++++++++++++++++++++++++++++ src/conf/snapshot_conf.h | 157 ++++++++ src/esx/esx_driver.c | 1 + src/qemu/qemu_command.c | 1 + src/qemu/qemu_domain.h | 3 +- src/vbox/vbox_tmpl.c | 1 + 10 files changed, 1143 insertions(+), 1054 deletions(-) create mode 100644 src/conf/snapshot_conf.c create mode 100644 src/conf/snapshot_conf.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 20d9d82..43da304 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -19,6 +19,7 @@ src/conf/node_device_conf.c src/conf/nwfilter_conf.c src/conf/nwfilter_params.c src/conf/secret_conf.c +src/conf/snapshot_conf.c src/conf/storage_conf.c src/conf/storage_encryption_conf.c src/conf/virconsole.c diff --git a/src/Makefile.am b/src/Makefile.am index d35edd6..bd4fc6b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -153,7 +153,8 @@ DOMAIN_CONF_SOURCES = \ conf/capabilities.c conf/capabilities.h \ conf/domain_conf.c conf/domain_conf.h \ conf/domain_audit.c conf/domain_audit.h \ - conf/domain_nwfilter.c conf/domain_nwfilter.h + conf/domain_nwfilter.c conf/domain_nwfilter.h \ + conf/snapshot_conf.c conf/snapshot_conf.h DOMAIN_EVENT_SOURCES = \ conf/domain_event.c conf/domain_event.h diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 0c6671c..e2700fa 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -35,6 +35,7 @@ #include "virterror_internal.h" #include "datatypes.h" #include "domain_conf.h" +#include "snapshot_conf.h" #include "memory.h" #include "verify.h" #include "xml.h" @@ -220,12 +221,6 @@ VIR_ENUM_IMPL(virDomainDiskCopyOnRead, VIR_DOMAIN_DISK_COPY_ON_READ_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", @@ -522,18 +517,6 @@ VIR_ENUM_IMPL(virDomainState, VIR_DOMAIN_LAST, "crashed", "pmsuspended") -/* virDomainSnapshotState is really virDomainState plus one extra state */ -VIR_ENUM_IMPL(virDomainSnapshotState, VIR_DOMAIN_SNAPSHOT_STATE_LAST, - "nostate", - "running", - "blocked", - "paused", - "shutdown", - "shutoff", - "crashed", - "pmsuspended", - "disk-snapshot") - #define VIR_DOMAIN_NOSTATE_LAST (VIR_DOMAIN_NOSTATE_UNKNOWN + 1) VIR_ENUM_IMPL(virDomainNostateReason, VIR_DOMAIN_NOSTATE_LAST, "unknown") @@ -655,15 +638,6 @@ VIR_ENUM_IMPL(virDomainNumatuneMemPlacementMode, #define VIR_DOMAIN_XML_WRITE_FLAGS VIR_DOMAIN_XML_SECURE #define VIR_DOMAIN_XML_READ_FLAGS VIR_DOMAIN_XML_INACTIVE -struct _virDomainSnapshotObjList { - /* name string -> virDomainSnapshotObj mapping - * for O(1), lockless lookup-by-name */ - virHashTable *objs; - - virDomainSnapshotObj metaroot; /* Special parent of all root snapshots */ -}; - - static virClassPtr virDomainObjClass; static void virDomainObjDispose(void *obj); @@ -13783,856 +13757,6 @@ cleanup: return -1; } -/* 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) { - virReportError(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) { - virReportError(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; -} - -/* flags is bitwise-or of virDomainSnapshotParseFlags. - * If flags does not include VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE, then - * caps and expectedVirtTypes are ignored. - */ -virDomainSnapshotDefPtr -virDomainSnapshotDefParseString(const char *xmlStr, - virCapsPtr caps, - unsigned int expectedVirtTypes, - unsigned int flags) -{ - xmlXPathContextPtr ctxt = NULL; - xmlDocPtr xml = NULL; - virDomainSnapshotDefPtr def = NULL; - virDomainSnapshotDefPtr ret = NULL; - xmlNodePtr *nodes = NULL; - int i; - char *creation = NULL, *state = NULL; - struct timeval tv; - int active; - char *tmp; - int keepBlanksDefault = xmlKeepBlanksDefault(0); - - xml = virXMLParseCtxt(NULL, xmlStr, _("(domain_snapshot)"), &ctxt); - if (!xml) { - xmlKeepBlanksDefault(keepBlanksDefault); - return NULL; - } - xmlKeepBlanksDefault(keepBlanksDefault); - - if (VIR_ALLOC(def) < 0) { - virReportOOMError(); - goto cleanup; - } - - if (!xmlStrEqual(ctxt->node->name, BAD_CAST "domainsnapshot")) { - virReportError(VIR_ERR_XML_ERROR, "%s", _("domainsnapshot")); - goto cleanup; - } - - gettimeofday(&tv, NULL); - - def->name = virXPathString("string(./name)", ctxt); - if (def->name == NULL) { - if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("a redefined snapshot must have a name")); - goto cleanup; - } else { - ignore_value(virAsprintf(&def->name, "%lld", - (long long)tv.tv_sec)); - } - } - - if (def->name == NULL) { - virReportOOMError(); - goto cleanup; - } - - def->description = virXPathString("string(./description)", ctxt); - - if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { - if (virXPathLongLong("string(./creationTime)", ctxt, - &def->creationTime) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing creationTime from existing snapshot")); - goto cleanup; - } - - def->parent = virXPathString("string(./parent/name)", ctxt); - - state = virXPathString("string(./state)", ctxt); - if (state == NULL) { - /* there was no state in an existing snapshot; this - * should never happen - */ - virReportError(VIR_ERR_XML_ERROR, "%s", - _("missing state from existing snapshot")); - goto cleanup; - } - def->state = virDomainSnapshotStateTypeFromString(state); - if (def->state < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("Invalid state '%s' in domain snapshot XML"), - state); - goto cleanup; - } - - /* 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))) { - xmlNodePtr domainNode = virXPathNode("./domain", ctxt); - - VIR_FREE(tmp); - if (!domainNode) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing domain in snapshot")); - goto cleanup; - } - def->dom = virDomainDefParseNode(caps, xml, domainNode, - expectedVirtTypes, - (VIR_DOMAIN_XML_INACTIVE | - VIR_DOMAIN_XML_SECURE)); - if (!def->dom) - goto cleanup; - } else { - VIR_WARN("parsing older snapshot that lacks domain"); - } - } else { - def->creationTime = tv.tv_sec; - } - - if ((i = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) - goto cleanup; - if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_DISKS || - (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE && - def->state == VIR_DOMAIN_DISK_SNAPSHOT)) { - 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); - } else if (i) { - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", - _("unable to handle disk requests in snapshot")); - goto cleanup; - } - - if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL) { - if (virXPathInt("string(./active)", ctxt, &active) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Could not find 'active' element")); - goto cleanup; - } - def->current = active != 0; - } - - ret = def; - -cleanup: - VIR_FREE(creation); - VIR_FREE(state); - VIR_FREE(nodes); - xmlXPathFreeContext(ctxt); - if (ret == NULL) - virDomainSnapshotDefFree(def); - xmlFreeDoc(xml); - - 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. 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, - bool require_match) -{ - int ret = -1; - virBitmapPtr map = NULL; - int i; - int ndisks; - bool inuse; - - if (!def->dom) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing domain in snapshot")); - goto cleanup; - } - - if (def->ndisks > def->dom->ndisks) { - virReportError(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, false); - int disk_snapshot; - - if (idx < 0) { - virReportError(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) { - virReportError(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); - virReportError(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) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("file '%s' for disk '%s' requires " - "use of external snapshot mode"), - 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. */ - 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, but only if the - * backing file is a regular file. */ - 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; - struct stat sb; - - if (!original) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("cannot generate external snapshot name " - "for disk '%s' without source"), - disk->name); - goto cleanup; - } - if (stat(original, &sb) < 0 || !S_ISREG(sb.st_mode)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("source for disk '%s' is not a regular " - "file; refusing to generate external " - "snapshot name"), - 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) { - virReportError(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(const char *domain_uuid, - virDomainSnapshotDefPtr def, - unsigned int flags, - int internal) -{ - virBuffer buf = VIR_BUFFER_INITIALIZER; - int i; - - virCheckFlags(VIR_DOMAIN_XML_SECURE | - VIR_DOMAIN_XML_UPDATE_CPU, NULL); - - flags |= VIR_DOMAIN_XML_INACTIVE; - - virBufferAddLit(&buf, "<domainsnapshot>\n"); - virBufferEscapeString(&buf, " <name>%s</name>\n", def->name); - if (def->description) - virBufferEscapeString(&buf, " <description>%s</description>\n", - def->description); - virBufferAsprintf(&buf, " <state>%s</state>\n", - virDomainSnapshotStateTypeToString(def->state)); - if (def->parent) { - virBufferAddLit(&buf, " <parent>\n"); - virBufferEscapeString(&buf, " <name>%s</name>\n", def->parent); - virBufferAddLit(&buf, " </parent>\n"); - } - 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->driverType) - virBufferEscapeString(&buf, " <driver type='%s'/>\n", - disk->driverType); - if (disk->file) - virBufferEscapeString(&buf, " <source file='%s'/>\n", - disk->file); - virBufferAddLit(&buf, " </disk>\n"); - } else { - virBufferAddLit(&buf, "/>\n"); - } - } - virBufferAddLit(&buf, " </disks>\n"); - } - if (def->dom) { - virBufferAdjustIndent(&buf, 2); - if (virDomainDefFormatInternal(def->dom, flags, &buf) < 0) { - virBufferFreeAndReset(&buf); - return NULL; - } - virBufferAdjustIndent(&buf, -2); - } else if (domain_uuid) { - 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"); - - if (virBufferError(&buf)) { - virBufferFreeAndReset(&buf); - virReportOOMError(); - return NULL; - } - - return virBufferContentAndReset(&buf); -} - -/* Snapshot Obj functions */ -static virDomainSnapshotObjPtr virDomainSnapshotObjNew(void) -{ - virDomainSnapshotObjPtr snapshot; - - if (VIR_ALLOC(snapshot) < 0) { - virReportOOMError(); - return NULL; - } - - VIR_DEBUG("obj=%p", snapshot); - - return snapshot; -} - -static void virDomainSnapshotObjFree(virDomainSnapshotObjPtr snapshot) -{ - if (!snapshot) - return; - - VIR_DEBUG("obj=%p", snapshot); - - virDomainSnapshotDefFree(snapshot->def); - VIR_FREE(snapshot); -} - -virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, - const virDomainSnapshotDefPtr def) -{ - virDomainSnapshotObjPtr snap; - - if (virHashLookup(snapshots->objs, def->name) != NULL) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unexpected domain snapshot %s already exists"), - def->name); - return NULL; - } - - if (!(snap = virDomainSnapshotObjNew())) - return NULL; - snap->def = def; - - if (virHashAddEntry(snapshots->objs, snap->def->name, snap) < 0) { - VIR_FREE(snap); - return NULL; - } - - return snap; -} - -/* Snapshot Obj List functions */ -static void -virDomainSnapshotObjListDataFree(void *payload, - const void *name ATTRIBUTE_UNUSED) -{ - virDomainSnapshotObjPtr obj = payload; - - virDomainSnapshotObjFree(obj); -} - -virDomainSnapshotObjListPtr -virDomainSnapshotObjListNew(void) -{ - virDomainSnapshotObjListPtr snapshots; - if (VIR_ALLOC(snapshots) < 0) { - virReportOOMError(); - return NULL; - } - snapshots->objs = virHashCreate(50, virDomainSnapshotObjListDataFree); - if (!snapshots->objs) { - VIR_FREE(snapshots); - return NULL; - } - return snapshots; -} - -void -virDomainSnapshotObjListFree(virDomainSnapshotObjListPtr snapshots) -{ - if (!snapshots) - return; - virHashFree(snapshots->objs); - VIR_FREE(snapshots); -} - -struct virDomainSnapshotNameData { - char **const names; - int maxnames; - unsigned int flags; - int count; - bool error; -}; - -static void virDomainSnapshotObjListCopyNames(void *payload, - const void *name ATTRIBUTE_UNUSED, - void *opaque) -{ - virDomainSnapshotObjPtr obj = payload; - struct virDomainSnapshotNameData *data = opaque; - - if (data->error) - return; - /* Caller already sanitized flags. Filtering on DESCENDANTS was - * done by choice of iteration in the caller. */ - if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_LEAVES) && obj->nchildren) - return; - if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES) && !obj->nchildren) - return; - - if (data->names && data->count < data->maxnames && - !(data->names[data->count] = strdup(obj->def->name))) { - data->error = true; - virReportOOMError(); - return; - } - data->count++; -} - -int -virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr from, - char **const names, int maxnames, - unsigned int flags) -{ - struct virDomainSnapshotNameData data = { names, maxnames, flags, 0, - false }; - int i; - - if (!from) { - /* LIST_ROOTS and LIST_DESCENDANTS have the same bit value, - * but opposite semantics. Toggle here to get the correct - * traversal on the metaroot. */ - flags ^= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS; - from = &snapshots->metaroot; - } - - /* We handle LIST_ROOT/LIST_DESCENDANTS directly, mask that bit - * out to determine when we must use the filter callback. */ - data.flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS; - - /* If this common code is being used, we assume that all snapshots - * have metadata, and thus can handle METADATA up front as an - * all-or-none filter. XXX This might not always be true, if we - * add the ability to track qcow2 internal snapshots without the - * use of metadata. */ - if ((data.flags & VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA) == - VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) - return 0; - data.flags &= ~VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA; - - /* For ease of coding the visitor, it is easier to zero the LEAVES - * group if both bits are set. */ - if ((data.flags & VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES) == - VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES) - data.flags &= ~VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES; - - if (flags & VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS) { - if (from->def) - virDomainSnapshotForEachDescendant(from, - virDomainSnapshotObjListCopyNames, - &data); - else if (names || data.flags) - virHashForEach(snapshots->objs, virDomainSnapshotObjListCopyNames, - &data); - else - data.count = virHashSize(snapshots->objs); - } else if (names || data.flags) { - virDomainSnapshotForEachChild(from, - virDomainSnapshotObjListCopyNames, &data); - } else { - data.count = from->nchildren; - } - - if (data.error) { - for (i = 0; i < data.count; i++) - VIR_FREE(names[i]); - return -1; - } - - return data.count; -} - -int -virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr from, - unsigned int flags) -{ - return virDomainSnapshotObjListGetNames(snapshots, from, NULL, 0, flags); -} - -virDomainSnapshotObjPtr -virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, - const char *name) -{ - return name ? virHashLookup(snapshots->objs, name) : &snapshots->metaroot; -} - -void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr snapshot) -{ - virHashRemoveEntry(snapshots->objs, snapshot->def->name); -} - -int -virDomainSnapshotForEach(virDomainSnapshotObjListPtr snapshots, - virHashIterator iter, - void *data) -{ - return virHashForEach(snapshots->objs, iter, data); -} - -/* 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(virDomainSnapshotObjPtr snapshot, - virHashIterator iter, - void *data) -{ - virDomainSnapshotObjPtr child = snapshot->first_child; - - while (child) { - virDomainSnapshotObjPtr next = child->sibling; - (iter)(child, child->def->name, data); - child = next; - } - - return snapshot->nchildren; -} - -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; - - curr->number += 1 + virDomainSnapshotForEachDescendant(obj, - curr->iter, - curr->data); - (curr->iter)(payload, name, curr->data); -} - -/* 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(virDomainSnapshotObjPtr snapshot, - virHashIterator iter, - void *data) -{ - struct snapshot_act_on_descendant act; - - act.number = 0; - act.iter = iter; - act.data = data; - virDomainSnapshotForEachChild(snapshot, - virDomainSnapshotActOnDescendant, &act); - - return act.number; -} - -/* Struct and callback function used as a hash table callback; each call - * inspects the pre-existing snapshot->def->parent field, and adjusts - * the snapshot->parent field as well as the parent's child fields to - * wire up the hierarchical relations for the given snapshot. The error - * indicator gets set if a parent is missing or a requested parent would - * cause a circular parent chain. */ -struct snapshot_set_relation { - virDomainSnapshotObjListPtr snapshots; - int err; -}; -static void -virDomainSnapshotSetRelations(void *payload, - const void *name ATTRIBUTE_UNUSED, - void *data) -{ - virDomainSnapshotObjPtr obj = payload; - struct snapshot_set_relation *curr = data; - virDomainSnapshotObjPtr tmp; - - obj->parent = virDomainSnapshotFindByName(curr->snapshots, - obj->def->parent); - if (!obj->parent) { - curr->err = -1; - obj->parent = &curr->snapshots->metaroot; - VIR_WARN("snapshot %s lacks parent", obj->def->name); - } else { - tmp = obj->parent; - while (tmp && tmp->def) { - if (tmp == obj) { - curr->err = -1; - obj->parent = &curr->snapshots->metaroot; - VIR_WARN("snapshot %s in circular chain", obj->def->name); - break; - } - tmp = tmp->parent; - } - } - obj->parent->nchildren++; - obj->sibling = obj->parent->first_child; - obj->parent->first_child = obj; -} - -/* Populate parent link and child count of all snapshots, with all - * relations starting as 0/NULL. Return 0 on success, -1 if a parent - * is missing or if a circular relationship was requested. */ -int -virDomainSnapshotUpdateRelations(virDomainSnapshotObjListPtr snapshots) -{ - struct snapshot_set_relation act = { snapshots, 0 }; - - virHashForEach(snapshots->objs, virDomainSnapshotSetRelations, &act); - return act.err; -} - -/* Prepare to reparent or delete snapshot, by removing it from its - * current listed parent. Note that when bulk removing all children - * of a parent, it is faster to just 0 the count rather than calling - * this function on each child. */ -void -virDomainSnapshotDropParent(virDomainSnapshotObjPtr snapshot) -{ - virDomainSnapshotObjPtr prev = NULL; - virDomainSnapshotObjPtr curr = NULL; - - snapshot->parent->nchildren--; - curr = snapshot->parent->first_child; - while (curr != snapshot) { - if (!curr) { - VIR_WARN("inconsistent snapshot relations"); - return; - } - prev = curr; - curr = curr->sibling; - } - if (prev) - prev->sibling = snapshot->sibling; - else - snapshot->parent->first_child = snapshot->sibling; - snapshot->parent = NULL; - snapshot->sibling = NULL; -} - - int virDomainChrDefForeach(virDomainDefPtr def, bool abortOnError, virDomainChrDefIterator iter, @@ -15482,46 +14606,3 @@ cleanup: VIR_FREE(data.domains); return ret; } - -int -virDomainListSnapshots(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr from, - virDomainPtr dom, - virDomainSnapshotPtr **snaps, - unsigned int flags) -{ - int count = virDomainSnapshotObjListNum(snapshots, from, flags); - virDomainSnapshotPtr *list; - char **names; - int ret = -1; - int i; - - if (!snaps) - return count; - if (VIR_ALLOC_N(names, count) < 0 || - VIR_ALLOC_N(list, count + 1) < 0) { - virReportOOMError(); - goto cleanup; - } - - if (virDomainSnapshotObjListGetNames(snapshots, from, names, count, - flags) < 0) - goto cleanup; - for (i = 0; i < count; i++) - if ((list[i] = virGetDomainSnapshot(dom, names[i])) == NULL) - goto cleanup; - - ret = count; - *snaps = list; - -cleanup: - for (i = 0; i < count; i++) - VIR_FREE(names[i]); - VIR_FREE(names); - if (ret < 0 && list) { - for (i = 0; i < count; i++) - virObjectUnref(list[i]); - VIR_FREE(list); - } - return ret; -} diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 78b6bca..4b4719e 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -101,6 +101,12 @@ typedef virDomainChrDef *virDomainChrDefPtr; typedef struct _virDomainMemballoonDef virDomainMemballoonDef; typedef virDomainMemballoonDef *virDomainMemballoonDefPtr; +typedef struct _virDomainSnapshotObj virDomainSnapshotObj; +typedef virDomainSnapshotObj *virDomainSnapshotObjPtr; + +typedef struct _virDomainSnapshotObjList virDomainSnapshotObjList; +typedef virDomainSnapshotObjList *virDomainSnapshotObjListPtr; + /* Flags for the 'type' field in virDomainDeviceDef */ typedef enum { VIR_DOMAIN_DEVICE_NONE = 0, @@ -491,21 +497,6 @@ enum virDomainDiskCopyOnRead { VIR_DOMAIN_DISK_COPY_ON_READ_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 -}; - -enum virDomainSnapshotState { - /* Inherit the VIR_DOMAIN_* states from virDomainState. */ - VIR_DOMAIN_DISK_SNAPSHOT = VIR_DOMAIN_LAST, - VIR_DOMAIN_SNAPSHOT_STATE_LAST -}; - enum virDomainStartupPolicy { VIR_DOMAIN_STARTUP_POLICY_DEFAULT = 0, VIR_DOMAIN_STARTUP_POLICY_MANDATORY, @@ -571,7 +562,7 @@ struct _virDomainDiskDef { int ioeventfd; int event_idx; int copy_on_read; - int snapshot; /* enum virDomainDiskSnapshot */ + int snapshot; /* enum virDomainDiskSnapshot, snapshot_conf.h */ int startupPolicy; /* enum virDomainStartupPolicy */ unsigned int readonly : 1; unsigned int shared : 1; @@ -1684,102 +1675,6 @@ enum virDomainTaintFlags { VIR_DOMAIN_TAINT_LAST }; -/* 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 { - /* Public XML. */ - char *name; - char *description; - 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. */ - bool current; /* At most one snapshot in the list should have this set */ -}; - -typedef struct _virDomainSnapshotObj virDomainSnapshotObj; -typedef virDomainSnapshotObj *virDomainSnapshotObjPtr; -struct _virDomainSnapshotObj { - virDomainSnapshotDefPtr def; /* non-NULL except for metaroot */ - - virDomainSnapshotObjPtr parent; /* non-NULL except for metaroot, before - virDomainSnapshotUpdateRelations, or - after virDomainSnapshotDropParent */ - virDomainSnapshotObjPtr sibling; /* NULL if last child of parent */ - size_t nchildren; - virDomainSnapshotObjPtr first_child; /* NULL if no children */ -}; - -typedef struct _virDomainSnapshotObjList virDomainSnapshotObjList; -typedef virDomainSnapshotObjList *virDomainSnapshotObjListPtr; - -virDomainSnapshotObjListPtr virDomainSnapshotObjListNew(void); -void virDomainSnapshotObjListFree(virDomainSnapshotObjListPtr snapshots); - -typedef enum { - VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE = 1 << 0, - VIR_DOMAIN_SNAPSHOT_PARSE_DISKS = 1 << 1, - VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL = 1 << 2, -} virDomainSnapshotParseFlags; - -virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, - virCapsPtr caps, - unsigned int expectedVirtTypes, - unsigned int flags); -void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def); -char *virDomainSnapshotDefFormat(const 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); - -int virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr from, - char **const names, int maxnames, - unsigned int flags); -int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr from, - unsigned int flags); -virDomainSnapshotObjPtr virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, - const char *name); -void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr snapshot); -int virDomainSnapshotForEach(virDomainSnapshotObjListPtr snapshots, - virHashIterator iter, - void *data); -int virDomainSnapshotForEachChild(virDomainSnapshotObjPtr snapshot, - virHashIterator iter, - void *data); -int virDomainSnapshotForEachDescendant(virDomainSnapshotObjPtr snapshot, - virHashIterator iter, - void *data); -int virDomainSnapshotUpdateRelations(virDomainSnapshotObjListPtr snapshots); -void virDomainSnapshotDropParent(virDomainSnapshotObjPtr snapshot); - /* Guest VM runtime state */ typedef struct _virDomainStateReason virDomainStateReason; struct _virDomainStateReason { @@ -2156,7 +2051,6 @@ VIR_ENUM_DECL(virDomainDiskErrorPolicy) VIR_ENUM_DECL(virDomainDiskProtocol) VIR_ENUM_DECL(virDomainDiskIo) VIR_ENUM_DECL(virDomainDiskSecretType) -VIR_ENUM_DECL(virDomainDiskSnapshot) VIR_ENUM_DECL(virDomainDiskTray) VIR_ENUM_DECL(virDomainIoEventFd) VIR_ENUM_DECL(virDomainVirtioEventIdx) @@ -2207,7 +2101,6 @@ VIR_ENUM_DECL(virDomainGraphicsSpiceClipboardCopypaste) VIR_ENUM_DECL(virDomainGraphicsSpiceMouseMode) VIR_ENUM_DECL(virDomainNumatuneMemMode) VIR_ENUM_DECL(virDomainNumatuneMemPlacementMode) -VIR_ENUM_DECL(virDomainSnapshotState) /* from libvirt.h */ VIR_ENUM_DECL(virDomainState) VIR_ENUM_DECL(virDomainNostateReason) @@ -2270,25 +2163,7 @@ virDomainNetDefPtr virDomainNetFind(virDomainDefPtr def, VIR_CONNECT_LIST_DOMAINS_FILTERS_AUTOSTART | \ VIR_CONNECT_LIST_DOMAINS_FILTERS_SNAPSHOT) -# define VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA \ - (VIR_DOMAIN_SNAPSHOT_LIST_METADATA | \ - VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) - -# define VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES \ - (VIR_DOMAIN_SNAPSHOT_LIST_LEAVES | \ - VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES) - -# define VIR_DOMAIN_SNAPSHOT_FILTERS_ALL \ - (VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA | \ - VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES) - int virDomainList(virConnectPtr conn, virHashTablePtr domobjs, virDomainPtr **domains, unsigned int flags); -int virDomainListSnapshots(virDomainSnapshotObjListPtr snapshots, - virDomainSnapshotObjPtr from, - virDomainPtr dom, - virDomainSnapshotPtr **snaps, - unsigned int flags); - #endif /* __DOMAIN_CONF_H */ diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c new file mode 100644 index 0000000..894a74c --- /dev/null +++ b/src/conf/snapshot_conf.c @@ -0,0 +1,970 @@ +/* + * snapshot_conf.c: domain snapshot XML processing + * + * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/time.h> + +#include "internal.h" +#include "virterror_internal.h" +#include "datatypes.h" +#include "domain_conf.h" +#include "snapshot_conf.h" +#include "memory.h" +#include "xml.h" +#include "uuid.h" +#include "util.h" +#include "buf.h" +#include "logging.h" +#include "nwfilter_conf.h" +#include "storage_file.h" +#include "virfile.h" +#include "bitmap.h" +#include "count-one-bits.h" +#include "secret_conf.h" +#include "netdev_vport_profile_conf.h" +#include "netdev_bandwidth_conf.h" + +#define VIR_FROM_THIS VIR_FROM_DOMAIN_SNAPSHOT + +VIR_ENUM_IMPL(virDomainDiskSnapshot, VIR_DOMAIN_DISK_SNAPSHOT_LAST, + "default", + "no", + "internal", + "external") + +/* virDomainSnapshotState is really virDomainState plus one extra state */ +VIR_ENUM_IMPL(virDomainSnapshotState, VIR_DOMAIN_SNAPSHOT_STATE_LAST, + "nostate", + "running", + "blocked", + "paused", + "shutdown", + "shutoff", + "crashed", + "pmsuspended", + "disk-snapshot") + +struct _virDomainSnapshotObjList { + /* name string -> virDomainSnapshotObj mapping + * for O(1), lockless lookup-by-name */ + virHashTable *objs; + + virDomainSnapshotObj metaroot; /* Special parent of all root snapshots */ +}; + +/* 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) { + virReportError(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) { + virReportError(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; +} + +/* flags is bitwise-or of virDomainSnapshotParseFlags. + * If flags does not include VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE, then + * caps and expectedVirtTypes are ignored. + */ +virDomainSnapshotDefPtr +virDomainSnapshotDefParseString(const char *xmlStr, + virCapsPtr caps, + unsigned int expectedVirtTypes, + unsigned int flags) +{ + xmlXPathContextPtr ctxt = NULL; + xmlDocPtr xml = NULL; + virDomainSnapshotDefPtr def = NULL; + virDomainSnapshotDefPtr ret = NULL; + xmlNodePtr *nodes = NULL; + int i; + char *creation = NULL, *state = NULL; + struct timeval tv; + int active; + char *tmp; + int keepBlanksDefault = xmlKeepBlanksDefault(0); + + xml = virXMLParseCtxt(NULL, xmlStr, _("(domain_snapshot)"), &ctxt); + if (!xml) { + xmlKeepBlanksDefault(keepBlanksDefault); + return NULL; + } + xmlKeepBlanksDefault(keepBlanksDefault); + + if (VIR_ALLOC(def) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (!xmlStrEqual(ctxt->node->name, BAD_CAST "domainsnapshot")) { + virReportError(VIR_ERR_XML_ERROR, "%s", _("domainsnapshot")); + goto cleanup; + } + + gettimeofday(&tv, NULL); + + def->name = virXPathString("string(./name)", ctxt); + if (def->name == NULL) { + if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("a redefined snapshot must have a name")); + goto cleanup; + } else { + ignore_value(virAsprintf(&def->name, "%lld", + (long long)tv.tv_sec)); + } + } + + if (def->name == NULL) { + virReportOOMError(); + goto cleanup; + } + + def->description = virXPathString("string(./description)", ctxt); + + if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) { + if (virXPathLongLong("string(./creationTime)", ctxt, + &def->creationTime) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing creationTime from existing snapshot")); + goto cleanup; + } + + def->parent = virXPathString("string(./parent/name)", ctxt); + + state = virXPathString("string(./state)", ctxt); + if (state == NULL) { + /* there was no state in an existing snapshot; this + * should never happen + */ + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing state from existing snapshot")); + goto cleanup; + } + def->state = virDomainSnapshotStateTypeFromString(state); + if (def->state < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("Invalid state '%s' in domain snapshot XML"), + state); + goto cleanup; + } + + /* 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))) { + xmlNodePtr domainNode = virXPathNode("./domain", ctxt); + + VIR_FREE(tmp); + if (!domainNode) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in snapshot")); + goto cleanup; + } + def->dom = virDomainDefParseNode(caps, xml, domainNode, + expectedVirtTypes, + (VIR_DOMAIN_XML_INACTIVE | + VIR_DOMAIN_XML_SECURE)); + if (!def->dom) + goto cleanup; + } else { + VIR_WARN("parsing older snapshot that lacks domain"); + } + } else { + def->creationTime = tv.tv_sec; + } + + if ((i = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) + goto cleanup; + if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_DISKS || + (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE && + def->state == VIR_DOMAIN_DISK_SNAPSHOT)) { + 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); + } else if (i) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("unable to handle disk requests in snapshot")); + goto cleanup; + } + + if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL) { + if (virXPathInt("string(./active)", ctxt, &active) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Could not find 'active' element")); + goto cleanup; + } + def->current = active != 0; + } + + ret = def; + +cleanup: + VIR_FREE(creation); + VIR_FREE(state); + VIR_FREE(nodes); + xmlXPathFreeContext(ctxt); + if (ret == NULL) + virDomainSnapshotDefFree(def); + xmlFreeDoc(xml); + + 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. 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, + bool require_match) +{ + int ret = -1; + virBitmapPtr map = NULL; + int i; + int ndisks; + bool inuse; + + if (!def->dom) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing domain in snapshot")); + goto cleanup; + } + + if (def->ndisks > def->dom->ndisks) { + virReportError(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, false); + int disk_snapshot; + + if (idx < 0) { + virReportError(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) { + virReportError(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); + virReportError(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) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("file '%s' for disk '%s' requires " + "use of external snapshot mode"), + 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. */ + 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, but only if the + * backing file is a regular file. */ + 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; + struct stat sb; + + if (!original) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("cannot generate external snapshot name " + "for disk '%s' without source"), + disk->name); + goto cleanup; + } + if (stat(original, &sb) < 0 || !S_ISREG(sb.st_mode)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("source for disk '%s' is not a regular " + "file; refusing to generate external " + "snapshot name"), + 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) { + virReportError(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(const char *domain_uuid, + virDomainSnapshotDefPtr def, + unsigned int flags, + int internal) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + int i; + + virCheckFlags(VIR_DOMAIN_XML_SECURE | + VIR_DOMAIN_XML_UPDATE_CPU, NULL); + + flags |= VIR_DOMAIN_XML_INACTIVE; + + virBufferAddLit(&buf, "<domainsnapshot>\n"); + virBufferEscapeString(&buf, " <name>%s</name>\n", def->name); + if (def->description) + virBufferEscapeString(&buf, " <description>%s</description>\n", + def->description); + virBufferAsprintf(&buf, " <state>%s</state>\n", + virDomainSnapshotStateTypeToString(def->state)); + if (def->parent) { + virBufferAddLit(&buf, " <parent>\n"); + virBufferEscapeString(&buf, " <name>%s</name>\n", def->parent); + virBufferAddLit(&buf, " </parent>\n"); + } + 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->driverType) + virBufferEscapeString(&buf, " <driver type='%s'/>\n", + disk->driverType); + if (disk->file) + virBufferEscapeString(&buf, " <source file='%s'/>\n", + disk->file); + virBufferAddLit(&buf, " </disk>\n"); + } else { + virBufferAddLit(&buf, "/>\n"); + } + } + virBufferAddLit(&buf, " </disks>\n"); + } + if (def->dom) { + virBufferAdjustIndent(&buf, 2); + if (virDomainDefFormatInternal(def->dom, flags, &buf) < 0) { + virBufferFreeAndReset(&buf); + return NULL; + } + virBufferAdjustIndent(&buf, -2); + } else if (domain_uuid) { + 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"); + + if (virBufferError(&buf)) { + virBufferFreeAndReset(&buf); + virReportOOMError(); + return NULL; + } + + return virBufferContentAndReset(&buf); +} + +/* Snapshot Obj functions */ +static virDomainSnapshotObjPtr virDomainSnapshotObjNew(void) +{ + virDomainSnapshotObjPtr snapshot; + + if (VIR_ALLOC(snapshot) < 0) { + virReportOOMError(); + return NULL; + } + + VIR_DEBUG("obj=%p", snapshot); + + return snapshot; +} + +static void virDomainSnapshotObjFree(virDomainSnapshotObjPtr snapshot) +{ + if (!snapshot) + return; + + VIR_DEBUG("obj=%p", snapshot); + + virDomainSnapshotDefFree(snapshot->def); + VIR_FREE(snapshot); +} + +virDomainSnapshotObjPtr virDomainSnapshotAssignDef(virDomainSnapshotObjListPtr snapshots, + const virDomainSnapshotDefPtr def) +{ + virDomainSnapshotObjPtr snap; + + if (virHashLookup(snapshots->objs, def->name) != NULL) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected domain snapshot %s already exists"), + def->name); + return NULL; + } + + if (!(snap = virDomainSnapshotObjNew())) + return NULL; + snap->def = def; + + if (virHashAddEntry(snapshots->objs, snap->def->name, snap) < 0) { + VIR_FREE(snap); + return NULL; + } + + return snap; +} + +/* Snapshot Obj List functions */ +static void +virDomainSnapshotObjListDataFree(void *payload, + const void *name ATTRIBUTE_UNUSED) +{ + virDomainSnapshotObjPtr obj = payload; + + virDomainSnapshotObjFree(obj); +} + +virDomainSnapshotObjListPtr +virDomainSnapshotObjListNew(void) +{ + virDomainSnapshotObjListPtr snapshots; + if (VIR_ALLOC(snapshots) < 0) { + virReportOOMError(); + return NULL; + } + snapshots->objs = virHashCreate(50, virDomainSnapshotObjListDataFree); + if (!snapshots->objs) { + VIR_FREE(snapshots); + return NULL; + } + return snapshots; +} + +void +virDomainSnapshotObjListFree(virDomainSnapshotObjListPtr snapshots) +{ + if (!snapshots) + return; + virHashFree(snapshots->objs); + VIR_FREE(snapshots); +} + +struct virDomainSnapshotNameData { + char **const names; + int maxnames; + unsigned int flags; + int count; + bool error; +}; + +static void virDomainSnapshotObjListCopyNames(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *opaque) +{ + virDomainSnapshotObjPtr obj = payload; + struct virDomainSnapshotNameData *data = opaque; + + if (data->error) + return; + /* Caller already sanitized flags. Filtering on DESCENDANTS was + * done by choice of iteration in the caller. */ + if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_LEAVES) && obj->nchildren) + return; + if ((data->flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES) && !obj->nchildren) + return; + + if (data->names && data->count < data->maxnames && + !(data->names[data->count] = strdup(obj->def->name))) { + data->error = true; + virReportOOMError(); + return; + } + data->count++; +} + +int +virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr from, + char **const names, int maxnames, + unsigned int flags) +{ + struct virDomainSnapshotNameData data = { names, maxnames, flags, 0, + false }; + int i; + + if (!from) { + /* LIST_ROOTS and LIST_DESCENDANTS have the same bit value, + * but opposite semantics. Toggle here to get the correct + * traversal on the metaroot. */ + flags ^= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS; + from = &snapshots->metaroot; + } + + /* We handle LIST_ROOT/LIST_DESCENDANTS directly, mask that bit + * out to determine when we must use the filter callback. */ + data.flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS; + + /* If this common code is being used, we assume that all snapshots + * have metadata, and thus can handle METADATA up front as an + * all-or-none filter. XXX This might not always be true, if we + * add the ability to track qcow2 internal snapshots without the + * use of metadata. */ + if ((data.flags & VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA) == + VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) + return 0; + data.flags &= ~VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA; + + /* For ease of coding the visitor, it is easier to zero the LEAVES + * group if both bits are set. */ + if ((data.flags & VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES) == + VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES) + data.flags &= ~VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES; + + if (flags & VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS) { + if (from->def) + virDomainSnapshotForEachDescendant(from, + virDomainSnapshotObjListCopyNames, + &data); + else if (names || data.flags) + virHashForEach(snapshots->objs, virDomainSnapshotObjListCopyNames, + &data); + else + data.count = virHashSize(snapshots->objs); + } else if (names || data.flags) { + virDomainSnapshotForEachChild(from, + virDomainSnapshotObjListCopyNames, &data); + } else { + data.count = from->nchildren; + } + + if (data.error) { + for (i = 0; i < data.count; i++) + VIR_FREE(names[i]); + return -1; + } + + return data.count; +} + +int +virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr from, + unsigned int flags) +{ + return virDomainSnapshotObjListGetNames(snapshots, from, NULL, 0, flags); +} + +virDomainSnapshotObjPtr +virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, + const char *name) +{ + return name ? virHashLookup(snapshots->objs, name) : &snapshots->metaroot; +} + +void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot) +{ + virHashRemoveEntry(snapshots->objs, snapshot->def->name); +} + +int +virDomainSnapshotForEach(virDomainSnapshotObjListPtr snapshots, + virHashIterator iter, + void *data) +{ + return virHashForEach(snapshots->objs, iter, data); +} + +/* 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(virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data) +{ + virDomainSnapshotObjPtr child = snapshot->first_child; + + while (child) { + virDomainSnapshotObjPtr next = child->sibling; + (iter)(child, child->def->name, data); + child = next; + } + + return snapshot->nchildren; +} + +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; + + curr->number += 1 + virDomainSnapshotForEachDescendant(obj, + curr->iter, + curr->data); + (curr->iter)(payload, name, curr->data); +} + +/* 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(virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data) +{ + struct snapshot_act_on_descendant act; + + act.number = 0; + act.iter = iter; + act.data = data; + virDomainSnapshotForEachChild(snapshot, + virDomainSnapshotActOnDescendant, &act); + + return act.number; +} + +/* Struct and callback function used as a hash table callback; each call + * inspects the pre-existing snapshot->def->parent field, and adjusts + * the snapshot->parent field as well as the parent's child fields to + * wire up the hierarchical relations for the given snapshot. The error + * indicator gets set if a parent is missing or a requested parent would + * cause a circular parent chain. */ +struct snapshot_set_relation { + virDomainSnapshotObjListPtr snapshots; + int err; +}; +static void +virDomainSnapshotSetRelations(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data) +{ + virDomainSnapshotObjPtr obj = payload; + struct snapshot_set_relation *curr = data; + virDomainSnapshotObjPtr tmp; + + obj->parent = virDomainSnapshotFindByName(curr->snapshots, + obj->def->parent); + if (!obj->parent) { + curr->err = -1; + obj->parent = &curr->snapshots->metaroot; + VIR_WARN("snapshot %s lacks parent", obj->def->name); + } else { + tmp = obj->parent; + while (tmp && tmp->def) { + if (tmp == obj) { + curr->err = -1; + obj->parent = &curr->snapshots->metaroot; + VIR_WARN("snapshot %s in circular chain", obj->def->name); + break; + } + tmp = tmp->parent; + } + } + obj->parent->nchildren++; + obj->sibling = obj->parent->first_child; + obj->parent->first_child = obj; +} + +/* Populate parent link and child count of all snapshots, with all + * relations starting as 0/NULL. Return 0 on success, -1 if a parent + * is missing or if a circular relationship was requested. */ +int +virDomainSnapshotUpdateRelations(virDomainSnapshotObjListPtr snapshots) +{ + struct snapshot_set_relation act = { snapshots, 0 }; + + virHashForEach(snapshots->objs, virDomainSnapshotSetRelations, &act); + return act.err; +} + +/* Prepare to reparent or delete snapshot, by removing it from its + * current listed parent. Note that when bulk removing all children + * of a parent, it is faster to just 0 the count rather than calling + * this function on each child. */ +void +virDomainSnapshotDropParent(virDomainSnapshotObjPtr snapshot) +{ + virDomainSnapshotObjPtr prev = NULL; + virDomainSnapshotObjPtr curr = NULL; + + snapshot->parent->nchildren--; + curr = snapshot->parent->first_child; + while (curr != snapshot) { + if (!curr) { + VIR_WARN("inconsistent snapshot relations"); + return; + } + prev = curr; + curr = curr->sibling; + } + if (prev) + prev->sibling = snapshot->sibling; + else + snapshot->parent->first_child = snapshot->sibling; + snapshot->parent = NULL; + snapshot->sibling = NULL; +} + +int +virDomainListSnapshots(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr from, + virDomainPtr dom, + virDomainSnapshotPtr **snaps, + unsigned int flags) +{ + int count = virDomainSnapshotObjListNum(snapshots, from, flags); + virDomainSnapshotPtr *list; + char **names; + int ret = -1; + int i; + + if (!snaps) + return count; + if (VIR_ALLOC_N(names, count) < 0 || + VIR_ALLOC_N(list, count + 1) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (virDomainSnapshotObjListGetNames(snapshots, from, names, count, + flags) < 0) + goto cleanup; + for (i = 0; i < count; i++) + if ((list[i] = virGetDomainSnapshot(dom, names[i])) == NULL) + goto cleanup; + + ret = count; + *snaps = list; + +cleanup: + for (i = 0; i < count; i++) + VIR_FREE(names[i]); + VIR_FREE(names); + if (ret < 0 && list) { + for (i = 0; i < count; i++) + virObjectUnref(list[i]); + VIR_FREE(list); + } + return ret; +} diff --git a/src/conf/snapshot_conf.h b/src/conf/snapshot_conf.h new file mode 100644 index 0000000..314c4d1 --- /dev/null +++ b/src/conf/snapshot_conf.h @@ -0,0 +1,157 @@ +/* + * snapshot_conf.h: domain snapshot XML processing + * + * Copyright (C) 2006-2012 Red Hat, Inc. + * Copyright (C) 2006-2008 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * <http://www.gnu.org/licenses/>. + * + * Author: Daniel P. Berrange <berrange@redhat.com> + */ + +#ifndef __SNAPSHOT_CONF_H +# define __SNAPSHOT_CONF_H + +# include "internal.h" +# include "domain_conf.h" + +/* Items related to snapshot state */ + +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 +}; + +enum virDomainSnapshotState { + /* Inherit the VIR_DOMAIN_* states from virDomainState. */ + VIR_DOMAIN_DISK_SNAPSHOT = VIR_DOMAIN_LAST, + VIR_DOMAIN_SNAPSHOT_STATE_LAST +}; + +/* 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 { + /* Public XML. */ + char *name; + char *description; + 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. */ + bool current; /* At most one snapshot in the list should have this set */ +}; + +struct _virDomainSnapshotObj { + virDomainSnapshotDefPtr def; /* non-NULL except for metaroot */ + + virDomainSnapshotObjPtr parent; /* non-NULL except for metaroot, before + virDomainSnapshotUpdateRelations, or + after virDomainSnapshotDropParent */ + virDomainSnapshotObjPtr sibling; /* NULL if last child of parent */ + size_t nchildren; + virDomainSnapshotObjPtr first_child; /* NULL if no children */ +}; + +virDomainSnapshotObjListPtr virDomainSnapshotObjListNew(void); +void virDomainSnapshotObjListFree(virDomainSnapshotObjListPtr snapshots); + +typedef enum { + VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE = 1 << 0, + VIR_DOMAIN_SNAPSHOT_PARSE_DISKS = 1 << 1, + VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL = 1 << 2, +} virDomainSnapshotParseFlags; + +virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, + virCapsPtr caps, + unsigned int expectedVirtTypes, + unsigned int flags); +void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def); +char *virDomainSnapshotDefFormat(const 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); + +int virDomainSnapshotObjListGetNames(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr from, + char **const names, int maxnames, + unsigned int flags); +int virDomainSnapshotObjListNum(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr from, + unsigned int flags); +virDomainSnapshotObjPtr virDomainSnapshotFindByName(const virDomainSnapshotObjListPtr snapshots, + const char *name); +void virDomainSnapshotObjListRemove(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr snapshot); +int virDomainSnapshotForEach(virDomainSnapshotObjListPtr snapshots, + virHashIterator iter, + void *data); +int virDomainSnapshotForEachChild(virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data); +int virDomainSnapshotForEachDescendant(virDomainSnapshotObjPtr snapshot, + virHashIterator iter, + void *data); +int virDomainSnapshotUpdateRelations(virDomainSnapshotObjListPtr snapshots); +void virDomainSnapshotDropParent(virDomainSnapshotObjPtr snapshot); + +# define VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA \ + (VIR_DOMAIN_SNAPSHOT_LIST_METADATA | \ + VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) + +# define VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES \ + (VIR_DOMAIN_SNAPSHOT_LIST_LEAVES | \ + VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES) + +# define VIR_DOMAIN_SNAPSHOT_FILTERS_ALL \ + (VIR_DOMAIN_SNAPSHOT_FILTERS_METADATA | \ + VIR_DOMAIN_SNAPSHOT_FILTERS_LEAVES) + +int virDomainListSnapshots(virDomainSnapshotObjListPtr snapshots, + virDomainSnapshotObjPtr from, + virDomainPtr dom, + virDomainSnapshotPtr **snaps, + unsigned int flags); + +VIR_ENUM_DECL(virDomainDiskSnapshot) +VIR_ENUM_DECL(virDomainSnapshotState) + +#endif /* __SNAPSHOT_CONF_H */ diff --git a/src/esx/esx_driver.c b/src/esx/esx_driver.c index 72a7acc..e57296a 100644 --- a/src/esx/esx_driver.c +++ b/src/esx/esx_driver.c @@ -26,6 +26,7 @@ #include "internal.h" #include "domain_conf.h" +#include "snapshot_conf.h" #include "virauth.h" #include "util.h" #include "memory.h" diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 4ca3047..40eafcb 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -38,6 +38,7 @@ #include "domain_nwfilter.h" #include "domain_audit.h" #include "domain_conf.h" +#include "snapshot_conf.h" #include "network/bridge_driver.h" #include "virnetdevtap.h" #include "base64.h" diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index b96087e..dff53cf 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -1,7 +1,7 @@ /* * qemu_domain.h: QEMU domain private state * - * Copyright (C) 2006-2011 Red Hat, Inc. + * Copyright (C) 2006-2012 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * This library is free software; you can redistribute it and/or @@ -26,6 +26,7 @@ # include "threads.h" # include "domain_conf.h" +# include "snapshot_conf.h" # include "qemu_monitor.h" # include "qemu_agent.h" # include "qemu_conf.h" diff --git a/src/vbox/vbox_tmpl.c b/src/vbox/vbox_tmpl.c index 4cdb11c..48f371f 100644 --- a/src/vbox/vbox_tmpl.c +++ b/src/vbox/vbox_tmpl.c @@ -43,6 +43,7 @@ #include "internal.h" #include "datatypes.h" #include "domain_conf.h" +#include "snapshot_conf.h" #include "network_conf.h" #include "virterror_internal.h" #include "domain_event.h" -- 1.7.11.2

The name 'virDomainDiskSnapshot' didn't fit in with our normal conventions of using a prefix hinting that it is related to a virDomainSnapshotPtr. Also, a future patch will reuse the enum for declaring where the VM memory is stored. * src/conf/snapshot_conf.h (virDomainDiskSnapshot): Rename... (virDomainSnapshotLocation): ...to this. (_virDomainSnapshotDiskDef): Update clients. * src/conf/domain_conf.h (_virDomainDiskDef): Likewise. * src/libvirt_private.syms (domain_conf.h): Likewise. * src/conf/domain_conf.c (virDomainDiskDefParseXML) (virDomainDiskDefFormat): Likewise. * src/conf/snapshot_conf.c: (virDomainSnapshotDiskDefParseXML) (virDomainSnapshotAlignDisks, virDomainSnapshotDefFormat): Likewise. * src/qemu/qemu_driver.c (qemuDomainSnapshotDiskPrepare) (qemuDomainSnapshotCreateSingleDiskActive) (qemuDomainSnapshotCreateDiskActive, qemuDomainSnapshotCreateXML): Likewise. --- src/conf/domain_conf.c | 8 ++++---- src/conf/domain_conf.h | 2 +- src/conf/snapshot_conf.c | 16 +++++++++------- src/conf/snapshot_conf.h | 16 ++++++++-------- src/libvirt_private.syms | 4 ++-- src/qemu/qemu_driver.c | 19 ++++++++++--------- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index e2700fa..e5069dc 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -3594,7 +3594,7 @@ virDomainDiskDefParseXML(virCapsPtr caps, } if (snapshot) { - def->snapshot = virDomainDiskSnapshotTypeFromString(snapshot); + def->snapshot = virDomainSnapshotLocationTypeFromString(snapshot); if (def->snapshot <= 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unknown disk snapshot setting '%s'"), @@ -3602,7 +3602,7 @@ virDomainDiskDefParseXML(virCapsPtr caps, goto error; } } else if (def->readonly) { - def->snapshot = VIR_DOMAIN_DISK_SNAPSHOT_NO; + def->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE; } if (rawio) { @@ -11045,9 +11045,9 @@ virDomainDiskDefFormat(virBufferPtr buf, } } if (def->snapshot && - !(def->snapshot == VIR_DOMAIN_DISK_SNAPSHOT_NO && def->readonly)) + !(def->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE && def->readonly)) virBufferAsprintf(buf, " snapshot='%s'", - virDomainDiskSnapshotTypeToString(def->snapshot)); + virDomainSnapshotLocationTypeToString(def->snapshot)); virBufferAddLit(buf, ">\n"); if (def->driverName || def->driverType || def->cachemode || diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 4b4719e..d7a55b9 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -562,7 +562,7 @@ struct _virDomainDiskDef { int ioeventfd; int event_idx; int copy_on_read; - int snapshot; /* enum virDomainDiskSnapshot, snapshot_conf.h */ + int snapshot; /* enum virDomainSnapshotLocation, snapshot_conf.h */ int startupPolicy; /* enum virDomainStartupPolicy */ unsigned int readonly : 1; unsigned int shared : 1; diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index 894a74c..b9eb74c 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -51,7 +51,7 @@ #define VIR_FROM_THIS VIR_FROM_DOMAIN_SNAPSHOT -VIR_ENUM_IMPL(virDomainDiskSnapshot, VIR_DOMAIN_DISK_SNAPSHOT_LAST, +VIR_ENUM_IMPL(virDomainSnapshotLocation, VIR_DOMAIN_SNAPSHOT_LOCATION_LAST, "default", "no", "internal", @@ -120,7 +120,7 @@ virDomainSnapshotDiskDefParseXML(xmlNodePtr node, snapshot = virXMLPropString(node, "snapshot"); if (snapshot) { - def->snapshot = virDomainDiskSnapshotTypeFromString(snapshot); + def->snapshot = virDomainSnapshotLocationTypeFromString(snapshot); if (def->snapshot <= 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unknown disk snapshot setting '%s'"), @@ -144,7 +144,7 @@ virDomainSnapshotDiskDefParseXML(xmlNodePtr node, } if (!def->snapshot && (def->file || def->driverType)) - def->snapshot = VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL; + def->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; ret = 0; cleanup: @@ -390,14 +390,16 @@ virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def, disk->snapshot = disk_snapshot; } else if (disk_snapshot && require_match && disk->snapshot != disk_snapshot) { - const char *tmp = virDomainDiskSnapshotTypeToString(disk_snapshot); + const char *tmp; + + tmp = virDomainSnapshotLocationTypeToString(disk_snapshot); virReportError(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) { + disk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("file '%s' for disk '%s' requires " "use of external snapshot mode"), @@ -445,7 +447,7 @@ virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def, for (i = 0; i < def->ndisks; i++) { virDomainSnapshotDiskDefPtr disk = &def->disks[i]; - if (disk->snapshot == VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL && + if (disk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL && !disk->file) { const char *original = def->dom->disks[i]->src; const char *tmp; @@ -534,7 +536,7 @@ char *virDomainSnapshotDefFormat(const char *domain_uuid, virBufferEscapeString(&buf, " <disk name='%s'", disk->name); if (disk->snapshot) virBufferAsprintf(&buf, " snapshot='%s'", - virDomainDiskSnapshotTypeToString(disk->snapshot)); + virDomainSnapshotLocationTypeToString(disk->snapshot)); if (disk->file || disk->driverType) { virBufferAddLit(&buf, ">\n"); if (disk->driverType) diff --git a/src/conf/snapshot_conf.h b/src/conf/snapshot_conf.h index 314c4d1..7d46a82 100644 --- a/src/conf/snapshot_conf.h +++ b/src/conf/snapshot_conf.h @@ -29,13 +29,13 @@ /* Items related to snapshot state */ -enum virDomainDiskSnapshot { - VIR_DOMAIN_DISK_SNAPSHOT_DEFAULT = 0, - VIR_DOMAIN_DISK_SNAPSHOT_NO, - VIR_DOMAIN_DISK_SNAPSHOT_INTERNAL, - VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL, +enum virDomainSnapshotLocation { + VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT = 0, + VIR_DOMAIN_SNAPSHOT_LOCATION_NONE, + VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL, + VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL, - VIR_DOMAIN_DISK_SNAPSHOT_LAST + VIR_DOMAIN_SNAPSHOT_LOCATION_LAST }; enum virDomainSnapshotState { @@ -50,7 +50,7 @@ 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 */ + int snapshot; /* enum virDomainSnapshotLocation */ char *file; /* new source file when snapshot is external */ char *driverType; /* file format type of new file */ }; @@ -151,7 +151,7 @@ int virDomainListSnapshots(virDomainSnapshotObjListPtr snapshots, virDomainSnapshotPtr **snaps, unsigned int flags); -VIR_ENUM_DECL(virDomainDiskSnapshot) +VIR_ENUM_DECL(virDomainSnapshotLocation) VIR_ENUM_DECL(virDomainSnapshotState) #endif /* __SNAPSHOT_CONF_H */ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index bf37cb3..1aefb74 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -331,8 +331,6 @@ virDomainDiskIoTypeToString; virDomainDiskPathByName; virDomainDiskRemove; virDomainDiskRemoveByName; -virDomainDiskSnapshotTypeFromString; -virDomainDiskSnapshotTypeToString; virDomainDiskTypeFromString; virDomainDiskTypeToString; virDomainFSDefFree; @@ -473,6 +471,8 @@ virDomainSnapshotFindByName; virDomainSnapshotForEach; virDomainSnapshotForEachChild; virDomainSnapshotForEachDescendant; +virDomainSnapshotLocationTypeFromString; +virDomainSnapshotLocationTypeToString; virDomainSnapshotObjListGetNames; virDomainSnapshotObjListNum; virDomainSnapshotObjListRemove; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 11e043f..8264bf2 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -10039,7 +10039,7 @@ qemuDomainSnapshotDiskPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def, virDomainSnapshotDiskDefPtr disk = &def->disks[i]; switch (disk->snapshot) { - case VIR_DOMAIN_DISK_SNAPSHOT_INTERNAL: + case VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL: if (active) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("active qemu domains require external disk " @@ -10059,7 +10059,7 @@ qemuDomainSnapshotDiskPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def, found = true; break; - case VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL: + case VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL: if (!disk->driverType) { if (!(disk->driverType = strdup("qcow2"))) { virReportOOMError(); @@ -10091,10 +10091,10 @@ qemuDomainSnapshotDiskPrepare(virDomainObjPtr vm, virDomainSnapshotDefPtr def, external++; break; - case VIR_DOMAIN_DISK_SNAPSHOT_NO: + case VIR_DOMAIN_SNAPSHOT_LOCATION_NONE: break; - case VIR_DOMAIN_DISK_SNAPSHOT_DEFAULT: + case VIR_DOMAIN_SNAPSHOT_LOCATION_DEFAULT: default: virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unexpected code path")); @@ -10149,7 +10149,7 @@ qemuDomainSnapshotCreateSingleDiskActive(struct qemud_driver *driver, char *origdriver = NULL; bool need_unlink = false; - if (snap->snapshot != VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL) { + if (snap->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unexpected code path")); return -1; @@ -10400,7 +10400,7 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn, for (i = 0; i < snap->def->ndisks; i++) { virDomainDiskDefPtr persistDisk = NULL; - if (snap->def->disks[i].snapshot == VIR_DOMAIN_DISK_SNAPSHOT_NO) + if (snap->def->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) continue; if (vm->newDef) { int indx = virDomainDiskIndexByName(vm->newDef, @@ -10430,7 +10430,8 @@ qemuDomainSnapshotCreateDiskActive(virConnectPtr conn, while (--i >= 0) { virDomainDiskDefPtr persistDisk = NULL; - if (snap->def->disks[i].snapshot == VIR_DOMAIN_DISK_SNAPSHOT_NO) + if (snap->def->disks[i].snapshot == + VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) continue; if (vm->newDef) { int indx = virDomainDiskIndexByName(vm->newDef, @@ -10659,7 +10660,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, } if (def->state == VIR_DOMAIN_DISK_SNAPSHOT && def->dom) { if (virDomainSnapshotAlignDisks(def, - VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL, + VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL, false) < 0) goto cleanup; } @@ -10680,7 +10681,7 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, goto cleanup; } if (virDomainSnapshotAlignDisks(def, - VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL, + VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL, false) < 0) goto cleanup; if (qemuDomainSnapshotDiskPrepare(vm, def, &flags) < 0) -- 1.7.11.2

Each <domainsnapshot> can now contain an optional <memory> element that describes how the VM state was handled, similar to disk snapshots. The new element will always appear in output; for back-compat, an input that lacks the element will assume 'no' or 'internal' according to the domain state. Along with this change, it is now possible to pass <disks> in the XML for an offline snapshot; this also needs to be wired up in a future patch, to make it possible to choose internal vs. external on a per-disk basis for each disk in an offline domain. At that point, using the --disk-only flag for an offline domain will be able to work. For some examples below, remember that qemu supports the following snapshot actions: qemu-img: offline external and internal disk savevm: online internal VM and disk migrate: online external VM transaction: online external disk ===== <domainsnapshot> <memory snapshot='no'/> ... </domainsnapshot> implies that there is no VM state saved (mandatory for offline and disk-only snapshots, not possible otherwise); using qemu-img for offline domains and transaction for online. ===== <domainsnapshot> <memory snapshot='internal'/> ... </domainsnapshot> state is saved inside one of the disks (as in qemu's 'savevm' system checkpoint implementation). If needed in the future, we can also add an attribute pointing out _which_ disk saved the internal state; maybe disk='vda'. ===== <domainsnapshot> <memory snapshot='external' file='/path/to/state'/> ... </domainsnapshot> This is not wired up yet, but future patches will allow this to control a combination of 'virsh save /path/to/state' plus disk snapshots from the same point in time. ===== So for 0.10.0, I plan to implement this table of combinations, with '*' designating new code and '+' designating existing code reached through new combinations of xml and/or the existing DISK_ONLY flag: domain memory disk disk-only | result ----------------------------------------- offline omit omit any | memory=no disk=int, via qemu-img offline no omit any |+memory=no disk=int, via qemu-img offline omit/no no any | invalid combination (nothing to snapshot) offline omit/no int any |+memory=no disk=int, via qemu-img offline omit/no ext any |*memory=no disk=ext, via qemu-img offline int/ext any any | invalid combination (no memory to save) online omit omit off | memory=int disk=int, via savevm online omit omit on | memory=no disk=default, via transaction online omit no/ext off | unsupported for now online omit no on | invalid combination (nothing to snapshot) online omit ext on | memory=no disk=ext, via transaction online omit int off |+memory=int disk=int, via savevm online omit int on | unsupported for now online no omit any |+memory=no disk=default, via transaction online no no any | invalid combination (nothing to snapshot) online no int any | unsupported for now online no ext any |+memory=no disk=ext, via transaction online int/ext any on | invalid combination (disk-only vs. memory) online int omit off |+memory=int disk=int, via savevm online int no/ext off | unsupported for now online int int off |+memory=int disk=int, via savevm online ext omit off |*memory=ext disk=default, via migrate+trans online ext no off |+memory=ext disk=no, via migrate online ext int off | unsupported for now online ext ext off |*memory=ext disk=ext, via migrate+transaction * docs/schemas/domainsnapshot.rng (memory): New RNG element. * docs/formatsnapshot.html.in: Document it. * src/conf/snapshot_conf.h (virDomainSnapshotDef): New fields. * src/conf/domain_conf.c (virDomainSnapshotDefFree) (virDomainSnapshotDefParseString, virDomainSnapshotDefFormat): Manage new fields. * tests/domainsnapshotxml2xmltest.c: New test. * tests/domainsnapshotxml2xmlin/*.xml: Update existing tests. * tests/domainsnapshotxml2xmlout/*.xml: Likewise. --- docs/formatsnapshot.html.in | 11 +++++ docs/schemas/domainsnapshot.rng | 23 +++++++++ src/conf/snapshot_conf.c | 55 ++++++++++++++++++++-- src/conf/snapshot_conf.h | 3 ++ tests/domainsnapshotxml2xmlin/external_vm.xml | 10 ++++ tests/domainsnapshotxml2xmlin/noparent.xml | 9 ++++ tests/domainsnapshotxml2xmlout/all_parameters.xml | 1 + tests/domainsnapshotxml2xmlout/disk_snapshot.xml | 1 + tests/domainsnapshotxml2xmlout/external_vm.xml | 43 +++++++++++++++++ tests/domainsnapshotxml2xmlout/full_domain.xml | 1 + tests/domainsnapshotxml2xmlout/metadata.xml | 1 + tests/domainsnapshotxml2xmlout/noparent.xml | 1 + .../noparent_nodescription.xml | 1 + .../noparent_nodescription_noactive.xml | 1 + tests/domainsnapshotxml2xmltest.c | 1 + 15 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tests/domainsnapshotxml2xmlin/external_vm.xml create mode 100644 tests/domainsnapshotxml2xmlin/noparent.xml create mode 100644 tests/domainsnapshotxml2xmlout/external_vm.xml diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index ec5ebf3..e2dd9a2 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -106,6 +106,17 @@ description is omitted when initially creating the snapshot, then this field will be empty. </dd> + <dt><code>memory</code></dt> + <dd>On input, this is an optional request for how to handle VM + state. For an offline domain or a disk-only snapshot, + attribute <code>snapshot</code> must be <code>no</code>, since + there is no VM state saved; otherwise, the attribute can + be <code>internal</code> if the VM state is piggy-backed with + other internal disk state, or <code>external</code> along with + a second attribute <code>file</code> giving the absolute path + of the file holding the VM state. <span class="since">Since + 0.10.0</span> + </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 diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 0ef0631..230833d 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -31,6 +31,29 @@ </element> </optional> <optional> + <element name='memory'> + <choice> + <attribute name='snapshot'> + <choice> + <value>no</value> + <value>internal</value> + </choice> + </attribute> + <group> + <optional> + <attribute name='snapshot'> + <value>external</value> + </attribute> + </optional> + <attribute name='file'> + <ref name='absFilePath'/> + </attribute> + </group> + </choice> + <empty/> + </element> + </optional> + <optional> <element name='disks'> <zeroOrMore> <ref name='disksnapshot'/> diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index b9eb74c..ce40724 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -96,6 +96,7 @@ void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) VIR_FREE(def->name); VIR_FREE(def->description); VIR_FREE(def->parent); + VIR_FREE(def->file); for (i = 0; i < def->ndisks; i++) virDomainSnapshotDiskDefClear(&def->disks[i]); VIR_FREE(def->disks); @@ -175,6 +176,8 @@ virDomainSnapshotDefParseString(const char *xmlStr, int active; char *tmp; int keepBlanksDefault = xmlKeepBlanksDefault(0); + char *memorySnapshot = NULL; + char *memoryFile = NULL; xml = virXMLParseCtxt(NULL, xmlStr, _("(domain_snapshot)"), &ctxt); if (!xml) { @@ -267,11 +270,48 @@ virDomainSnapshotDefParseString(const char *xmlStr, def->creationTime = tv.tv_sec; } + memorySnapshot = virXPathString("string(./memory/@snapshot)", ctxt); + memoryFile = virXPathString("string(./memory/@file)", ctxt); + if (memorySnapshot) { + def->memory = virDomainSnapshotLocationTypeFromString(memorySnapshot); + if (def->memory <= 0) { + virReportError(VIR_ERR_XML_ERROR, + _("unknown memory snapshot setting '%s'"), + memorySnapshot); + goto cleanup; + } + if (memoryFile && + def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { + virReportError(VIR_ERR_XML_ERROR, + _("memory filename '%s' requires external snapshot"), + memoryFile); + goto cleanup; + } + } else if (memoryFile) { + def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; + } else { + def->memory = ((flags & VIR_DOMAIN_SNAPSHOT_PARSE_DISKS || + (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE && + def->state == VIR_DOMAIN_DISK_SNAPSHOT)) ? + VIR_DOMAIN_SNAPSHOT_LOCATION_NONE : + VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL); + } + if ((def->state == VIR_DOMAIN_SHUTOFF || + def->state == VIR_DOMAIN_DISK_SNAPSHOT) && + def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("memory state cannot be saved with offline snapshot")); + goto cleanup; + } + def->file = memoryFile; + memoryFile = NULL; + if ((i = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0) goto cleanup; if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_DISKS || (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE && - def->state == VIR_DOMAIN_DISK_SNAPSHOT)) { + def->state == VIR_DOMAIN_DISK_SNAPSHOT) || + def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { def->ndisks = i; if (def->ndisks && VIR_ALLOC_N(def->disks, def->ndisks) < 0) { virReportOOMError(); @@ -303,6 +343,8 @@ cleanup: VIR_FREE(creation); VIR_FREE(state); VIR_FREE(nodes); + VIR_FREE(memorySnapshot); + VIR_FREE(memoryFile); xmlXPathFreeContext(ctxt); if (ret == NULL) virDomainSnapshotDefFree(def); @@ -524,8 +566,15 @@ char *virDomainSnapshotDefFormat(const 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) { + if (def->memory) { + virBufferAsprintf(&buf, " <memory snapshot='%s'", + virDomainSnapshotLocationTypeToString(def->memory)); + virBufferEscapeString(&buf, " file='%s'", def->file); + virBufferAddLit(&buf, "/>\n"); + } + /* Only output <disks> on disk-snapshot or external memory snapshot */ + if (def->state == VIR_DOMAIN_DISK_SNAPSHOT || + def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) { virBufferAddLit(&buf, " <disks>\n"); for (i = 0; i < def->ndisks; i++) { virDomainSnapshotDiskDefPtr disk = &def->disks[i]; diff --git a/src/conf/snapshot_conf.h b/src/conf/snapshot_conf.h index 7d46a82..d17b3ff 100644 --- a/src/conf/snapshot_conf.h +++ b/src/conf/snapshot_conf.h @@ -66,6 +66,9 @@ struct _virDomainSnapshotDef { long long creationTime; /* in seconds */ int state; /* enum virDomainSnapshotState */ + int memory; /* enum virDomainMemorySnapshot */ + char *file; /* memory state file when snapshot is external */ + size_t ndisks; /* should not exceed dom->ndisks */ virDomainSnapshotDiskDef *disks; diff --git a/tests/domainsnapshotxml2xmlin/external_vm.xml b/tests/domainsnapshotxml2xmlin/external_vm.xml new file mode 100644 index 0000000..3bcd150 --- /dev/null +++ b/tests/domainsnapshotxml2xmlin/external_vm.xml @@ -0,0 +1,10 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <state>running</state> + <memory snapshot='external' file='/dev/HostVG/GuestMemory'/> + <parent> + <name>earlier_snap</name> + </parent> + <creationTime>1272917631</creationTime> +</domainsnapshot> diff --git a/tests/domainsnapshotxml2xmlin/noparent.xml b/tests/domainsnapshotxml2xmlin/noparent.xml new file mode 100644 index 0000000..cbac0d8 --- /dev/null +++ b/tests/domainsnapshotxml2xmlin/noparent.xml @@ -0,0 +1,9 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <state>running</state> + <creationTime>1272917631</creationTime> + <domain> + <uuid>9d37b878-a7cc-9f9a-b78f-49b3abad25a8</uuid> + </domain> +</domainsnapshot> diff --git a/tests/domainsnapshotxml2xmlout/all_parameters.xml b/tests/domainsnapshotxml2xmlout/all_parameters.xml index eb2ee85..4178ac6 100644 --- a/tests/domainsnapshotxml2xmlout/all_parameters.xml +++ b/tests/domainsnapshotxml2xmlout/all_parameters.xml @@ -6,6 +6,7 @@ <name>earlier_snap</name> </parent> <creationTime>1272917631</creationTime> + <memory snapshot='internal'/> <domain> <uuid>9d37b878-a7cc-9f9a-b78f-49b3abad25a8</uuid> </domain> diff --git a/tests/domainsnapshotxml2xmlout/disk_snapshot.xml b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml index 0a4b179..57aef16 100644 --- a/tests/domainsnapshotxml2xmlout/disk_snapshot.xml +++ b/tests/domainsnapshotxml2xmlout/disk_snapshot.xml @@ -6,6 +6,7 @@ <name>earlier_snap</name> </parent> <creationTime>1272917631</creationTime> + <memory snapshot='no'/> <disks> <disk name='hda' snapshot='no'/> <disk name='hdb' snapshot='no'/> diff --git a/tests/domainsnapshotxml2xmlout/external_vm.xml b/tests/domainsnapshotxml2xmlout/external_vm.xml new file mode 100644 index 0000000..8814bce --- /dev/null +++ b/tests/domainsnapshotxml2xmlout/external_vm.xml @@ -0,0 +1,43 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <state>running</state> + <parent> + <name>earlier_snap</name> + </parent> + <creationTime>1272917631</creationTime> + <memory snapshot='external' file='/dev/HostVG/GuestMemory'/> + <disks> + <disk name='hda' snapshot='no'/> + </disks> + <domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <metadata> + <app1:foo xmlns:app1="http://foo.org/">fooish</app1:foo> + <app2:bar xmlns:app2="http://bar.com/" maman="baz">barish</app2:bar> + </metadata> + <memory unit='KiB'>219100</memory> + <currentMemory unit='KiB'>219100</currentMemory> + <vcpu placement='static' 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' snapshot='no'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <memballoon model='virtio'/> + </devices> + </domain> +</domainsnapshot> diff --git a/tests/domainsnapshotxml2xmlout/full_domain.xml b/tests/domainsnapshotxml2xmlout/full_domain.xml index 27cf41d..65d1469 100644 --- a/tests/domainsnapshotxml2xmlout/full_domain.xml +++ b/tests/domainsnapshotxml2xmlout/full_domain.xml @@ -6,6 +6,7 @@ <name>earlier_snap</name> </parent> <creationTime>1272917631</creationTime> + <memory snapshot='internal'/> <domain type='qemu'> <name>QEMUGuest1</name> <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> diff --git a/tests/domainsnapshotxml2xmlout/metadata.xml b/tests/domainsnapshotxml2xmlout/metadata.xml index 93c9f39..f961458 100644 --- a/tests/domainsnapshotxml2xmlout/metadata.xml +++ b/tests/domainsnapshotxml2xmlout/metadata.xml @@ -6,6 +6,7 @@ <name>earlier_snap</name> </parent> <creationTime>1272917631</creationTime> + <memory snapshot='internal'/> <domain type='qemu'> <name>QEMUGuest1</name> <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> diff --git a/tests/domainsnapshotxml2xmlout/noparent.xml b/tests/domainsnapshotxml2xmlout/noparent.xml index cbac0d8..0cbbb65 100644 --- a/tests/domainsnapshotxml2xmlout/noparent.xml +++ b/tests/domainsnapshotxml2xmlout/noparent.xml @@ -3,6 +3,7 @@ <description>!@#$%^</description> <state>running</state> <creationTime>1272917631</creationTime> + <memory snapshot='internal'/> <domain> <uuid>9d37b878-a7cc-9f9a-b78f-49b3abad25a8</uuid> </domain> diff --git a/tests/domainsnapshotxml2xmlout/noparent_nodescription.xml b/tests/domainsnapshotxml2xmlout/noparent_nodescription.xml index 0de202d..4eb4016 100644 --- a/tests/domainsnapshotxml2xmlout/noparent_nodescription.xml +++ b/tests/domainsnapshotxml2xmlout/noparent_nodescription.xml @@ -3,5 +3,6 @@ <description>!@#$%^</description> <state>running</state> <creationTime>1272917631</creationTime> + <memory snapshot='internal'/> <active>1</active> </domainsnapshot> diff --git a/tests/domainsnapshotxml2xmlout/noparent_nodescription_noactive.xml b/tests/domainsnapshotxml2xmlout/noparent_nodescription_noactive.xml index 25b60f3..94d59a3 100644 --- a/tests/domainsnapshotxml2xmlout/noparent_nodescription_noactive.xml +++ b/tests/domainsnapshotxml2xmlout/noparent_nodescription_noactive.xml @@ -3,4 +3,5 @@ <description>!@#$%^</description> <state>running</state> <creationTime>1272917631</creationTime> + <memory snapshot='no'/> </domainsnapshot> diff --git a/tests/domainsnapshotxml2xmltest.c b/tests/domainsnapshotxml2xmltest.c index e363c99..84278d6 100644 --- a/tests/domainsnapshotxml2xmltest.c +++ b/tests/domainsnapshotxml2xmltest.c @@ -110,6 +110,7 @@ mymain(void) DO_TEST("noparent_nodescription", NULL, 1); DO_TEST("noparent", "9d37b878-a7cc-9f9a-b78f-49b3abad25a8", 0); DO_TEST("metadata", "c7a5fdbd-edaf-9455-926a-d65c16db1809", 0); + DO_TEST("external_vm", "c7a5fdbd-edaf-9455-926a-d65c16db1809", 0); virCapabilitiesFree(driver.caps); -- 1.7.11.2

On 18.08.2012 04:08, Eric Blake wrote:
I've determined that it is possible to mix migration to file with disk snapshots on existing qemu 1.1 in such a way to take a live system checkpoint snapshot of a system without the downtime of either 'virsh snapshot-create' or 'virsh save'; without the loss in disk state of 'virsh save'; and without the fsck penalties of 'virsh snapshot-create --disk-only'; basically by combining the best of those three approaches.
I'm also quite tired of 'virsh snapshot-create --disk-only' failing for an offline domain, and have upcoming patches to drive qemu-img to do that for an offline image.
Unfortunately, qemu 1.2 missed out on adding the 'drive-mirror' or 'block-commit' commands, so I still can't do quite everything I want with snapshots (in particular, I can't preserve the original filename; although you can do a snapshot/blockpull/snapshot to get back to the original filename with twice the work). But I think this series still leaves room for future enhancements as future qemu provides the means.
Although I'm still in the middle of polishing the src/qemu patches, I'd at least like to post this series of prep-work to document how I plan to expose it all, and to make sure my design decisions are on track. The first three can be applied now, the fourth should probably not be applied until I actually have later patches using the new XML, hopefully still in time for 0.10.0.
Eric Blake (4): snapshot: make virDomainSnapshotObjList opaque snapshot: split snapshot conf code into own file snapshot: rename an enum snapshot: new XML for external system checkpoint
docs/formatsnapshot.html.in | 11 + docs/schemas/domainsnapshot.rng | 23 + po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/conf/domain_conf.c | 933 +----------------- src/conf/domain_conf.h | 143 +-- src/conf/snapshot_conf.c | 1021 ++++++++++++++++++++ src/conf/snapshot_conf.h | 160 +++ src/esx/esx_driver.c | 1 + src/libvirt_private.syms | 5 +- src/qemu/qemu_command.c | 1 + src/qemu/qemu_domain.c | 7 +- src/qemu/qemu_domain.h | 3 +- src/qemu/qemu_driver.c | 69 +- src/qemu/qemu_migration.c | 2 +- src/vbox/vbox_tmpl.c | 1 + tests/domainsnapshotxml2xmlin/external_vm.xml | 10 + tests/domainsnapshotxml2xmlin/noparent.xml | 9 + tests/domainsnapshotxml2xmlout/all_parameters.xml | 1 + tests/domainsnapshotxml2xmlout/disk_snapshot.xml | 1 + tests/domainsnapshotxml2xmlout/external_vm.xml | 43 + tests/domainsnapshotxml2xmlout/full_domain.xml | 1 + tests/domainsnapshotxml2xmlout/metadata.xml | 1 + tests/domainsnapshotxml2xmlout/noparent.xml | 1 + .../noparent_nodescription.xml | 1 + .../noparent_nodescription_noactive.xml | 1 + tests/domainsnapshotxml2xmltest.c | 1 + 27 files changed, 1365 insertions(+), 1089 deletions(-) create mode 100644 src/conf/snapshot_conf.c create mode 100644 src/conf/snapshot_conf.h create mode 100644 tests/domainsnapshotxml2xmlin/external_vm.xml create mode 100644 tests/domainsnapshotxml2xmlin/noparent.xml create mode 100644 tests/domainsnapshotxml2xmlout/external_vm.xml
Eric, I could apply the first patch (however using 3-way merge) but cannot the second. Can you please (rebase? and) resend? Or is this intended to be applied on the top of another patchset: Applying: snapshot: split snapshot conf code into own file fatal: sha1 information is lacking or useless (src/conf/domain_conf.c). Repository lacks necessary blobs to fall back on 3-way merge. Cannot fall back to three-way merge. Patch failed at 0001 snapshot: split snapshot conf code into own file When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort". Michal

On 08/22/2012 07:46 AM, Michal Privoznik wrote:
Although I'm still in the middle of polishing the src/qemu patches, I'd at least like to post this series of prep-work to document how I plan to expose it all, and to make sure my design decisions are on track. The first three can be applied now, the fourth should probably not be applied until I actually have later patches using the new XML, hopefully still in time for 0.10.0.
Looks like I missed rc1, so while the first three patches might still be okay for 0.10.0, the new XML is now 0.10.1 material.
I could apply the first patch (however using 3-way merge) but cannot the second. Can you please (rebase? and) resend? Or is this intended to be applied on the top of another patchset:
Yes, I'll rebase and resend (file splits tend to be hairy if not applied right away). -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
participants (2)
-
Eric Blake
-
Michal Privoznik