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