render / rpms / libvirt

Forked from rpms/libvirt 9 months ago
Clone
7a3408
From 043fdb6ee0ef322f8cf925d8515d0dd5adfe2ca7 Mon Sep 17 00:00:00 2001
7a3408
Message-Id: <043fdb6ee0ef322f8cf925d8515d0dd5adfe2ca7@dist-git>
7a3408
From: Peter Krempa <pkrempa@redhat.com>
7a3408
Date: Tue, 21 Jul 2015 16:18:33 +0200
7a3408
Subject: [PATCH] virsh: Refactor block job waiting in cmdBlockPull
7a3408
7a3408
https://bugzilla.redhat.com/show_bug.cgi?id=1227551
7a3408
https://bugzilla.redhat.com/show_bug.cgi?id=1197592
7a3408
7a3408
Introduce helper function that will provide logic for waiting for block
7a3408
job completion so the 3 open coded places can be unified and improved.
7a3408
7a3408
This patch introduces the whole logic and uses it to fix
7a3408
cmdBlockJobPull. The vshBlockJobWait function provides common logic for
7a3408
block job waiting that should be robust enough to work across all
7a3408
previous versions of libvirt. Since virsh allows passing user-provided
7a3408
strings as paths of block devices we can't reliably use block job events
7a3408
for detection of block job states so the function contains a great deal
7a3408
of fallback logic.
7a3408
7a3408
(cherry picked from commit 2e7827636476fdf976f17cd234b636973dedffc0)
7a3408
7a3408
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
7a3408
---
7a3408
 tools/virsh-domain.c | 326 ++++++++++++++++++++++++++++++++++++++-------------
7a3408
 1 file changed, 244 insertions(+), 82 deletions(-)
7a3408
7a3408
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c
7a3408
index 2f6ad46..385eba2 100644
7a3408
--- a/tools/virsh-domain.c
7a3408
+++ b/tools/virsh-domain.c
7a3408
@@ -53,6 +53,7 @@
7a3408
 #include "virsh-console.h"
7a3408
 #include "virsh-domain-monitor.h"
7a3408
 #include "virerror.h"
7a3408
+#include "virtime.h"
7a3408
 #include "virtypedparam.h"
7a3408
 #include "virxml.h"
7a3408
 
7a3408
@@ -1702,17 +1703,237 @@ static void vshCatchInt(int sig ATTRIBUTE_UNUSED,
7a3408
     intCaught = 1;
7a3408
 }
7a3408
 
7a3408
+
7a3408
+typedef struct _vshBlockJobWaitData vshBlockJobWaitData;
7a3408
+typedef vshBlockJobWaitData *vshBlockJobWaitDataPtr;
7a3408
+struct _vshBlockJobWaitData {
7a3408
+    vshControl *ctl;
7a3408
+    virDomainPtr dom;
7a3408
+    const char *dev;
7a3408
+    const char *job_name;
7a3408
+
7a3408
+    bool verbose;
7a3408
+    unsigned int timeout;
7a3408
+    bool async_abort;
7a3408
+
7a3408
+    int cb_id;
7a3408
+    int cb_id2;
7a3408
+    int status;
7a3408
+};
7a3408
+
7a3408
+
7a3408
 static void
7a3408
 vshBlockJobStatusHandler(virConnectPtr conn ATTRIBUTE_UNUSED,
7a3408
                          virDomainPtr dom ATTRIBUTE_UNUSED,
7a3408
-                         const char *disk ATTRIBUTE_UNUSED,
7a3408
+                         const char *disk,
7a3408
                          int type ATTRIBUTE_UNUSED,
7a3408
                          int status,
7a3408
                          void *opaque)
7a3408
 {
7a3408
-    *(int *) opaque = status;
7a3408
+    vshBlockJobWaitDataPtr data = opaque;
7a3408
+
7a3408
+    if (STREQ_NULLABLE(disk, data->dev))
7a3408
+        data->status = status;
7a3408
 }
7a3408
 
