render / rpms / libvirt

Forked from rpms/libvirt 9 months ago
Clone
d89b3e
From 12fdae1ebb74296a4db3b191f16dfda757024b8f Mon Sep 17 00:00:00 2001
d89b3e
Message-Id: <12fdae1ebb74296a4db3b191f16dfda757024b8f@dist-git>
d89b3e
From: Peter Krempa <pkrempa@redhat.com>
d89b3e
Date: Tue, 17 Mar 2015 13:13:52 +0100
d89b3e
Subject: [PATCH] qemu: event: Don't fiddle with disk backing trees without a
d89b3e
 job
d89b3e
d89b3e
https://bugzilla.redhat.com/show_bug.cgi?id=1202719
d89b3e
d89b3e
Surprisingly we did not grab a VM job when a block job finished and we'd
d89b3e
happily rewrite the backing chain data. This made it possible to crash
d89b3e
libvirt when queueing two backing chains tightly and other badness.
d89b3e
d89b3e
To fix it, add yet another handler to the helper thread that handles
d89b3e
monitor events that require a job.
d89b3e
d89b3e
(cherry picked from commit 1a92c719101e5bfa6fe2b78006ad04c7f075ea28)
d89b3e
d89b3e
 Changes: src/qemu/qemu_driver.c: qemuDomainObjEndJob() needs it's
d89b3e
 return value to be ignored as the locking was not refactored yet.
d89b3e
d89b3e
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
d89b3e
---
d89b3e
 src/qemu/qemu_domain.h  |   2 +
d89b3e
 src/qemu/qemu_driver.c  | 142 ++++++++++++++++++++++++++++++++++++++++++++++++
d89b3e
 src/qemu/qemu_process.c | 129 ++++++++-----------------------------------
d89b3e
 3 files changed, 168 insertions(+), 105 deletions(-)
d89b3e
d89b3e
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
d89b3e
index bf37e26..76054ec 100644
d89b3e
--- a/src/qemu/qemu_domain.h
d89b3e
+++ b/src/qemu/qemu_domain.h
d89b3e
@@ -196,6 +196,7 @@ typedef enum {
d89b3e
     QEMU_PROCESS_EVENT_DEVICE_DELETED,
d89b3e
     QEMU_PROCESS_EVENT_NIC_RX_FILTER_CHANGED,
d89b3e
     QEMU_PROCESS_EVENT_SERIAL_CHANGED,
d89b3e
+    QEMU_PROCESS_EVENT_BLOCK_JOB,
d89b3e
 
d89b3e
     QEMU_PROCESS_EVENT_LAST
d89b3e
 } qemuProcessEventType;
d89b3e
@@ -204,6 +205,7 @@ struct qemuProcessEvent {
d89b3e
     virDomainObjPtr vm;
d89b3e
     qemuProcessEventType eventType;
d89b3e
     int action;
d89b3e
+    int status;
d89b3e
     void *data;
d89b3e
 };
d89b3e
 
d89b3e
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
d89b3e
index c25c5ac..a19281d 100644
d89b3e
--- a/src/qemu/qemu_driver.c
d89b3e
+++ b/src/qemu/qemu_driver.c
d89b3e
@@ -4401,6 +4401,141 @@ processSerialChangedEvent(virQEMUDriverPtr driver,
d89b3e
 }
d89b3e
 
d89b3e
 
