[libvirt] [PATCHv3 10/16] blockjob: support pivot operation on cancel

Eric Blake eblake at redhat.com
Fri Apr 6 18:29:41 UTC 2012


This is the bare minimum to end a copy job (of course, until a
later patch adds the ability to start a copy job, this patch
doesn't do much in isolation; I've just split the patches to
ease the review).

This patch intentionally avoids SELinux, lock manager, and audit
actions, saving that for a later patch that affects the overall
lifecycle of a disk copy.  In particular, I'm still fuzzy on the
exact qemu error semantics, and whether I need to make more of
an effort after a 'drive-reopen' fails.

When a mirror job is started, cancelling the job safely reverts back
to the source disk, regardless of whether the destination is in
phase 1 (streaming, in which case the destination is worthless) or
phase 2 (mirroring, in which case the destination is sync'd up to
the source at the time of the cancel).  Our existing code does just
fine in either phase, other than some bookkeeping cleanup.

Pivoting the job requires the use of the new 'drive-reopen' command.
Here, failure of the command is potentially catastrophic to the
domain, since it rips out the old disk before attempting to open
the new one; if our recovery path of retrying the reopen on the
original source disk also fails, the domain is hosed.  If only qemu
could get 'drive-reopen' inside 'transaction'...

Interesting side note: while snapshot-create --disk-only creates a
copy of the disk at a point in time by moving the domain on to a
new file (the copy is the file now in the just-extended backing
chain), blockjob --abort of a copy job creates a copy of the disk
while keeping the domain on the original file.  There may be
potential improvements to the snapshot code to exploit block copy
over multiple disks all at one point in time.  And, if
'block_job_cancel' were made part of 'transaction', you could
copy multiple disks at the same point in time without pausing
the domain.  This also implies we may want to add a --quiesce
flag to the pivot operation, so that when breaking a mirror, the
side of the mirror that we are abandoning is at least in a stable
state with regards to guest I/O.

* src/qemu/qemu_driver.c (qemuDomainBlockJobAbort): Accept new flag.
(qemuDomainBlockPivot): New helper function.
(qemuDomainBlockJobImpl): Implement it.
---
 src/qemu/qemu_driver.c |  118 +++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 112 insertions(+), 6 deletions(-)

diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 5dc263c..dd844a1 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -11618,6 +11618,86 @@ cleanup:
     return ret;
 }