7a3408
+
7a3408
+/**
7a3408
+ * vshBlockJobWaitInit:
7a3408
+ * @ctl: vsh control structure
7a3408
+ * @dom: domain object
7a3408
+ * @dev: block device name to wait for
7a3408
+ * @job_name: block job name to display in user-facing messages
7a3408
+ * @verbose: enable progress reporting
7a3408
+ * @timeout: number of milliseconds to wait before aborting the job
7a3408
+ * @async_abort: abort the job asynchronously
7a3408
+ *
7a3408
+ * Prepares virsh for waiting for completion of a block job. This function
7a3408
+ * registers event handlers for block job events and prepares the data structures
7a3408
+ * for them. A call to vshBlockJobWait then waits for completion of the given
7a3408
+ * block job. This function should be tolerant to different versions of daemon
7a3408
+ * and the reporting capabilities of those.
7a3408
+ *
7a3408
+ * Returns the data structure that holds data needed for block job waiting or
7a3408
+ * NULL in case of error.
7a3408
+ */
7a3408
+static vshBlockJobWaitDataPtr
7a3408
+vshBlockJobWaitInit(vshControl *ctl,
7a3408
+                    virDomainPtr dom,
7a3408
+                    const char *dev,
7a3408
+                    const char *job_name,
7a3408
+                    bool verbose,
7a3408
+                    unsigned int timeout,
7a3408
+                    bool async_abort)
7a3408
+{
7a3408
+    vshBlockJobWaitDataPtr ret;
7a3408
+
7a3408
+    if (VIR_ALLOC(ret) < 0)
7a3408
+        return NULL;
7a3408
+
7a3408
+    ret->ctl = ctl;
7a3408
+    ret->dom = dom;
7a3408
+    ret->dev = dev;
7a3408
+    ret->job_name = job_name;
7a3408
+
7a3408
+    ret->async_abort = async_abort;
7a3408
+    ret->timeout = timeout;
7a3408
+    ret->verbose = verbose;
7a3408
+
7a3408
+    ret->status = -1;
7a3408
+
7a3408
+    virConnectDomainEventGenericCallback cb =
7a3408
+        VIR_DOMAIN_EVENT_CALLBACK(vshBlockJobStatusHandler);
7a3408
+
7a3408
+    if ((ret->cb_id = virConnectDomainEventRegisterAny(ctl->conn, dom,
7a3408
+                                                       VIR_DOMAIN_EVENT_ID_BLOCK_JOB,
7a3408
+                                                       cb, ret, NULL)) < 0)
7a3408
+        vshResetLibvirtError();
7a3408
+
7a3408
+    if ((ret->cb_id2 = virConnectDomainEventRegisterAny(ctl->conn, dom,
7a3408
+                                                        VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2,
7a3408
+                                                        cb, ret, NULL)) < 0)
7a3408
+        vshResetLibvirtError();
7a3408
+
7a3408
+    return ret;
7a3408
+}
7a3408
+
7a3408
+
7a3408
+static void
7a3408
+vshBlockJobWaitFree(vshBlockJobWaitDataPtr data)
7a3408
+{
7a3408
+    if (!data)
7a3408
+        return;
7a3408
+
7a3408
+    if (data->cb_id >= 0)
7a3408
+        virConnectDomainEventDeregisterAny(data->ctl->conn, data->cb_id);
7a3408
+    if (data->cb_id2 >= 0)
7a3408
+        virConnectDomainEventDeregisterAny(data->ctl->conn, data->cb_id2);
7a3408
+
7a3408
+    VIR_FREE(data);
7a3408
+}
7a3408
+
7a3408
+
7a3408
+/**
7a3408
+ * vshBlockJobWait:
7a3408
+ * @data: private data initialized by vshBlockJobWaitInit
7a3408
+ *
7a3408
+ * Waits for the block job to complete. This function prefers to get an event
7a3408
+ * from libvirt but still has fallback means if the device name can't be matched
7a3408
+ *
7a3408
+ * This function returns values from the virConnectDomainEventBlockJobStatus enum
7a3408
+ * or -1 in case of a internal error. Fallback states if a block job vanishes
7a3408
+ * without triggering the event is VIR_DOMAIN_BLOCK_JOB_COMPLETED. For two phase
7a3408
+ * jobs after the retry count for waiting for the event expires is
7a3408
+ * VIR_DOMAIN_BLOCK_JOB_READY.
7a3408
+ */
7a3408
+static int
7a3408
+vshBlockJobWait(vshBlockJobWaitDataPtr data)
7a3408
+{
7a3408
+    /* For two phase jobs like active commit or block copy, the marker reaches
7a3408
+     * 100% and an event fires. In case where virsh would not be able to match
7a3408
+     * the event to the given block job we will wait for the number of retries
7a3408
+     * before claiming that we entered synchronised phase */
7a3408
+    unsigned int retries = 5;
7a3408
+
7a3408
+    struct sigaction sig_action;
7a3408
+    struct sigaction old_sig_action;
7a3408
+    sigset_t sigmask, oldsigmask;
7a3408
+
7a3408
+    unsigned long long start = 0;
7a3408
+    unsigned long long curr = 0;
7a3408
+
7a3408
+    unsigned int abort_flags = 0;
7a3408
+    int ret = -1;
7a3408
+    virDomainBlockJobInfo info;
7a3408
+    int result;
7a3408
+
7a3408
+    if (!data)
7a3408
+        return 0;
7a3408
+
7a3408
+    if (data->async_abort)
7a3408
+        abort_flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC;
7a3408
+
7a3408
+    sigemptyset(&sigmask);
7a3408
+    sigaddset(&sigmask, SIGINT);
7a3408
+
7a3408
+    intCaught = 0;
7a3408
+    sig_action.sa_sigaction = vshCatchInt;
7a3408
+    sig_action.sa_flags = SA_SIGINFO;
7a3408
+    sigemptyset(&sig_action.sa_mask);
7a3408
+    sigaction(SIGINT, &sig_action, &old_sig_action);
7a3408
+
7a3408
+    if (data->timeout && virTimeMillisNow(&start) < 0) {
7a3408
+        vshSaveLibvirtError();
7a3408
+        return -1;
7a3408
+    }
7a3408
+
7a3408
+    while (true) {
7a3408
+        pthread_sigmask(SIG_BLOCK, &sigmask, &oldsigmask);
7a3408
+        result = virDomainGetBlockJobInfo(data->dom, data->dev, &info, 0);
7a3408
+        pthread_sigmask(SIG_SETMASK, &oldsigmask, NULL);
7a3408
+
7a3408
+        if (result < 0) {
7a3408
+            vshError(data->ctl, _("failed to query job for disk %s"), data->dev);
7a3408
+            goto cleanup;
7a3408
+        }
7a3408
+
7a3408
+        /* if we've got an event for the device we are waiting for we can end
7a3408
+         * the waiting loop */
7a3408
+        if ((data->cb_id >= 0 || data->cb_id2 >= 0) && data->status != -1) {
7a3408
+            ret = data->status;
7a3408
+            goto cleanup;
7a3408
+        }
7a3408
+
7a3408
+        /* since virsh can't guarantee that the path provided by the user will
7a3408
+         * later be matched in the event we will need to keep the fallback
7a3408
+         * approach and claim success if the block job finishes or vanishes. */
7a3408
+        if (result == 0)
7a3408
+            break;
7a3408
+
7a3408
+        /* for two-phase jobs we will try to wait in the synchronized phase
7a3408
+         * for event arrival since 100% completion doesn't necessarily mean that
7a3408
+         * the block job has finished and can be terminated with success */
7a3408
+        if (info.end == info.cur && --retries == 0) {
7a3408
+            ret = VIR_DOMAIN_BLOCK_JOB_READY;
7a3408
+            goto cleanup;
7a3408
+        }
7a3408
+
7a3408
+        if (data->verbose)
7a3408
+            vshPrintJobProgress(data->job_name, info.end - info.cur, info.end);
7a3408
+
7a3408
+        if (data->timeout && virTimeMillisNow(&curr) < 0) {
7a3408
+            vshSaveLibvirtError();
7a3408
+            goto cleanup;
7a3408
+        }
7a3408
+
7a3408
+        if (intCaught || (data->timeout && (curr - start > data->timeout))) {
7a3408
+            if (virDomainBlockJobAbort(data->dom, data->dev, abort_flags) < 0) {
7a3408
+                vshError(data->ctl, _("failed to abort job for disk '%s'"),
7a3408
+                         data->dev);
7a3408
+                goto cleanup;
7a3408
+            }
7a3408
+
7a3408
+            ret = VIR_DOMAIN_BLOCK_JOB_CANCELED;
7a3408
+            goto cleanup;
7a3408
+        }
7a3408
+
7a3408
+        usleep(500 * 1000);
7a3408
+    }
7a3408
+
7a3408
+    ret = VIR_DOMAIN_BLOCK_JOB_COMPLETED;
7a3408
+
7a3408
+ cleanup:
7a3408
+    /* print 100% completed */
7a3408
+    if (data->verbose &&
7a3408
+        (ret == VIR_DOMAIN_BLOCK_JOB_COMPLETED ||
7a3408
+         ret == VIR_DOMAIN_BLOCK_JOB_READY))
7a3408
+        vshPrintJobProgress(data->job_name, 0, 1);
7a3408
+
7a3408
+    sigaction(SIGINT, &old_sig_action, NULL);
7a3408
+    return ret;
7a3408
+}
7a3408
+
7a3408
+
7a3408
 /*
7a3408
  * "blockcommit" command
7a3408
  */