d89b3e
+static void
d89b3e
+processBlockJobEvent(virQEMUDriverPtr driver,
d89b3e
+                     virDomainObjPtr vm,
d89b3e
+                     char *diskAlias,
d89b3e
+                     int type,
d89b3e
+                     int status)
d89b3e
+{
d89b3e
+    virObjectEventPtr event = NULL;
d89b3e
+    virObjectEventPtr event2 = NULL;
d89b3e
+    const char *path;
d89b3e
+    virDomainDiskDefPtr disk;
d89b3e
+    virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
d89b3e
+    virDomainDiskDefPtr persistDisk = NULL;
d89b3e
+    bool save = false;
d89b3e
+
d89b3e
+    if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
d89b3e
+        goto cleanup;
d89b3e
+
d89b3e
+    if (!virDomainObjIsActive(vm)) {
d89b3e
+        VIR_DEBUG("Domain is not running");
d89b3e
+        goto endjob;
d89b3e
+    }
d89b3e
+
d89b3e
+    disk = qemuProcessFindDomainDiskByAlias(vm, diskAlias);
d89b3e
+
d89b3e
+    if (disk) {
d89b3e
+        /* Have to generate two variants of the event for old vs. new
d89b3e
+         * client callbacks */
d89b3e
+        if (type == VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT &&
d89b3e
+            disk->mirrorJob == VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT)
d89b3e
+            type = disk->mirrorJob;
d89b3e
+        path = virDomainDiskGetSource(disk);
d89b3e
+        event = virDomainEventBlockJobNewFromObj(vm, path, type, status);
d89b3e
+        event2 = virDomainEventBlockJob2NewFromObj(vm, disk->dst, type,
d89b3e
+                                                   status);
d89b3e
+
d89b3e
+        /* If we completed a block pull or commit, then update the XML
d89b3e
+         * to match.  */
d89b3e
+        switch ((virConnectDomainEventBlockJobStatus) status) {
d89b3e
+        case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
d89b3e
+            if (disk->mirrorState == VIR_DOMAIN_DISK_MIRROR_STATE_PIVOT) {
d89b3e
+                if (vm->newDef) {
d89b3e
+                    int indx = virDomainDiskIndexByName(vm->newDef, disk->dst,
d89b3e
+                                                        false);
d89b3e
+                    virStorageSourcePtr copy = NULL;
d89b3e
+
d89b3e
+                    if (indx >= 0) {
d89b3e
+                        persistDisk = vm->newDef->disks[indx];
d89b3e
+                        copy = virStorageSourceCopy(disk->mirror, false);
d89b3e
+                        if (virStorageSourceInitChainElement(copy,
d89b3e
+                                                             persistDisk->src,
d89b3e
+                                                             true) < 0) {
d89b3e
+                            VIR_WARN("Unable to update persistent definition "
d89b3e
+                                     "on vm %s after block job",
d89b3e
+                                     vm->def->name);
d89b3e
+                            virStorageSourceFree(copy);
d89b3e
+                            copy = NULL;
d89b3e
+                            persistDisk = NULL;
d89b3e
+                        }
d89b3e
+                    }
d89b3e
+                    if (copy) {
d89b3e
+                        virStorageSourceFree(persistDisk->src);
d89b3e
+                        persistDisk->src = copy;
d89b3e
+                    }
d89b3e
+                }
d89b3e
+
d89b3e
+                /* XXX We want to revoke security labels and disk
d89b3e
+                 * lease, as well as audit that revocation, before
d89b3e
+                 * dropping the original source.  But it gets tricky
d89b3e
+                 * if both source and mirror share common backing
d89b3e
+                 * files (we want to only revoke the non-shared
d89b3e
+                 * portion of the chain); so for now, we leak the
d89b3e
+                 * access to the original.  */
d89b3e
+                virStorageSourceFree(disk->src);
d89b3e
+                disk->src = disk->mirror;
d89b3e
+            } else {
d89b3e
+                virStorageSourceFree(disk->mirror);
d89b3e
+            }
d89b3e
+
d89b3e
+            /* Recompute the cached backing chain to match our
d89b3e
+             * updates.  Better would be storing the chain ourselves
d89b3e
+             * rather than reprobing, but we haven't quite completed
d89b3e
+             * that conversion to use our XML tracking. */
d89b3e
+            disk->mirror = NULL;
d89b3e
+            save = disk->mirrorState != VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
d89b3e
+            disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
d89b3e
+            disk->mirrorJob = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
d89b3e
+            ignore_value(qemuDomainDetermineDiskChain(driver, vm, disk,
d89b3e
+                                                      true, true));
d89b3e
+            break;
d89b3e
+
d89b3e
+        case VIR_DOMAIN_BLOCK_JOB_READY:
d89b3e
+            disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_READY;
d89b3e
+            save = true;
d89b3e
+            break;
d89b3e
+
d89b3e
+        case VIR_DOMAIN_BLOCK_JOB_FAILED:
d89b3e
+        case VIR_DOMAIN_BLOCK_JOB_CANCELED:
d89b3e
+            virStorageSourceFree(disk->mirror);
d89b3e
+            disk->mirror = NULL;
d89b3e
+            disk->mirrorState = status == VIR_DOMAIN_BLOCK_JOB_FAILED ?
d89b3e
+                VIR_DOMAIN_DISK_MIRROR_STATE_ABORT : VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
d89b3e
+            disk->mirrorJob = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
d89b3e
+            save = true;
d89b3e
+            break;
d89b3e
+
d89b3e
+        case VIR_DOMAIN_BLOCK_JOB_LAST:
d89b3e
+            break;
d89b3e
+        }
d89b3e
+    }
d89b3e
+
d89b3e
+    if (save) {
d89b3e
+        if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0)
d89b3e
+            VIR_WARN("Unable to save status on vm %s after block job",
d89b3e
+                     vm->def->name);
d89b3e
+        if (persistDisk && virDomainSaveConfig(cfg->configDir,
d89b3e
+                                               vm->newDef) < 0)
d89b3e
+            VIR_WARN("Unable to update persistent definition on vm %s "
d89b3e
+                     "after block job", vm->def->name);
d89b3e
+    }
d89b3e
+    virObjectUnlock(vm);
d89b3e
+    virObjectUnref(cfg);
d89b3e
+
d89b3e
+    if (event)
d89b3e
+        qemuDomainEventQueue(driver, event);
d89b3e
+    if (event2)
d89b3e
+        qemuDomainEventQueue(driver, event2);
d89b3e
+
d89b3e
+ endjob:
d89b3e
+    ignore_value(qemuDomainObjEndJob(driver, vm));
d89b3e
+ cleanup:
d89b3e
+    VIR_FREE(diskAlias);
d89b3e
+}
d89b3e
+
d89b3e
+
d89b3e
 static void qemuProcessEventHandler(void *data, void *opaque)