+/* Called while holding the VM job lock, to implement a block job
+ * abort with pivot; this updates the VM definition as appropriate, on
+ * either success or failure (although there are some forms of
+ * catastrophic failure that will leave the VM unusable).  */
+static int
+qemuDomainBlockPivot(struct qemud_driver *driver, virDomainObjPtr vm,
+                     const char *device, virDomainDiskDefPtr disk)
+{
+    int ret = -1;
+    qemuDomainObjPrivatePtr priv = vm->privateData;
+    virDomainBlockJobInfo info;
+
+    /* Probe the status, if needed.  */
+    if (!disk->mirroring) {
+        qemuDomainObjEnterMonitorWithDriver(driver, vm);
+        ret = qemuMonitorBlockJob(priv->mon, device, NULL, 0, &info,
+                                  BLOCK_JOB_INFO);
+        qemuDomainObjExitMonitorWithDriver(driver, vm);
+        if (ret < 0)
+            goto cleanup;
+        if (!virDomainObjIsActive(vm)) {
+            qemuReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                            _("domain is not running"));
+            goto cleanup;
+        }
+        if (ret == 1 && info.cur == info.end &&
+            info.type == VIR_DOMAIN_BLOCK_JOB_TYPE_COPY)
+            disk->mirroring = true;
+    }
+
+    if (!disk->mirroring) {
+        qemuReportError(VIR_ERR_BLOCK_COPY_ACTIVE,
+                        _("disk '%s' not ready for pivot yet"),
+                        disk->dst);
+        goto cleanup;
+    }
+
+    /* Attempt the pivot.  */
+    qemuDomainObjEnterMonitorWithDriver(driver, vm);
+    ret = qemuMonitorDriveReopen(priv->mon, device, disk->mirror,
+                                 disk->mirrorFormat);
+    qemuDomainObjExitMonitorWithDriver(driver, vm);
+
+    /* XXX Until qemu adds support for 'drive-reopen' inside
+     * 'transaction', we have the remote risk of a catastrophic
+     * failure, where the drive-reopen fails but can't recover by
+     * reopening the source.  Not much we can do about it.  */
+
+    if (ret == 0) {
+        /* XXX We want to revoke security labels and disk lease, as
+         * well as audit that revocation, before dropping the original
+         * source.  But it gets tricky if both source and mirror share
+         * common backing files (we want to only revoke the non-shared
+         * portion of the chain, and is made more difficult by the
+         * fact that we aren't tracking the full chain ourselves; so
+         * for now, we leak the access to the original.  */
+        VIR_FREE(disk->src);
+        VIR_FREE(disk->driverType);
+        disk->src = disk->mirror;
+        disk->driverType = disk->mirrorFormat;
+        disk->mirror = NULL;
+        disk->mirrorFormat = NULL;
+        disk->mirroring = false;
+    } else {
+        /* On failure, qemu abandons the mirror, and attempts to
+         * revert back to the source disk.  Hopefully it was able to
+         * reopen things.  */
+        /* XXX should we be parsing the exact qemu error, or calling
+         * 'query-block', to see what state we really got left in
+         * before killing the mirroring job?  And just as on the
+         * success case, there's security labeling to worry about.  */
+        VIR_FREE(disk->mirror);
+        VIR_FREE(disk->mirrorFormat);
+        disk->mirroring = false;
+    }
+
+cleanup:
+    return ret;
+}
+
 static int
 qemuDomainBlockJobImpl(virDomainPtr dom, const char *path, const char *base,
                        unsigned long bandwidth, virDomainBlockJobInfoPtr info,
@@ -11630,6 +11710,7 @@ qemuDomainBlockJobImpl(virDomainPtr dom, const char *path, const char *base,
     char *device = NULL;
     int ret = -1;
     int idx;
+    virDomainDiskDefPtr disk = NULL;

     qemuDriverLock(driver);
     virUUIDFormat(dom->uuid, uuidstr);
@@ -11644,10 +11725,20 @@ qemuDomainBlockJobImpl(virDomainPtr dom, const char *path, const char *base,
     if (!device) {
         goto cleanup;
     }
-    if (mode == BLOCK_JOB_PULL && vm->def->disks[idx]->mirror) {
+    disk = vm->def->disks[idx];
+    priv = vm->privateData;
+
+    if (disk->mirror && mode == BLOCK_JOB_PULL) {
         qemuReportError(VIR_ERR_BLOCK_COPY_ACTIVE,
                         _("disk '%s' already in active block copy job"),
-                        vm->def->disks[idx]->dst);
+                        disk->dst);
+        goto cleanup;
+    }
+    if (!disk->mirror && mode == BLOCK_JOB_ABORT &&
+        (flags & VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT)) {
+        qemuReportError(VIR_ERR_OPERATION_INVALID,
+                        _("pivot of disk '%s' requires an active copy job"),
+                        disk->dst);
         goto cleanup;
     }

@@ -11660,8 +11751,13 @@ qemuDomainBlockJobImpl(virDomainPtr dom, const char *path, const char *base,
         goto endjob;
     }

+    if (disk->mirror && mode == BLOCK_JOB_ABORT &&
+        (flags & VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT)) {
+        ret = qemuDomainBlockPivot(driver, vm, device, disk);
+        goto endjob;
+    }
+
     qemuDomainObjEnterMonitorWithDriver(driver, vm);
-    priv = vm->privateData;
     /* XXX - add a qemu capability check, since only qemu 1.1 or newer
      * supports the base argument.
      * XXX - libvirt should really be tracking the backing file chain
@@ -11678,9 +11774,18 @@ qemuDomainBlockJobImpl(virDomainPtr dom, const char *path, const char *base,

     /* Snoop block copy operations, so future cancel operations can
      * avoid checking if pivot is safe.  */
-    if (mode == BLOCK_JOB_INFO && ret == 1 && vm->def->disks[idx]->mirror &&
+    if (mode == BLOCK_JOB_INFO && ret == 1 && disk->mirror &&
         info->cur == info->end && info->type == VIR_DOMAIN_BLOCK_JOB_TYPE_COPY)
-        vm->def->disks[idx]->mirroring = true;
+        disk->mirroring = true;
+
+    /* A successful block job cancelation stops any mirroring.  */
+    if (mode == BLOCK_JOB_ABORT && disk->mirror) {
+        /* XXX We should also revoke security labels and disk lease on
+         * the mirror, and audit that fact, before dropping things.  */
+        VIR_FREE(disk->mirror);
+        VIR_FREE(disk->mirrorFormat);
+        disk->mirroring = false;
+    }

     /* Qemu provides asynchronous block job cancellation, but without
      * the VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC flag libvirt guarantees a
@@ -11738,7 +11843,8 @@ cleanup:
 static int
 qemuDomainBlockJobAbort(virDomainPtr dom, const char *path, unsigned int flags)
 {
-    virCheckFlags(VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC, -1);
+    virCheckFlags(VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC |
+                  VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT, -1);
     return qemuDomainBlockJobImpl(dom, path, NULL, 0, NULL, BLOCK_JOB_ABORT,
                                   flags);
 }
-- 
1.7.7.6




More information about the libvir-list mailing list