7a3408
@@ -2656,19 +2877,11 @@ cmdBlockPull(vshControl *ctl, const vshCmd *cmd)
7a3408
     bool verbose = vshCommandOptBool(cmd, "verbose");
7a3408
     bool async = vshCommandOptBool(cmd, "async");
7a3408
     int timeout = 0;
7a3408
-    struct sigaction sig_action;
7a3408
-    struct sigaction old_sig_action;
7a3408
-    sigset_t sigmask, oldsigmask;
7a3408
-    struct timeval start;
7a3408
-    struct timeval curr;
7a3408
     const char *path = NULL;
7a3408
     const char *base = NULL;
7a3408
     unsigned long bandwidth = 0;
7a3408
-    bool quit = false;
7a3408
-    int abort_flags = 0;
7a3408
-    int status = -1;
7a3408
-    int cb_id = -1;
7a3408
     unsigned int flags = 0;
7a3408
+    vshBlockJobWaitDataPtr bjWait = NULL;
7a3408
 
7a3408
     VSH_REQUIRE_OPTION("verbose", "wait");
7a3408
     VSH_REQUIRE_OPTION("async", "wait");
7a3408
@@ -2688,35 +2901,13 @@ cmdBlockPull(vshControl *ctl, const vshCmd *cmd)
7a3408
     if (vshCommandOptBool(cmd, "keep-relative"))
