[libvirt] [PATCH v4 0/2] snapshot: Store both config and live XML in the

This patchset store both config and live XML in the snapshot XML. To avoid nest 'config' XML one level deeper ('inactive/domain'), it was necessary to create a function that has a new rootname parameter. V4: - Create a new function to format the XML domain choosing the root name Maxiwell S. Garcia (2): qemu: formatting XML from domain def choosing the root name snapshot: Store both config and live XML in the snapshot domain src/conf/domain_conf.c | 35 ++++++++++++++++++++++++++--------- src/conf/domain_conf.h | 6 ++++++ src/conf/moment_conf.c | 1 + src/conf/moment_conf.h | 11 +++++++++++ src/conf/snapshot_conf.c | 22 ++++++++++++++++++++-- src/qemu/qemu_driver.c | 37 ++++++++++++++++++++++++++++--------- 6 files changed, 92 insertions(+), 20 deletions(-) -- 2.20.1

The function virDomainDefFormatInternal() has the predefined root name "domain" to format the XML. But to save both active and inactive domain in the snapshot XML, the new root name "inactiveDomain" was created. So, the new function virDomainDefFormatInternalSetRootName() allows to choose the root name of XML. The former function became a tiny wrapper to call the new function setting the correct parameters. Signed-off-by: Maxiwell S. Garcia <maxiwell@linux.ibm.com> --- src/conf/domain_conf.c | 35 ++++++++++++++++++++++++++--------- src/conf/domain_conf.h | 6 ++++++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index b7a342bb91..3154c07a86 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -21517,10 +21517,11 @@ virDomainDefParseNode(xmlDocPtr xml, virDomainDefPtr def = NULL; virDomainDefPtr ret = NULL; - if (!virXMLNodeNameEqual(root, "domain")) { + if ((!virXMLNodeNameEqual(root, "domain")) && + (!virXMLNodeNameEqual(root, "inactiveDomain"))) { virReportError(VIR_ERR_XML_ERROR, _("unexpected root element <%s>, " - "expecting <domain>"), + "expecting <domain> or <inactiveDomain>"), root->name); goto cleanup; } @@ -28277,17 +28278,29 @@ virDomainDefFormatFeatures(virBufferPtr buf, return virXMLFormatElement(buf, "features", NULL, &childBuf); } - -/* This internal version appends to an existing buffer - * (possibly with auto-indent), rather than flattening - * to string. - * Return -1 on failure. */ int virDomainDefFormatInternal(virDomainDefPtr def, virCapsPtr caps, unsigned int flags, virBufferPtr buf, virDomainXMLOptionPtr xmlopt) +{ + return virDomainDefFormatInternalSetRootName(def, caps, flags, buf, + xmlopt, "domain"); +} + + +/* This internal version appends to an existing buffer + * (possibly with auto-indent), rather than flattening + * to string. + * Return -1 on failure. */ +int +virDomainDefFormatInternalSetRootName(virDomainDefPtr def, + virCapsPtr caps, + unsigned int flags, + virBufferPtr buf, + virDomainXMLOptionPtr xmlopt, + const char *rootname) { unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -28312,7 +28325,11 @@ virDomainDefFormatInternal(virDomainDefPtr def, if (def->id == -1) flags |= VIR_DOMAIN_DEF_FORMAT_INACTIVE; - virBufferAsprintf(buf, "<domain type='%s'", type); + if (!rootname || (STRNEQ(rootname, "domain") && + STRNEQ(rootname, "inactiveDomain"))) + goto error; + + virBufferAsprintf(buf, "<%s type='%s'", rootname, type); if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)) virBufferAsprintf(buf, " id='%d'", def->id); if (def->namespaceData && def->ns.format) @@ -28794,7 +28811,7 @@ virDomainDefFormatInternal(virDomainDefPtr def, virDomainSEVDefFormat(buf, def->sev); virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</domain>\n"); + virBufferAsprintf(buf, "</%s>\n", rootname); if (virBufferCheckError(buf) < 0) goto error; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 33cef5b75c..af1335cc16 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3073,6 +3073,12 @@ int virDomainDefFormatInternal(virDomainDefPtr def, unsigned int flags, virBufferPtr buf, virDomainXMLOptionPtr xmlopt); +int virDomainDefFormatInternalSetRootName(virDomainDefPtr def, + virCapsPtr caps, + unsigned int flags, + virBufferPtr buf, + virDomainXMLOptionPtr xmlopt, + const char *rootname); int virDomainDiskSourceFormat(virBufferPtr buf, virStorageSourcePtr src, -- 2.20.1

On 8/29/19 5:55 PM, Maxiwell S. Garcia wrote:
The function virDomainDefFormatInternal() has the predefined root name "domain" to format the XML. But to save both active and inactive domain in the snapshot XML, the new root name "inactiveDomain" was created. So, the new function virDomainDefFormatInternalSetRootName() allows to choose the root name of XML. The former function became a tiny wrapper to call the new function setting the correct parameters.
Signed-off-by: Maxiwell S. Garcia <maxiwell@linux.ibm.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com> Tested-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/conf/domain_conf.c | 35 ++++++++++++++++++++++++++--------- src/conf/domain_conf.h | 6 ++++++ 2 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index b7a342bb91..3154c07a86 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -21517,10 +21517,11 @@ virDomainDefParseNode(xmlDocPtr xml, virDomainDefPtr def = NULL; virDomainDefPtr ret = NULL;
- if (!virXMLNodeNameEqual(root, "domain")) { + if ((!virXMLNodeNameEqual(root, "domain")) && + (!virXMLNodeNameEqual(root, "inactiveDomain"))) { virReportError(VIR_ERR_XML_ERROR, _("unexpected root element <%s>, " - "expecting <domain>"), + "expecting <domain> or <inactiveDomain>"), root->name); goto cleanup; } @@ -28277,17 +28278,29 @@ virDomainDefFormatFeatures(virBufferPtr buf, return virXMLFormatElement(buf, "features", NULL, &childBuf); }
- -/* This internal version appends to an existing buffer - * (possibly with auto-indent), rather than flattening - * to string. - * Return -1 on failure. */ int virDomainDefFormatInternal(virDomainDefPtr def, virCapsPtr caps, unsigned int flags, virBufferPtr buf, virDomainXMLOptionPtr xmlopt) +{ + return virDomainDefFormatInternalSetRootName(def, caps, flags, buf, + xmlopt, "domain"); +} + + +/* This internal version appends to an existing buffer + * (possibly with auto-indent), rather than flattening + * to string. + * Return -1 on failure. */ +int +virDomainDefFormatInternalSetRootName(virDomainDefPtr def, + virCapsPtr caps, + unsigned int flags, + virBufferPtr buf, + virDomainXMLOptionPtr xmlopt, + const char *rootname) { unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -28312,7 +28325,11 @@ virDomainDefFormatInternal(virDomainDefPtr def, if (def->id == -1) flags |= VIR_DOMAIN_DEF_FORMAT_INACTIVE;
- virBufferAsprintf(buf, "<domain type='%s'", type); + if (!rootname || (STRNEQ(rootname, "domain") && + STRNEQ(rootname, "inactiveDomain"))) + goto error; + + virBufferAsprintf(buf, "<%s type='%s'", rootname, type); if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)) virBufferAsprintf(buf, " id='%d'", def->id); if (def->namespaceData && def->ns.format) @@ -28794,7 +28811,7 @@ virDomainDefFormatInternal(virDomainDefPtr def, virDomainSEVDefFormat(buf, def->sev);
virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</domain>\n"); + virBufferAsprintf(buf, "</%s>\n", rootname);
if (virBufferCheckError(buf) < 0) goto error; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 33cef5b75c..af1335cc16 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3073,6 +3073,12 @@ int virDomainDefFormatInternal(virDomainDefPtr def, unsigned int flags, virBufferPtr buf, virDomainXMLOptionPtr xmlopt); +int virDomainDefFormatInternalSetRootName(virDomainDefPtr def, + virCapsPtr caps, + unsigned int flags, + virBufferPtr buf, + virDomainXMLOptionPtr xmlopt, + const char *rootname);
int virDomainDiskSourceFormat(virBufferPtr buf, virStorageSourcePtr src,

On Thu, Aug 29, 2019 at 17:55:42 -0300, Maxiwell S. Garcia wrote:
The function virDomainDefFormatInternal() has the predefined root name "domain" to format the XML. But to save both active and inactive domain in the snapshot XML, the new root name "inactiveDomain" was created. So, the new function virDomainDefFormatInternalSetRootName() allows to choose the root name of XML. The former function became a tiny wrapper to call the new function setting the correct parameters.
Signed-off-by: Maxiwell S. Garcia <maxiwell@linux.ibm.com> --- src/conf/domain_conf.c | 35 ++++++++++++++++++++++++++--------- src/conf/domain_conf.h | 6 ++++++ 2 files changed, 32 insertions(+), 9 deletions(-)
diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index b7a342bb91..3154c07a86 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -21517,10 +21517,11 @@ virDomainDefParseNode(xmlDocPtr xml, virDomainDefPtr def = NULL; virDomainDefPtr ret = NULL;
- if (!virXMLNodeNameEqual(root, "domain")) { + if ((!virXMLNodeNameEqual(root, "domain")) && + (!virXMLNodeNameEqual(root, "inactiveDomain"))) { virReportError(VIR_ERR_XML_ERROR, _("unexpected root element <%s>, " - "expecting <domain>"), + "expecting <domain> or <inactiveDomain>"), root->name); goto cleanup; }
The check does not belong to virDomainDefParseNode. It should be moved to virDomainDefParse, which is the only place we don't select the domain root element via XPath. Once moved, it must not be changed, since we don't want to allow <inactiveDomain> to be allowed in a domain definition XML files. I've just sent patches moving this code.
@@ -28277,17 +28278,29 @@ virDomainDefFormatFeatures(virBufferPtr buf, return virXMLFormatElement(buf, "features", NULL, &childBuf); }
- -/* This internal version appends to an existing buffer - * (possibly with auto-indent), rather than flattening - * to string. - * Return -1 on failure. */ int virDomainDefFormatInternal(virDomainDefPtr def, virCapsPtr caps, unsigned int flags, virBufferPtr buf, virDomainXMLOptionPtr xmlopt) +{ + return virDomainDefFormatInternalSetRootName(def, caps, flags, buf, + xmlopt, "domain"); +} + + +/* This internal version appends to an existing buffer + * (possibly with auto-indent), rather than flattening + * to string. + * Return -1 on failure. */ +int +virDomainDefFormatInternalSetRootName(virDomainDefPtr def, + virCapsPtr caps, + unsigned int flags, + virBufferPtr buf, + virDomainXMLOptionPtr xmlopt, + const char *rootname) { unsigned char *uuid; char uuidstr[VIR_UUID_STRING_BUFLEN]; @@ -28312,7 +28325,11 @@ virDomainDefFormatInternal(virDomainDefPtr def, if (def->id == -1) flags |= VIR_DOMAIN_DEF_FORMAT_INACTIVE;
- virBufferAsprintf(buf, "<domain type='%s'", type); + if (!rootname || (STRNEQ(rootname, "domain") && + STRNEQ(rootname, "inactiveDomain"))) + goto error;
This would return an error without setting any error message. But it's not a big deal since we don't need runtime checks for programmers' errors and we can just delete this check completely.
+ + virBufferAsprintf(buf, "<%s type='%s'", rootname, type); if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE)) virBufferAsprintf(buf, " id='%d'", def->id); if (def->namespaceData && def->ns.format) @@ -28794,7 +28811,7 @@ virDomainDefFormatInternal(virDomainDefPtr def, virDomainSEVDefFormat(buf, def->sev);
virBufferAdjustIndent(buf, -2); - virBufferAddLit(buf, "</domain>\n"); + virBufferAsprintf(buf, "</%s>\n", rootname);
if (virBufferCheckError(buf) < 0) goto error; diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 33cef5b75c..af1335cc16 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -3073,6 +3073,12 @@ int virDomainDefFormatInternal(virDomainDefPtr def, unsigned int flags, virBufferPtr buf, virDomainXMLOptionPtr xmlopt); +int virDomainDefFormatInternalSetRootName(virDomainDefPtr def, + virCapsPtr caps, + unsigned int flags, + virBufferPtr buf, + virDomainXMLOptionPtr xmlopt, + const char *rootname);
int virDomainDiskSourceFormat(virBufferPtr buf, virStorageSourcePtr src,
With the two problematic hunks removed Reviewed-by: Jiri Denemark <jdenemar@redhat.com> I'll push this once the patches moving the check for "domain" root element are acked.

The snapshot-create operation of running guests saves the live XML and uses it to replace the active and inactive domain in case of revert. So, the config XML is ignored by the snapshot process. This commit changes it and adds the config XML in the snapshot XML as the <inactiveDomain> entry. In case of offline guest, the behavior remains the same and the config XML is saved in the snapshot XML as <domain> entry. The behavior of older snapshots of running guests, that don't have the new <inactiveDomain>, remains the same too. The revert, in this case, overrides both active and inactive domain with the <domain> entry. So, the <inactiveDomain> in the snapshot XML is not required to snapshot work, but it's useful to preserve the config XML of running guests. Signed-off-by: Maxiwell S. Garcia <maxiwell@linux.ibm.com> --- src/conf/moment_conf.c | 1 + src/conf/moment_conf.h | 11 +++++++++++ src/conf/snapshot_conf.c | 22 ++++++++++++++++++++-- src/qemu/qemu_driver.c | 37 ++++++++++++++++++++++++++++--------- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/conf/moment_conf.c b/src/conf/moment_conf.c index fea13f0f97..f54a44b33e 100644 --- a/src/conf/moment_conf.c +++ b/src/conf/moment_conf.c @@ -66,6 +66,7 @@ virDomainMomentDefDispose(void *obj) VIR_FREE(def->description); VIR_FREE(def->parent_name); virDomainDefFree(def->dom); + virDomainDefFree(def->inactiveDom); } /* Provide defaults for creation time and moment name after parsing XML */ diff --git a/src/conf/moment_conf.h b/src/conf/moment_conf.h index 9fdbef2172..70cc47bd70 100644 --- a/src/conf/moment_conf.h +++ b/src/conf/moment_conf.h @@ -36,7 +36,18 @@ struct _virDomainMomentDef { char *parent_name; long long creationTime; /* in seconds */ + /* + * Store the active domain definition in case of online + * guest and the inactive domain definition in case of + * offline guest + */ virDomainDefPtr dom; + + /* + * Store the inactive domain definition in case of online + * guest and leave NULL in case of offline guest + */ + virDomainDefPtr inactiveDom; }; virClassPtr virClassForDomainMomentDef(void); diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index 7996589ad2..cce9a7999c 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -235,6 +235,7 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, virDomainSnapshotDefPtr def = NULL; virDomainSnapshotDefPtr ret = NULL; xmlNodePtr *nodes = NULL; + xmlNodePtr inactiveDomNode = NULL; size_t i; int n; char *creation = NULL, *state = NULL; @@ -244,6 +245,8 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, char *memoryFile = NULL; bool offline = !!(flags & VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE); virSaveCookieCallbacksPtr saveCookie = virDomainXMLOptionGetSaveCookie(xmlopt); + int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; if (!(def = virDomainSnapshotDefNew())) return NULL; @@ -293,8 +296,6 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, * clients will have to decide between best effort * initialization or outright failure. */ if ((tmp = virXPathString("string(./domain/@type)", ctxt))) { - int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | - VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; xmlNodePtr domainNode = virXPathNode("./domain", ctxt); VIR_FREE(tmp); @@ -311,6 +312,16 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, } else { VIR_WARN("parsing older snapshot that lacks domain"); } + + /* /inactiveDomain entry saves the config XML present in a running + * VM. In case of absent, leave parent.inactiveDom NULL and use + * parent.dom for config and live XML. */ + if ((inactiveDomNode = virXPathNode("./inactiveDomain", ctxt))) { + def->parent.inactiveDom = virDomainDefParseNode(ctxt->node->doc, inactiveDomNode, + caps, xmlopt, NULL, domainflags); + if (!def->parent.inactiveDom) + goto cleanup; + } } else if (virDomainXMLOptionRunMomentPostParse(xmlopt, &def->parent) < 0) { goto cleanup; } @@ -908,6 +919,13 @@ virDomainSnapshotDefFormatInternal(virBufferPtr buf, virBufferAddLit(buf, "</domain>\n"); } + if (def->parent.inactiveDom) { + if (virDomainDefFormatInternalSetRootName(def->parent.inactiveDom, caps, + domainflags, buf, xmlopt, + "inactiveDomain") < 0) + goto error; + } + if (virSaveCookieFormatBuf(buf, def->cookie, virDomainXMLOptionGetSaveCookie(xmlopt)) < 0) goto error; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 78f5471b79..67511b705a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15939,6 +15939,13 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) goto endjob; + if (vm->newDef) { + def->parent.inactiveDom = virDomainDefCopy(vm->newDef, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!def->parent.inactiveDom) + goto endjob; + } + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; align_match = false; @@ -16471,6 +16478,7 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuDomainObjPrivatePtr priv; int rc; virDomainDefPtr config = NULL; + virDomainDefPtr inactiveConfig = NULL; virQEMUDriverConfigPtr cfg = NULL; virCapsPtr caps = NULL; bool was_stopped = false; @@ -16572,11 +16580,10 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, * in the failure cases where we know there was no change? */ } - /* Prepare to copy the snapshot inactive xml as the config of this - * domain. - * - * XXX Should domain snapshots track live xml rather - * than inactive xml? */ + /* Prepare to copy the snapshot inactive domain as the config XML + * and the snapshot domain as the live XML. In case of inactive domain + * NULL, both config and live XML will be copied from snapshot domain. + */ if (snap->def->dom) { config = virDomainDefCopy(snap->def->dom, caps, driver->xmlopt, priv->qemuCaps, true); @@ -16584,6 +16591,15 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } + if (snap->def->inactiveDom) { + inactiveConfig = virDomainDefCopy(snap->def->inactiveDom, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!inactiveConfig) + goto endjob; + } else { + inactiveConfig = config; + } + cookie = (qemuDomainSaveCookiePtr) snapdef->cookie; switch ((virDomainSnapshotState) snapdef->state) { @@ -16686,16 +16702,19 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } if (config) { - virDomainObjAssignDef(vm, config, false, NULL); virCPUDefFree(priv->origCPU); VIR_STEAL_PTR(priv->origCPU, origCPU); } + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); } else { /* Transitions 2, 3 */ load: was_stopped = true; + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); if (config) - virDomainObjAssignDef(vm, config, false, NULL); + virDomainObjAssignDef(vm, config, true, NULL); /* No cookie means libvirt which saved the domain was too old to * mess up the CPU definitions. @@ -16781,8 +16800,8 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuProcessEndJob(driver, vm); goto cleanup; } - if (config) - virDomainObjAssignDef(vm, config, false, NULL); + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) { -- 2.20.1

On 8/29/19 5:55 PM, Maxiwell S. Garcia wrote:
The snapshot-create operation of running guests saves the live XML and uses it to replace the active and inactive domain in case of revert. So, the config XML is ignored by the snapshot process. This commit changes it and adds the config XML in the snapshot XML as the <inactiveDomain> entry.
In case of offline guest, the behavior remains the same and the config XML is saved in the snapshot XML as <domain> entry. The behavior of older snapshots of running guests, that don't have the new <inactiveDomain>, remains the same too. The revert, in this case, overrides both active and inactive domain with the <domain> entry. So, the <inactiveDomain> in the snapshot XML is not required to snapshot work, but it's useful to preserve the config XML of running guests.
Signed-off-by: Maxiwell S. Garcia <maxiwell@linux.ibm.com> ---
Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com> Tested-by: Daniel Henrique Barboza <danielhb413@gmail.com>
src/conf/moment_conf.c | 1 + src/conf/moment_conf.h | 11 +++++++++++ src/conf/snapshot_conf.c | 22 ++++++++++++++++++++-- src/qemu/qemu_driver.c | 37 ++++++++++++++++++++++++++++--------- 4 files changed, 60 insertions(+), 11 deletions(-)
diff --git a/src/conf/moment_conf.c b/src/conf/moment_conf.c index fea13f0f97..f54a44b33e 100644 --- a/src/conf/moment_conf.c +++ b/src/conf/moment_conf.c @@ -66,6 +66,7 @@ virDomainMomentDefDispose(void *obj) VIR_FREE(def->description); VIR_FREE(def->parent_name); virDomainDefFree(def->dom); + virDomainDefFree(def->inactiveDom); }
/* Provide defaults for creation time and moment name after parsing XML */ diff --git a/src/conf/moment_conf.h b/src/conf/moment_conf.h index 9fdbef2172..70cc47bd70 100644 --- a/src/conf/moment_conf.h +++ b/src/conf/moment_conf.h @@ -36,7 +36,18 @@ struct _virDomainMomentDef { char *parent_name; long long creationTime; /* in seconds */
+ /* + * Store the active domain definition in case of online + * guest and the inactive domain definition in case of + * offline guest + */ virDomainDefPtr dom; + + /* + * Store the inactive domain definition in case of online + * guest and leave NULL in case of offline guest + */ + virDomainDefPtr inactiveDom; };
virClassPtr virClassForDomainMomentDef(void); diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index 7996589ad2..cce9a7999c 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -235,6 +235,7 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, virDomainSnapshotDefPtr def = NULL; virDomainSnapshotDefPtr ret = NULL; xmlNodePtr *nodes = NULL; + xmlNodePtr inactiveDomNode = NULL; size_t i; int n; char *creation = NULL, *state = NULL; @@ -244,6 +245,8 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, char *memoryFile = NULL; bool offline = !!(flags & VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE); virSaveCookieCallbacksPtr saveCookie = virDomainXMLOptionGetSaveCookie(xmlopt); + int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE;
if (!(def = virDomainSnapshotDefNew())) return NULL; @@ -293,8 +296,6 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, * clients will have to decide between best effort * initialization or outright failure. */ if ((tmp = virXPathString("string(./domain/@type)", ctxt))) { - int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | - VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; xmlNodePtr domainNode = virXPathNode("./domain", ctxt);
VIR_FREE(tmp); @@ -311,6 +312,16 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, } else { VIR_WARN("parsing older snapshot that lacks domain"); } + + /* /inactiveDomain entry saves the config XML present in a running + * VM. In case of absent, leave parent.inactiveDom NULL and use + * parent.dom for config and live XML. */ + if ((inactiveDomNode = virXPathNode("./inactiveDomain", ctxt))) { + def->parent.inactiveDom = virDomainDefParseNode(ctxt->node->doc, inactiveDomNode, + caps, xmlopt, NULL, domainflags); + if (!def->parent.inactiveDom) + goto cleanup; + } } else if (virDomainXMLOptionRunMomentPostParse(xmlopt, &def->parent) < 0) { goto cleanup; } @@ -908,6 +919,13 @@ virDomainSnapshotDefFormatInternal(virBufferPtr buf, virBufferAddLit(buf, "</domain>\n"); }
+ if (def->parent.inactiveDom) { + if (virDomainDefFormatInternalSetRootName(def->parent.inactiveDom, caps, + domainflags, buf, xmlopt, + "inactiveDomain") < 0) + goto error; + } + if (virSaveCookieFormatBuf(buf, def->cookie, virDomainXMLOptionGetSaveCookie(xmlopt)) < 0) goto error; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 78f5471b79..67511b705a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -15939,6 +15939,13 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) goto endjob;
+ if (vm->newDef) { + def->parent.inactiveDom = virDomainDefCopy(vm->newDef, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!def->parent.inactiveDom) + goto endjob; + } + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; align_match = false; @@ -16471,6 +16478,7 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuDomainObjPrivatePtr priv; int rc; virDomainDefPtr config = NULL; + virDomainDefPtr inactiveConfig = NULL; virQEMUDriverConfigPtr cfg = NULL; virCapsPtr caps = NULL; bool was_stopped = false; @@ -16572,11 +16580,10 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, * in the failure cases where we know there was no change? */ }
- /* Prepare to copy the snapshot inactive xml as the config of this - * domain. - * - * XXX Should domain snapshots track live xml rather - * than inactive xml? */ + /* Prepare to copy the snapshot inactive domain as the config XML + * and the snapshot domain as the live XML. In case of inactive domain + * NULL, both config and live XML will be copied from snapshot domain. + */ if (snap->def->dom) { config = virDomainDefCopy(snap->def->dom, caps, driver->xmlopt, priv->qemuCaps, true); @@ -16584,6 +16591,15 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; }
+ if (snap->def->inactiveDom) { + inactiveConfig = virDomainDefCopy(snap->def->inactiveDom, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!inactiveConfig) + goto endjob; + } else { + inactiveConfig = config; + } + cookie = (qemuDomainSaveCookiePtr) snapdef->cookie;
switch ((virDomainSnapshotState) snapdef->state) { @@ -16686,16 +16702,19 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } if (config) { - virDomainObjAssignDef(vm, config, false, NULL); virCPUDefFree(priv->origCPU); VIR_STEAL_PTR(priv->origCPU, origCPU); } + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); } else { /* Transitions 2, 3 */ load: was_stopped = true; + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); if (config) - virDomainObjAssignDef(vm, config, false, NULL); + virDomainObjAssignDef(vm, config, true, NULL);
/* No cookie means libvirt which saved the domain was too old to * mess up the CPU definitions. @@ -16781,8 +16800,8 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuProcessEndJob(driver, vm); goto cleanup; } - if (config) - virDomainObjAssignDef(vm, config, false, NULL); + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) {

On Thu, Aug 29, 2019 at 17:55:43 -0300, Maxiwell S. Garcia wrote:
The snapshot-create operation of running guests saves the live XML and uses it to replace the active and inactive domain in case of revert. So, the config XML is ignored by the snapshot process. This commit changes it and adds the config XML in the snapshot XML as the <inactiveDomain> entry.
In case of offline guest, the behavior remains the same and the config XML is saved in the snapshot XML as <domain> entry. The behavior of older snapshots of running guests, that don't have the new <inactiveDomain>, remains the same too. The revert, in this case, overrides both active and inactive domain with the <domain> entry. So, the <inactiveDomain> in the snapshot XML is not required to snapshot work, but it's useful to preserve the config XML of running guests.
Signed-off-by: Maxiwell S. Garcia <maxiwell@linux.ibm.com> --- src/conf/moment_conf.c | 1 + src/conf/moment_conf.h | 11 +++++++++++ src/conf/snapshot_conf.c | 22 ++++++++++++++++++++-- src/qemu/qemu_driver.c | 37 ++++++++++++++++++++++++++++--------- 4 files changed, 60 insertions(+), 11 deletions(-)
diff --git a/src/conf/moment_conf.c b/src/conf/moment_conf.c index fea13f0f97..f54a44b33e 100644 --- a/src/conf/moment_conf.c +++ b/src/conf/moment_conf.c @@ -66,6 +66,7 @@ virDomainMomentDefDispose(void *obj) VIR_FREE(def->description); VIR_FREE(def->parent_name); virDomainDefFree(def->dom); + virDomainDefFree(def->inactiveDom); }
/* Provide defaults for creation time and moment name after parsing XML */ diff --git a/src/conf/moment_conf.h b/src/conf/moment_conf.h index 9fdbef2172..70cc47bd70 100644 --- a/src/conf/moment_conf.h +++ b/src/conf/moment_conf.h @@ -36,7 +36,18 @@ struct _virDomainMomentDef { char *parent_name; long long creationTime; /* in seconds */
+ /* + * Store the active domain definition in case of online + * guest and the inactive domain definition in case of + * offline guest + */ virDomainDefPtr dom; + + /* + * Store the inactive domain definition in case of online + * guest and leave NULL in case of offline guest + */ + virDomainDefPtr inactiveDom;
OK, I can live with this since dom/inactiveDom are direct equivalents to domain/inactiveDomain elements in the snapshot XML, ...
};
virClassPtr virClassForDomainMomentDef(void); ... diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 78f5471b79..67511b705a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c ... @@ -16572,11 +16580,10 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, * in the failure cases where we know there was no change? */ }
- /* Prepare to copy the snapshot inactive xml as the config of this - * domain. - * - * XXX Should domain snapshots track live xml rather - * than inactive xml? */ + /* Prepare to copy the snapshot inactive domain as the config XML + * and the snapshot domain as the live XML. In case of inactive domain + * NULL, both config and live XML will be copied from snapshot domain. + */ if (snap->def->dom) { config = virDomainDefCopy(snap->def->dom, caps, driver->xmlopt, priv->qemuCaps, true); @@ -16584,6 +16591,15 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; }
+ if (snap->def->inactiveDom) { + inactiveConfig = virDomainDefCopy(snap->def->inactiveDom, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!inactiveConfig) + goto endjob; + } else { + inactiveConfig = config; + } +
...but the code here and the comment above should be changed a bit. You can't point both config and inactiveConfig to the same virDomainDef structure (see below). I believe config/inactiveConfig should be used strictly for active/inactive domain definition (thus config could be NULL for inactive snapshots) and we should copy the definition twice for active snapshots without inactiveDom: + if (snap->def->inactiveDom) { + inactiveConfig = virDomainDefCopy(snap->def->inactiveDom, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!inactiveConfig) + goto endjob; + } else { + /* Inactive domain definition is missing: + * - either this is an old active snapshot and we need to copy the + * active definition as an inactive one + * - or this is an inactive snapshot which means config contains the + * inactive definition. + */ + if (snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING || + snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) { + inactiveConfig = virDomainDefCopy(snap->def->dom, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!inactiveConfig) + goto endjob; + } else { + VIR_STEAL_PTR(inactiveConfig, config); + } + }
cookie = (qemuDomainSaveCookiePtr) snapdef->cookie;
switch ((virDomainSnapshotState) snapdef->state) { @@ -16686,16 +16702,19 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } if (config) { - virDomainObjAssignDef(vm, config, false, NULL); virCPUDefFree(priv->origCPU); VIR_STEAL_PTR(priv->origCPU, origCPU); } + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
If both domain and inactiveDomain elements were present in the snapshot XML, you'd leak config here. I realized that the config can be leaked even with the current code if qemuDomainRevertToSnapshot fails so I sent a patch fixing that: https://www.redhat.com/archives/libvir-list/2019-September/msg00382.html Once the memory leak is fixed, this hunk needs a trivial change to fit the new code.
} else { /* Transitions 2, 3 */ load: was_stopped = true; + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); if (config) - virDomainObjAssignDef(vm, config, false, NULL); + virDomainObjAssignDef(vm, config, true, NULL);
In case inactiveDomain was not present in the snapshot XML, both inactiveConfig and config would contain the same pointer. Thus you'd assign a single domain definition twice, which would result in funny things such as memory corruption and double free once one of them is freed for some reason. This is fixed by using the modified code for filling config and inactiveConfig (see above) and following the changes I made to avoid leaking memory (msg00382.html).
/* No cookie means libvirt which saved the domain was too old to * mess up the CPU definitions. @@ -16781,8 +16800,8 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuProcessEndJob(driver, vm); goto cleanup; } - if (config) - virDomainObjAssignDef(vm, config, false, NULL); + if (inactiveConfig) + virDomainObjAssignDef(vm, inactiveConfig, false, NULL);
This should be ok since we are reverting to a snapshot of a shutoff domain and thus the snapshot XML will contain only one domain definition. However, this hunk needs a trivial change to fit the new code after the memory leak fix is pushed.
if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) {
With the suggested changes Reviewed-by: Jiri Denemark <jdenemar@redhat.com> I'll send the patch with all modifications (which I will push once the memory leak fix is acked) as a reply to this email for completeness.

From: "Maxiwell S. Garcia" <maxiwell@linux.ibm.com> The snapshot-create operation of running guests saves the live XML and uses it to replace the active and inactive domain in case of revert. So, the config XML is ignored by the snapshot process. This commit changes it and adds the config XML in the snapshot XML as the <inactiveDomain> entry. In case of offline guest, the behavior remains the same and the config XML is saved in the snapshot XML as <domain> entry. The behavior of older snapshots of running guests, that don't have the new <inactiveDomain>, remains the same too. The revert, in this case, overrides both active and inactive domain with the <domain> entry. So, the <inactiveDomain> in the snapshot XML is not required to snapshot work, but it's useful to preserve the config XML of running guests. Signed-off-by: Maxiwell S. Garcia <maxiwell@linux.ibm.com> Reviewed-by: Daniel Henrique Barboza <danielhb413@gmail.com> Tested-by: Daniel Henrique Barboza <danielhb413@gmail.com> Reviewed-by: Jiri Denemark <jdenemar@redhat.com> --- src/conf/moment_conf.c | 1 + src/conf/moment_conf.h | 11 +++++++ src/conf/snapshot_conf.c | 22 ++++++++++++-- src/qemu/qemu_driver.c | 62 +++++++++++++++++++++++++++++++--------- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/conf/moment_conf.c b/src/conf/moment_conf.c index fea13f0f97..f54a44b33e 100644 --- a/src/conf/moment_conf.c +++ b/src/conf/moment_conf.c @@ -66,6 +66,7 @@ virDomainMomentDefDispose(void *obj) VIR_FREE(def->description); VIR_FREE(def->parent_name); virDomainDefFree(def->dom); + virDomainDefFree(def->inactiveDom); } /* Provide defaults for creation time and moment name after parsing XML */ diff --git a/src/conf/moment_conf.h b/src/conf/moment_conf.h index 9fdbef2172..70cc47bd70 100644 --- a/src/conf/moment_conf.h +++ b/src/conf/moment_conf.h @@ -36,7 +36,18 @@ struct _virDomainMomentDef { char *parent_name; long long creationTime; /* in seconds */ + /* + * Store the active domain definition in case of online + * guest and the inactive domain definition in case of + * offline guest + */ virDomainDefPtr dom; + + /* + * Store the inactive domain definition in case of online + * guest and leave NULL in case of offline guest + */ + virDomainDefPtr inactiveDom; }; virClassPtr virClassForDomainMomentDef(void); diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c index 7996589ad2..cce9a7999c 100644 --- a/src/conf/snapshot_conf.c +++ b/src/conf/snapshot_conf.c @@ -235,6 +235,7 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, virDomainSnapshotDefPtr def = NULL; virDomainSnapshotDefPtr ret = NULL; xmlNodePtr *nodes = NULL; + xmlNodePtr inactiveDomNode = NULL; size_t i; int n; char *creation = NULL, *state = NULL; @@ -244,6 +245,8 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, char *memoryFile = NULL; bool offline = !!(flags & VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE); virSaveCookieCallbacksPtr saveCookie = virDomainXMLOptionGetSaveCookie(xmlopt); + int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | + VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; if (!(def = virDomainSnapshotDefNew())) return NULL; @@ -293,8 +296,6 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, * clients will have to decide between best effort * initialization or outright failure. */ if ((tmp = virXPathString("string(./domain/@type)", ctxt))) { - int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE | - VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE; xmlNodePtr domainNode = virXPathNode("./domain", ctxt); VIR_FREE(tmp); @@ -311,6 +312,16 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt, } else { VIR_WARN("parsing older snapshot that lacks domain"); } + + /* /inactiveDomain entry saves the config XML present in a running + * VM. In case of absent, leave parent.inactiveDom NULL and use + * parent.dom for config and live XML. */ + if ((inactiveDomNode = virXPathNode("./inactiveDomain", ctxt))) { + def->parent.inactiveDom = virDomainDefParseNode(ctxt->node->doc, inactiveDomNode, + caps, xmlopt, NULL, domainflags); + if (!def->parent.inactiveDom) + goto cleanup; + } } else if (virDomainXMLOptionRunMomentPostParse(xmlopt, &def->parent) < 0) { goto cleanup; } @@ -908,6 +919,13 @@ virDomainSnapshotDefFormatInternal(virBufferPtr buf, virBufferAddLit(buf, "</domain>\n"); } + if (def->parent.inactiveDom) { + if (virDomainDefFormatInternalSetRootName(def->parent.inactiveDom, caps, + domainflags, buf, xmlopt, + "inactiveDomain") < 0) + goto error; + } + if (virSaveCookieFormatBuf(buf, def->cookie, virDomainXMLOptionGetSaveCookie(xmlopt)) < 0) goto error; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index a984b1e65c..7e9c28b42e 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -16029,6 +16029,13 @@ qemuDomainSnapshotCreateXML(virDomainPtr domain, VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE))) goto endjob; + if (vm->newDef) { + def->parent.inactiveDom = virDomainDefCopy(vm->newDef, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!def->parent.inactiveDom) + goto endjob; + } + if (flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) { align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL; align_match = false; @@ -16561,6 +16568,7 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuDomainObjPrivatePtr priv; int rc; virDomainDefPtr config = NULL; + virDomainDefPtr inactiveConfig = NULL; virQEMUDriverConfigPtr cfg = NULL; virCapsPtr caps = NULL; bool was_stopped = false; @@ -16663,11 +16671,6 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, * in the failure cases where we know there was no change? */ } - /* Prepare to copy the snapshot inactive xml as the config of this - * domain. - * - * XXX Should domain snapshots track live xml rather - * than inactive xml? */ if (snap->def->dom) { config = virDomainDefCopy(snap->def->dom, caps, driver->xmlopt, priv->qemuCaps, true); @@ -16675,6 +16678,29 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } + if (snap->def->inactiveDom) { + inactiveConfig = virDomainDefCopy(snap->def->inactiveDom, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!inactiveConfig) + goto endjob; + } else { + /* Inactive domain definition is missing: + * - either this is an old active snapshot and we need to copy the + * active definition as an inactive one + * - or this is an inactive snapshot which means config contains the + * inactive definition. + */ + if (snapdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING || + snapdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) { + inactiveConfig = virDomainDefCopy(snap->def->dom, caps, + driver->xmlopt, priv->qemuCaps, true); + if (!inactiveConfig) + goto endjob; + } else { + VIR_STEAL_PTR(inactiveConfig, config); + } + } + cookie = (qemuDomainSaveCookiePtr) snapdef->cookie; switch ((virDomainSnapshotState) snapdef->state) { @@ -16777,24 +16803,32 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, goto endjob; } if (config) { - virDomainObjAssignDef(vm, config, false, NULL); virCPUDefFree(priv->origCPU); VIR_STEAL_PTR(priv->origCPU, origCPU); - config = NULL; - defined = true; } if (cookie && !cookie->slirpHelper) priv->disableSlirp = true; + if (inactiveConfig) { + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); + inactiveConfig = NULL; + defined = true; + } } else { /* Transitions 2, 3 */ load: was_stopped = true; + + if (inactiveConfig) { + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); + inactiveConfig = NULL; + defined = true; + } + if (config) { - virDomainObjAssignDef(vm, config, false, NULL); + virDomainObjAssignDef(vm, config, true, NULL); config = NULL; - defined = true; } /* No cookie means libvirt which saved the domain was too old to @@ -16881,9 +16915,10 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, qemuProcessEndJob(driver, vm); goto cleanup; } - if (config) { - virDomainObjAssignDef(vm, config, false, NULL); - config = NULL; + + if (inactiveConfig) { + virDomainObjAssignDef(vm, inactiveConfig, false, NULL); + inactiveConfig = NULL; defined = true; } @@ -16970,6 +17005,7 @@ qemuDomainRevertToSnapshot(virDomainSnapshotPtr snapshot, virNWFilterUnlockFilterUpdates(); virCPUDefFree(origCPU); virDomainDefFree(config); + virDomainDefFree(inactiveConfig); return ret; } -- 2.23.0

On Thu, Aug 29, 2019 at 17:55:41 -0300, Maxiwell S. Garcia wrote:
This patchset store both config and live XML in the snapshot XML. To avoid nest 'config' XML one level deeper ('inactive/domain'), it was necessary to create a function that has a new rootname parameter.
V4: - Create a new function to format the XML domain choosing the root name
Maxiwell S. Garcia (2): qemu: formatting XML from domain def choosing the root name snapshot: Store both config and live XML in the snapshot domain
Both patches are pushed now. Thanks for the patient work. Jirka
participants (3)
-
Daniel Henrique Barboza
-
Jiri Denemark
-
Maxiwell S. Garcia