d89b3e
 {
d89b3e
     struct qemuProcessEvent *processEvent = data;
d89b3e
@@ -4427,6 +4562,13 @@ static void qemuProcessEventHandler(void *data, void *opaque)
d89b3e
     case QEMU_PROCESS_EVENT_SERIAL_CHANGED:
d89b3e
         processSerialChangedEvent(driver, vm, processEvent->data,
d89b3e
                                   processEvent->action);
d89b3e
+        break;
d89b3e
+    case QEMU_PROCESS_EVENT_BLOCK_JOB:
d89b3e
+        processBlockJobEvent(driver, vm,
d89b3e
+                             processEvent->data,
d89b3e
+                             processEvent->action,
d89b3e
+                             processEvent->status);
d89b3e
+        break;
d89b3e
     case QEMU_PROCESS_EVENT_LAST:
d89b3e
         break;
d89b3e
     }
d89b3e
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
d89b3e
index ffba29d..83a59a1 100644
d89b3e
--- a/src/qemu/qemu_process.c
d89b3e
+++ b/src/qemu/qemu_process.c
d89b3e
@@ -1024,123 +1024,42 @@ qemuProcessHandleBlockJob(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
d89b3e
                           void *opaque)
d89b3e
 {
d89b3e
     virQEMUDriverPtr driver = opaque;
d89b3e
-    virObjectEventPtr event = NULL;
d89b3e
-    virObjectEventPtr event2 = NULL;
d89b3e
-    const char *path;
d89b3e
-    virDomainDiskDefPtr disk;
d89b3e
-    virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
d89b3e
-    virDomainDiskDefPtr persistDisk = NULL;
d89b3e
-    bool save = false;
d89b3e
+    struct qemuProcessEvent *processEvent = NULL;
d89b3e
+    char *data;
d89b3e
 
d89b3e
     virObjectLock(vm);
d89b3e
-    disk = qemuProcessFindDomainDiskByAlias(vm, diskAlias);
d89b3e
 
d89b3e
-    if (disk) {
d89b3e
-        /* Have to generate two variants of the event for old vs. new
d89b3e
-         * client callbacks */
d89b3e
-        if (type == VIR_DOMAIN_BLOCK_JOB_TYPE_COMMIT &&
d89b3e
-            disk->mirrorJob == VIR_DOMAIN_BLOCK_JOB_TYPE_ACTIVE_COMMIT)
d89b3e
-            type = disk->mirrorJob;
d89b3e
-        path = virDomainDiskGetSource(disk);
d89b3e
-        event = virDomainEventBlockJobNewFromObj(vm, path, type, status);
d89b3e
-        event2 = virDomainEventBlockJob2NewFromObj(vm, disk->dst, type,
d89b3e
-                                                   status);
d89b3e
+    VIR_DEBUG("Block job for device %s (domain: %p,%s) type %d status %d",
d89b3e
+              diskAlias, vm, vm->def->name, type, status);
d89b3e
 
d89b3e
-        /* If we completed a block pull or commit, then update the XML
d89b3e
-         * to match.  */
d89b3e
-        switch ((virConnectDomainEventBlockJobStatus) status) {
d89b3e
-        case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
d89b3e
-            if (disk->mirrorState == VIR_DOMAIN_DISK_MIRROR_STATE_PIVOT) {
d89b3e
-                if (vm->newDef) {
d89b3e
-                    int indx = virDomainDiskIndexByName(vm->newDef, disk->dst,
d89b3e
-                                                        false);
d89b3e
-                    virStorageSourcePtr copy = NULL;
d89b3e
+    if (VIR_ALLOC(processEvent) < 0)
d89b3e
+        goto error;
d89b3e
 
d89b3e
-                    if (indx >= 0) {
d89b3e
-                        persistDisk = vm->newDef->disks[indx];
d89b3e
-                        copy = virStorageSourceCopy(disk->mirror, false);
d89b3e
-                        if (virStorageSourceInitChainElement(copy,
d89b3e
-                                                             persistDisk->src,
d89b3e
-                                                             true) < 0) {
d89b3e
-                            VIR_WARN("Unable to update persistent definition "
d89b3e
-                                     "on vm %s after block job",
d89b3e
-                                     vm->def->name);
d89b3e
-                            virStorageSourceFree(copy);
d89b3e
-                            copy = NULL;
d89b3e
-                            persistDisk = NULL;
d89b3e
-                        }
d89b3e
-                    }
d89b3e
-                    if (copy) {
d89b3e
-                        virStorageSourceFree(persistDisk->src);
d89b3e
-                        persistDisk->src = copy;
d89b3e
-                    }
d89b3e
-                }
d89b3e
+    processEvent->eventType = QEMU_PROCESS_EVENT_BLOCK_JOB;
d89b3e
+    if (VIR_STRDUP(data, diskAlias) < 0)
d89b3e
+        goto error;
d89b3e
+    processEvent->data = data;
d89b3e
+    processEvent->vm = vm;
d89b3e
+    processEvent->action = type;
d89b3e
+    processEvent->status = status;
d89b3e
 
d89b3e
-                /* XXX We want to revoke security labels and disk
d89b3e
-                 * lease, as well as audit that revocation, before
d89b3e
-                 * dropping the original source.  But it gets tricky
d89b3e
-                 * if both source and mirror share common backing
d89b3e
-                 * files (we want to only revoke the non-shared
d89b3e
-                 * portion of the chain); so for now, we leak the
d89b3e
-                 * access to the original.  */
d89b3e
-                virStorageSourceFree(disk->src);
d89b3e
-                disk->src = disk->mirror;
d89b3e
-            } else {
d89b3e
-                virStorageSourceFree(disk->mirror);
d89b3e
-            }
d89b3e
-
d89b3e
-            /* Recompute the cached backing chain to match our
d89b3e
-             * updates.  Better would be storing the chain ourselves
d89b3e
-             * rather than reprobing, but we haven't quite completed
d89b3e
-             * that conversion to use our XML tracking. */
d89b3e
-            disk->mirror = NULL;
d89b3e
-            save = disk->mirrorState != VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
d89b3e
-            disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
d89b3e
-            disk->mirrorJob = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
d89b3e
-            ignore_value(qemuDomainDetermineDiskChain(driver, vm, disk,
d89b3e
-                                                      true, true));
d89b3e
-            break;
d89b3e
-
d89b3e
-        case VIR_DOMAIN_BLOCK_JOB_READY:
d89b3e
-            disk->mirrorState = VIR_DOMAIN_DISK_MIRROR_STATE_READY;
d89b3e
-            save = true;
d89b3e
-            break;
d89b3e
-
d89b3e
-        case VIR_DOMAIN_BLOCK_JOB_FAILED:
d89b3e
-        case VIR_DOMAIN_BLOCK_JOB_CANCELED:
d89b3e
-            virStorageSourceFree(disk->mirror);
d89b3e
-            disk->mirror = NULL;
d89b3e
-            disk->mirrorState = status == VIR_DOMAIN_BLOCK_JOB_FAILED ?
d89b3e
-                VIR_DOMAIN_DISK_MIRROR_STATE_ABORT : VIR_DOMAIN_DISK_MIRROR_STATE_NONE;
d89b3e
-            disk->mirrorJob = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
d89b3e
-            save = true;
d89b3e
-            break;
d89b3e
-
d89b3e
-        case VIR_DOMAIN_BLOCK_JOB_LAST:
d89b3e
-            break;
d89b3e
-        }
d89b3e
+    virObjectRef(vm);
d89b3e
+    if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) {
d89b3e
+        ignore_value(virObjectUnref(vm));
d89b3e
+        goto error;
d89b3e
     }
d89b3e
 
d89b3e
-    if (save) {
d89b3e
-        if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm) < 0)
d89b3e
-            VIR_WARN("Unable to save status on vm %s after block job",
d89b3e
-                     vm->def->name);
d89b3e
-        if (persistDisk && virDomainSaveConfig(cfg->configDir,
d89b3e
-                                               vm->newDef) < 0)
d89b3e
-            VIR_WARN("Unable to update persistent definition on vm %s "
d89b3e
-                     "after block job", vm->def->name);
d89b3e
-    }
d89b3e
+ cleanup:
d89b3e
     virObjectUnlock(vm);
d89b3e
-    virObjectUnref(cfg);
d89b3e
-
d89b3e
-    if (event)
d89b3e
-        qemuDomainEventQueue(driver, event);
d89b3e
-    if (event2)
d89b3e
-        qemuDomainEventQueue(driver, event2);
d89b3e
-
d89b3e
     return 0;
d89b3e
+ error:
d89b3e
+    if (processEvent)
d89b3e
+        VIR_FREE(processEvent->data);
d89b3e
+    VIR_FREE(processEvent);
d89b3e
+    goto cleanup;
d89b3e
 }
d89b3e
 
d89b3e
+
d89b3e
 static int
d89b3e
 qemuProcessHandleGraphics(qemuMonitorPtr mon ATTRIBUTE_UNUSED,
d89b3e
                           virDomainObjPtr vm,
d89b3e
-- 
d89b3e
2.3.3
d89b3e