7a3408
         flags |= VIR_DOMAIN_BLOCK_REBASE_RELATIVE;
7a3408
 
7a3408
-    if (async)
7a3408
-        abort_flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC;
7a3408
-
7a3408
-    if (blocking) {
7a3408
-        sigemptyset(&sigmask);
7a3408
-        sigaddset(&sigmask, SIGINT);
7a3408
-
7a3408
-        intCaught = 0;
7a3408
-        sig_action.sa_sigaction = vshCatchInt;
7a3408
-        sig_action.sa_flags = SA_SIGINFO;
7a3408
-        sigemptyset(&sig_action.sa_mask);
7a3408
-        sigaction(SIGINT, &sig_action, &old_sig_action);
7a3408
-
7a3408
-        GETTIMEOFDAY(&start;;
7a3408
-    }
7a3408
-
7a3408
     if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
7a3408
         return false;
7a3408
 
7a3408
-    virConnectDomainEventGenericCallback cb =
7a3408
-        VIR_DOMAIN_EVENT_CALLBACK(vshBlockJobStatusHandler);
7a3408
-
7a3408
-    if ((cb_id = virConnectDomainEventRegisterAny(ctl->conn,
7a3408
-                                                  dom,
7a3408
-                                                  VIR_DOMAIN_EVENT_ID_BLOCK_JOB,
7a3408
-                                                  cb,
7a3408
-                                                  &status,
7a3408
-                                                  NULL)) < 0)
7a3408
-        vshResetLibvirtError();
7a3408
+    if (blocking &&
7a3408
+        !(bjWait = vshBlockJobWaitInit(ctl, dom, path, _("Block Pull"), verbose,
7a3408
+                                       timeout, async)))
7a3408
+        goto cleanup;
7a3408
 
7a3408
     if (base || flags) {
7a3408
         if (virDomainBlockRebase(dom, path, base, bandwidth, flags) < 0)
7a3408
@@ -2732,61 +2923,32 @@ cmdBlockPull(vshControl *ctl, const vshCmd *cmd)
7a3408
         goto cleanup;
7a3408
     }
7a3408
 
7a3408
-    while (blocking) {
7a3408
-        virDomainBlockJobInfo info;
7a3408
-        int result;
7a3408
-
7a3408
-        pthread_sigmask(SIG_BLOCK, &sigmask, &oldsigmask);
7a3408
-        result = virDomainGetBlockJobInfo(dom, path, &info, 0);
7a3408
-        pthread_sigmask(SIG_SETMASK, &oldsigmask, NULL);
7a3408
+    /* Execution continues here only if --wait or friends were specified */
7a3408
+    switch (vshBlockJobWait(bjWait)) {
7a3408
+        case -1:
7a3408
+            goto cleanup;
7a3408
 
7a3408
-        if (result < 0) {
7a3408
-            vshError(ctl, _("failed to query job for disk %s"), path);
7a3408
+        case VIR_DOMAIN_BLOCK_JOB_CANCELED:
7a3408
+            vshPrint(ctl, "\n%s", _("Pull aborted"));
7a3408
             goto cleanup;
7a3408
-        }
7a3408
-        if (result == 0)
7a3408
             break;
7a3408
 
7a3408
-        if (verbose)
7a3408
-            vshPrintJobProgress(_("Block Pull"), info.end - info.cur, info.end);
7a3408
-
7a3408
-        GETTIMEOFDAY(&curr);
7a3408
-        if (intCaught || (timeout &&
7a3408
-                          (((int)(curr.tv_sec - start.tv_sec)  * 1000 +
7a3408
-                            (int)(curr.tv_usec - start.tv_usec) / 1000) >
7a3408
-                           timeout))) {
7a3408
-            vshDebug(ctl, VSH_ERR_DEBUG,
7a3408
-                     intCaught ? "interrupted" : "timeout");
7a3408
-            intCaught = 0;
7a3408
-            timeout = 0;
7a3408
-            status = VIR_DOMAIN_BLOCK_JOB_CANCELED;
7a3408
-            if (virDomainBlockJobAbort(dom, path, abort_flags) < 0) {
7a3408
-                vshError(ctl, _("failed to abort job for disk %s"), path);
7a3408
-                goto cleanup;
7a3408
-            }
7a3408
-            if (abort_flags & VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC)
7a3408
-                break;
7a3408
-        } else {
7a3408
-            usleep(500 * 1000);
7a3408
-        }
7a3408
-    }
7a3408
-
7a3408
-    if (status == VIR_DOMAIN_BLOCK_JOB_CANCELED)
7a3408
-        quit = true;
7a3408
+        case VIR_DOMAIN_BLOCK_JOB_FAILED:
7a3408
+            vshPrint(ctl, "\n%s", _("Pull failed"));
7a3408
+            goto cleanup;
7a3408
+            break;
7a3408
 
7a3408
-    if (verbose && !quit) {
7a3408
-        /* printf [100 %] */
7a3408
-        vshPrintJobProgress(_("Block Pull"), 0, 1);
7a3408
+        case VIR_DOMAIN_BLOCK_JOB_READY:
7a3408
+        case VIR_DOMAIN_BLOCK_JOB_COMPLETED:
7a3408
+            vshPrint(ctl, "\n%s", _("Pull complete"));
7a3408
+            break;
7a3408
     }
7a3408
-    vshPrint(ctl, "\n%s", quit ? _("Pull aborted") : _("Pull complete"));
7a3408
 
7a3408
     ret = true;
7a3408
+
7a3408
  cleanup:
7a3408
     virDomainFree(dom);
7a3408
-    if (blocking)
7a3408
-        sigaction(SIGINT, &old_sig_action, NULL);
7a3408
-    if (cb_id >= 0)
7a3408
-        virConnectDomainEventDeregisterAny(ctl->conn, cb_id);
7a3408
+    vshBlockJobWaitFree(bjWait);
7a3408
     return ret;
7a3408
 }
7a3408
 
7a3408
-- 
7a3408
2.5.0
7a3408