This completes the public API for using mirrored snapshots as a
means of performing live storage migration. Of course, it will
take additional patches to actually provide the implementation.
The idea here is that oVirt can start with a domain with 'vda'
open on /path1/to/old.qcow2 with a base file of /path1/to/common,
then do the following steps for a live migration to /path2 (here
using virsh commands, although the underlying API would be
available to oVirt through other language bindings as well).
First, pre-create two files; it is important that the mirror file
have a relative backing file name (if we let qemu create the entire
snapshot, the backing file name would be absolute to /path1, and
pivoting to the mirror would still be using the original source):
$ qemu-img create -f qcow2 -o backing_file=old.qcow2 \
-o backing_fmt=qcow2 /path1/to/old.migrate
$ qemu-img create -f qcow2 -o backing_file=old.qcow2 \
-o backing_fmt=qcow2 /path2/to/new.qcow2
Next, create a mirrored snapshot, while telling qemu to respect
the pre-existing qcow2 header in both files:
$ virsh snapshot-create-as $dom migrate --reuse-external \
--diskspec vda,file=/path1/to/old.migrate,mirror=/path2/to/new.qcow2
which means 'vda' is now open read-write on /path1/to/old.migrate
and mirrored (write only) on /path2/to/new.qcow2, where qemu is
using /path1/to/old.qcow2 as the logial backing for both files,
but where the relative name is still intact in /path2/to/new.qcow2.
Next, the backing files can be copied between locations:
$ cp /path1/to/common /path1/to/old.qcow2 /path2/to
When that is complete, the mirrored snapshot is no longer necessary,
and requesting a pivot while deleting things will force qemu to
reread the header of /path2/to/new.qcow2, notice a relative backing
file name, and open /path2/to/old.qcow2 as the logical backing file:
$ virsh snapshot-delete $dom migrate --mirror-pivot
Now /path1 is no longer in use, but the backing chain on /path2
is longer than original. This can be cleaned up with:
$ virsh blockpull $dom vda --base /path2/to/common
to go back to having /path2/to/new.qcow2 directly backed by
/path2/to/common.
That smells like a hack, right? Well, there is a proposal for a
better, more powerful API named virDomainBlockCopy:
https://www.redhat.com/archives/libvir-list/2012-March/msg00585.html
which would more appropriately expose the various knobs of the new
qemu commands; but being a new API, it lacks the ability to be
backported without causing a .so bump. So this is the compromise.
* include/libvirt/libvirt.h.in
(VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_ABORT)
(VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_PIVOT): New flags.
* src/libvirt.c (virDomainSnapshotDelete): Document them.
(virDomainSnapshotCreateXML, virDomainRevertToSnapshot)
(virDomainSaveFlags): Mention effects of mirrored snapshots.
* tools/virsh.c (vshParseSnapshotDiskspec): Add <mirror> support.
(cmdSnapshotDelete): Add --mirror-abort, --mirror-pivot flags.
* tools/virsh.pod (snapshot-delete, snapshot-create-as): Document
new options.
---
include/libvirt/libvirt.h.in | 6 ++++++
src/libvirt.c | 37 +++++++++++++++++++++++++++++++++++--
tools/virsh.c | 13 +++++++++++++
tools/virsh.pod | 21 ++++++++++++++-------
4 files changed, 68 insertions(+), 9 deletions(-)
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index 4566580..dbb6358 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -3369,6 +3369,12 @@ typedef enum {
VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN = (1 << 0), /* Also delete children
*/
VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY = (1 << 1), /* Delete just metadata
*/
VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY = (1 << 2), /* Delete just children
*/
+ VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_ABORT = (1 << 3), /* Stop a mirrored
+ snapshot, reopening
+ to the source. */
+ VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_PIVOT = (1 << 4), /* Stop a mirrored
+ snapshot, reopening
+ to the mirror. */
} virDomainSnapshotDeleteFlags;
int virDomainSnapshotDelete(virDomainSnapshotPtr snapshot,
diff --git a/src/libvirt.c b/src/libvirt.c
index 7df3667..800d1ee 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -2702,6 +2702,9 @@ error:
* @flags will override what state gets saved into the file. These
* two flags are mutually exclusive.
*
+ * Some hypervisors may prevent this call if there is a current
+ * mirrored snapshot.
+ *
* A save file can be inspected or modified slightly with
* virDomainSaveImageGetXMLDesc() and virDomainSaveImageDefineXML().
*
@@ -17087,12 +17090,14 @@ virDomainSnapshotGetConnect(virDomainSnapshotPtr snapshot)
* not exist, the hypervisor may validate that reverting to the
* snapshot appears to be possible (for example, disk images have
* snapshot contents by the requested name). Not all hypervisors
- * support these flags.
+ * support these flags; this might also be forbidden if there is a
+ * current mirrored snapshot.
*
* If @flags includes VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA, then the
* domain's disk images are modified according to @xmlDesc, but then
* the just-created snapshot has its metadata deleted. This flag is
- * incompatible with VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE.
+ * incompatible with VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE, and may
+ * also prevent any @xmlDesc that tries to create a mirrored snapshot.
*
* If @flags includes VIR_DOMAIN_SNAPSHOT_CREATE_HALT, then the domain
* will be inactive after the snapshot completes, regardless of whether
@@ -17698,6 +17703,9 @@ error:
* inactive snapshots with a @flags request to start the domain after
* the revert.
*
+ * Some hypervisors may prevent reverting to snapshots if there is an
+ * active mirrored snapshot.
+ *
* Returns 0 if the creation is successful, -1 on error.
*/
int
@@ -17764,6 +17772,25 @@ error:
* libvirt metadata to track snapshots, then this flag is silently
* ignored.
*
+ * If the domain has a current mirrored snapshot (that is, if the XML
+ * given to virDomainSnapshotCreateXML() included <mirror> elements for
+ * a disk), then the caller must specify how to end the mirroring before
+ * the snapshot can be deleted. In this situation, @snap must be the
+ * current mirrored snapshot, and @flags must contain either
+ * VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_ABORT to abandon the mirror while
+ * keeping the primary external file, or contain
+ * VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_PIVOT to reopen the storage device
+ * using just the mirror, and abandon the primary external file. Note
+ * that while virDomainSnapshotCreateXML can create mirrors atomically,
+ * the deletion process might not be atomic; in this case, the operation
+ * will fail, but only after updating the snapshot object to record
+ * which mirrors were successfully reopened. If atomicity is important
+ * when using mirrored snapshots to perform live storage migration, it
+ * is recommended to migrate only one disk per snapshot. After using
+ * a mirrored snapshot to perform a storage migration, the caller may
+ * also want to use virDomainBlockRebase() to reduce the length of the
+ * backing chain that was lengthened by the snapshot process.
+ *
* Returns 0 if the selected snapshot(s) were successfully deleted,
* -1 on error.
*/
@@ -17797,6 +17824,12 @@ virDomainSnapshotDelete(virDomainSnapshotPtr snapshot,
"mutually exclusive"));
goto error;
}
+ if ((flags & VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_ABORT) &&
+ (flags & VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_PIVOT)) {
+ virLibDomainError(VIR_ERR_INVALID_ARG,
+ _("delete mirror flags are mutually exclusive"));
+ goto error;
+ }
if (conn->driver->domainSnapshotDelete) {
int ret = conn->driver->domainSnapshotDelete(snapshot, flags);
diff --git a/tools/virsh.c b/tools/virsh.c
index 2548b73..9c0358e 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -15790,6 +15790,7 @@ vshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const
char *str)
char *snapshot = NULL;
char *driver = NULL;
char *file = NULL;
+ char *mirror = NULL;
char *spec = vshStrdup(ctl, str);
char *tmp = spec;
size_t len = strlen(str);
@@ -15813,6 +15814,8 @@ vshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const
char *str)
driver = tmp + strlen("driver=");
else if (!file && STRPREFIX(tmp, "file="))
file = tmp + strlen("file=");
+ else if (!mirror && STRPREFIX(tmp, "mirror="))
+ mirror = tmp + strlen("mirror=");
else
goto cleanup;
}
@@ -15826,6 +15829,8 @@ vshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const
char *str)
virBufferAsprintf(buf, " <driver type='%s'/>\n",
driver);
if (file)
virBufferEscapeString(buf, " <source
file='%s'/>\n", file);
+ if (mirror)
+ virBufferEscapeString(buf, " <mirror
file='%s'/>\n", mirror);
virBufferAddLit(buf, " </disk>\n");
} else {
virBufferAddLit(buf, "/>\n");
@@ -16861,6 +16866,10 @@ static const vshCmdOptDef opts_snapshot_delete[] = {
{"children-only", VSH_OT_BOOL, 0, N_("delete children but not
snapshot")},
{"metadata", VSH_OT_BOOL, 0,
N_("delete only libvirt metadata, leaving snapshot contents behind")},
+ {"mirror-abort", VSH_OT_BOOL, 0,
+ N_("abort a mirror while removing snapshot")},
+ {"mirror-pivot", VSH_OT_BOOL, 0,
+ N_("pivot to the mirror before removing snapshot")},
{NULL, 0, 0, NULL}
};
@@ -16890,6 +16899,10 @@ cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd)
flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY;
if (vshCommandOptBool(cmd, "metadata"))
flags |= VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
+ if (vshCommandOptBool(cmd, "mirror-abort"))
+ flags |= VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_ABORT;
+ if (vshCommandOptBool(cmd, "mirror-pivot"))
+ flags |= VIR_DOMAIN_SNAPSHOT_DELETE_MIRROR_PIVOT;
/* XXX If we wanted, we could emulate DELETE_CHILDREN_ONLY even on
* older servers that reject the flag, by manually computing the
diff --git a/tools/virsh.pod b/tools/virsh.pod
index 8517fe5..5712c83 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -2355,12 +2355,12 @@ is specified, the snapshot will not include vm state.
The I<--disk-only> flag is used to request a disk-only snapshot. When
this flag is in use, the command can also take additional I<diskspec>
arguments to add <disk> elements to the xml. Each <diskspec> is in the
-form B<disk[,snapshot=type][,driver=type][,file=name]>. To include a
-literal comma in B<disk> or in B<file=name>, escape it with a second
-comma. A literal I<--diskspec> must preceed each B<diskspec> unless
-all three of I<domain>, I<name>, and I<description> are also present.
-For example, a diskspec of "vda,snapshot=external,file=/path/to,,new"
-results in the following XML:
+form B<disk[,snapshot=type][,driver=type][,file=name][,mirror=name]>.
+To include a literal comma in B<disk>, B<file=name>, or
B<mirror=name>,
+escape it with a second comma. A literal I<--diskspec> must preceed
+each B<diskspec> unless all three of I<domain>, I<name>, and
+I<description> are also present. For example, a diskspec of
+"vda,snapshot=external,file=/path/to,,new" results in the following XML:
<disk name='vda' snapshot='external'>
<source file='/path/to,new'/>
</disk>
@@ -2503,7 +2503,7 @@ with an inactive snapshot that is combined with the I<--start>
or
I<--pause> flag.
=item B<snapshot-delete> I<domain> {I<snapshot> | I<--current>}
[I<--metadata>]
-[{I<--children> | I<--children-only>}]
+[{I<--children> | I<--children-only>}] [{I<--mirror-abort> |
I<--mirror-pivot>}]
Delete the snapshot for the domain named I<snapshot>, or the current
snapshot with I<--current>. If this snapshot
@@ -2518,6 +2518,13 @@ maintained by libvirt, while leaving the snapshot contents intact
for
access by external tools; otherwise deleting a snapshot also removes
the data contents from that point in time.
+If the snapshot is mirrored (that is, the <domainsnapshot> XML used to
+create the snapshot included an external <mirror> designation for use
+in live migration of storage), then this command will fail unless the
+current snapshot is being deleted, and either the I<--mirror-abort> or
+I<--mirror-pivot> flag is present to control which half of the mirror
+is kept active.
+
=back
=head1 NWFILTER COMMMANDS
--
1.7.7.6