This extends <domainsnapshot> XML to add a new element under each <disk> of a disk snapshot: <disk name='vda'> <source file='/path/to/live'/> <mirror file='/path/to/mirror'/> </disk> For now, if a <mirror> is requested, the snapshot must be external, and assumes the same driver format (qcow2 or qed) as the <source>. qemu allows more flexibility in mirror creation, which could possibly be added by further XML enhancements, but more likely would be better served by adding a new API to specifically target mirroring, as well as XML changes to represent the entire backing chain in <domain> rather than commandeering <domainsnapshot>. Meanwhile, the changes in this patch are sufficient for the oVirt use case of using a mirror for live storage migration. The new XML is parsed but rejected until later patches update the hypervisor drivers to act on mirror requests. The testsuite additions show that we can round-trip the XML. * docs/schemas/domainsnapshot.rng (disksnapshot): Add optional mirror. * docs/formatsnapshot.html.in: Document it. * src/conf/domain_conf.h (VIR_DOMAIN_SNAPSHOT_PARSE_MIRROR): New flag. (_virDomainSnapshotDiskDef): Add new member. * src/conf/domain_conf.c (virDomainSnapshotDiskDefParseXML): Add parameter, to parse or reject mirrors. (virDomainSnapshotDefParseString): Honor new flag. (virDomainSnapshotDefFormat): Output any mirrors. (virDomainSnapshotDiskDefClear): Clean up. * tests/domainsnapshotxml2xmltest.c (mymain): Add a test. (testCompareXMLToXMLFiles): Allow for mirrors. * tests/domainsnapshotxml2xmlout/disk_snapshot_mirror.xml: New file. * tests/domainsnapshotxml2xmlin/disk_snapshot_mirror.xml: Likewise. --- docs/formatsnapshot.html.in | 31 ++++++++++++ docs/schemas/domainsnapshot.rng | 8 +++ src/conf/domain_conf.c | 38 ++++++++++++++- src/conf/domain_conf.h | 2 + .../disk_snapshot_mirror.xml | 13 +++++ .../disk_snapshot_mirror.xml | 49 ++++++++++++++++++++ tests/domainsnapshotxml2xmltest.c | 4 +- 7 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 tests/domainsnapshotxml2xmlin/disk_snapshot_mirror.xml create mode 100644 tests/domainsnapshotxml2xmlout/disk_snapshot_mirror.xml diff --git a/docs/formatsnapshot.html.in b/docs/formatsnapshot.html.in index ec5ebf3..cd39f1b 100644 --- a/docs/formatsnapshot.html.in +++ b/docs/formatsnapshot.html.in @@ -90,6 +90,30 @@ another snapshot. </p> <p> + External disk snapshots have another use of allowing live + storage migration across storage domains, while still + guaranteeing that at all points in time along the storage + migration, there exists a single storage domain with a + consistent view of the guest's data. This is done by adding the + concept of snapshot mirroring, where a snapshot request + specifies not only the new file name on the original storage + domain, but a secondary mirror file name on the target storage + domain. While the snapshot is active, reads are still tied to + the first storage domain, but all writes of changes from the + backing file are recorded in both storage domains; and the + management application can synchronize the backing files across + storage domains in parallel with the guest execution. Once the + second storage domain has all data in place, the management + application then deletes the snapshot, which stops the mirroring + and lets the hypervisor swap over to the second storage domain, + so that the first storage domain is no longer in active use. + Depending on the hypervisor, the use of a mirrored snapshots may + be restricted to transient domains, and the existence of a + mirrored snapshot can prevent migration, domain saves, disk hot + plug actions, and further snapshot manipulation until the + mirrored snapshot is deleted in order to stop the mirroring. + </p> + <p> The top-level <code>domainsnapshot</code> element may contain the following elements: </p> @@ -156,6 +180,13 @@ snapshots, the original file name becomes the read-only snapshot, and the new file name contains the read-write delta of all disk changes since the snapshot. + <span class="since">Since 0.9.11</span>, an external + snapshot may also have an optional + element <code>mirror</code>, which names a second file + alongside <code>source</code> that will mirror all changes + since the snapshot and using the same file format. A + mirror must have the <code>file</code> attribute listing + the file name to use. </dd> </dl> </dd> diff --git a/docs/schemas/domainsnapshot.rng b/docs/schemas/domainsnapshot.rng index 0ef0631..86193a7 100644 --- a/docs/schemas/domainsnapshot.rng +++ b/docs/schemas/domainsnapshot.rng @@ -121,6 +121,14 @@ <empty/> </element> </optional> + <optional> + <element name='mirror'> + <attribute name='file'> + <ref name='absFilePath'/> + </attribute> + <empty/> + </element> + </optional> </interleave> </group> </choice> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 1b3f34a..fd9fd2c 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -13393,6 +13393,7 @@ virDomainSnapshotDiskDefClear(virDomainSnapshotDiskDefPtr disk) VIR_FREE(disk->name); VIR_FREE(disk->file); VIR_FREE(disk->driverType); + VIR_FREE(disk->mirror); } void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) @@ -13414,11 +13415,13 @@ void virDomainSnapshotDefFree(virDomainSnapshotDefPtr def) static int virDomainSnapshotDiskDefParseXML(xmlNodePtr node, - virDomainSnapshotDiskDefPtr def) + virDomainSnapshotDiskDefPtr def, + bool allow_mirror) { int ret = -1; char *snapshot = NULL; xmlNodePtr cur; + xmlNodePtr mirror = NULL; def->name = virXMLPropString(node, "name"); if (!def->name) { @@ -13447,14 +13450,38 @@ virDomainSnapshotDiskDefParseXML(xmlNodePtr node, } else if (!def->driverType && xmlStrEqual(cur->name, BAD_CAST "driver")) { def->driverType = virXMLPropString(cur, "type"); + } else if (!mirror && xmlStrEqual(cur->name, BAD_CAST "mirror")) { + mirror = cur; } } cur = cur->next; } - if (!def->snapshot && (def->file || def->driverType)) + if (!def->snapshot && (def->file || def->driverType || mirror)) def->snapshot = VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL; + if (mirror) { + if (!allow_mirror) { + virDomainReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, + _("unable to handle mirroring for '%s'"), + def->name); + goto cleanup; + } + def->mirror = virXMLPropString(mirror, "file"); + if (!def->mirror) { + virDomainReportError(VIR_ERR_XML_ERROR, + _("mirror for '%s' requires file attribute"), + def->name); + goto cleanup; + } + if (def->snapshot != VIR_DOMAIN_DISK_SNAPSHOT_EXTERNAL) { + virDomainReportError(VIR_ERR_XML_ERROR, + _("mirror for '%s' requires external snapshot"), + def->name); + goto cleanup; + } + } + ret = 0; cleanup: VIR_FREE(snapshot); @@ -13484,6 +13511,7 @@ virDomainSnapshotDefParseString(const char *xmlStr, int active; char *tmp; int keepBlanksDefault = xmlKeepBlanksDefault(0); + bool allow_mirror = (flags & VIR_DOMAIN_SNAPSHOT_PARSE_MIRROR) != 0; xml = virXMLParseCtxt(NULL, xmlStr, _("(domain_snapshot)"), &ctxt); if (!xml) { @@ -13587,7 +13615,8 @@ virDomainSnapshotDefParseString(const char *xmlStr, goto cleanup; } for (i = 0; i < def->ndisks; i++) { - if (virDomainSnapshotDiskDefParseXML(nodes[i], &def->disks[i]) < 0) + if (virDomainSnapshotDiskDefParseXML(nodes[i], &def->disks[i], + allow_mirror) < 0) goto cleanup; } VIR_FREE(nodes); @@ -13852,6 +13881,9 @@ char *virDomainSnapshotDefFormat(const char *domain_uuid, if (disk->file) virBufferEscapeString(&buf, " <source file='%s'/>\n", disk->file); + if (disk->mirror) + virBufferEscapeString(&buf, " <mirror file='%s'/>\n", + disk->mirror); virBufferAddLit(&buf, " </disk>\n"); } else { virBufferAddLit(&buf, "/>\n"); diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 9d74f44..a25cd1a 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1667,6 +1667,7 @@ struct _virDomainSnapshotDiskDef { int snapshot; /* enum virDomainDiskSnapshot */ char *file; /* new source file when snapshot is external */ char *driverType; /* file format type of new file */ + char *mirror; /* external snapshot mirror, of same file format as file */ }; /* Stores the complete snapshot metadata */ @@ -1715,6 +1716,7 @@ typedef enum { VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE = 1 << 0, VIR_DOMAIN_SNAPSHOT_PARSE_DISKS = 1 << 1, VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL = 1 << 2, + VIR_DOMAIN_SNAPSHOT_PARSE_MIRROR = 1 << 3, } virDomainSnapshotParseFlags; virDomainSnapshotDefPtr virDomainSnapshotDefParseString(const char *xmlStr, diff --git a/tests/domainsnapshotxml2xmlin/disk_snapshot_mirror.xml b/tests/domainsnapshotxml2xmlin/disk_snapshot_mirror.xml new file mode 100644 index 0000000..4085260 --- /dev/null +++ b/tests/domainsnapshotxml2xmlin/disk_snapshot_mirror.xml @@ -0,0 +1,13 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <disks> + <disk name='hda' snapshot='external'> + <source file='/path/to/new'/> + <mirror file='/path/to/other'/> + </disk> + <disk name='hdb'> + <mirror file='/path/to/other2'/> + </disk> + </disks> +</domainsnapshot> diff --git a/tests/domainsnapshotxml2xmlout/disk_snapshot_mirror.xml b/tests/domainsnapshotxml2xmlout/disk_snapshot_mirror.xml new file mode 100644 index 0000000..c99fd5b --- /dev/null +++ b/tests/domainsnapshotxml2xmlout/disk_snapshot_mirror.xml @@ -0,0 +1,49 @@ +<domainsnapshot> + <name>my snap name</name> + <description>!@#$%^</description> + <state>disk-snapshot</state> + <creationTime>1272917631</creationTime> + <disks> + <disk name='hda' snapshot='external'> + <driver type='qcow2'/> + <source file='/path/to/new'/> + <mirror file='/path/to/other'/> + </disk> + <disk name='hdb' snapshot='external'> + <driver type='qcow2'/> + <source file='/path/to/generated'/> + <mirror file='/path/to/other2'/> + </disk> + </disks> + <domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <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'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest2'/> + <target dev='hdb' bus='ide'/> + <address type='drive' controller='0' bus='1' 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/domainsnapshotxml2xmltest.c b/tests/domainsnapshotxml2xmltest.c index e363c99..07ff553 100644 --- a/tests/domainsnapshotxml2xmltest.c +++ b/tests/domainsnapshotxml2xmltest.c @@ -26,7 +26,8 @@ testCompareXMLToXMLFiles(const char *inxml, const char *uuid, int internal) int ret = -1; virDomainSnapshotDefPtr def = NULL; unsigned int flags = (VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE | - VIR_DOMAIN_SNAPSHOT_PARSE_DISKS); + VIR_DOMAIN_SNAPSHOT_PARSE_DISKS | + VIR_DOMAIN_SNAPSHOT_PARSE_MIRROR); if (virtTestLoadFile(inxml, &inXmlData) < 0) goto fail; @@ -105,6 +106,7 @@ mymain(void) DO_TEST("all_parameters", "9d37b878-a7cc-9f9a-b78f-49b3abad25a8", 1); DO_TEST("disk_snapshot", "c7a5fdbd-edaf-9455-926a-d65c16db1809", 1); + DO_TEST("disk_snapshot_mirror", "c7a5fdbd-edaf-9455-926a-d65c16db1809", 0); DO_TEST("full_domain", "c7a5fdbd-edaf-9455-926a-d65c16db1809", 1); DO_TEST("noparent_nodescription_noactive", NULL, 0); DO_TEST("noparent_nodescription", NULL, 1); -- 1.7.7.6 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list