[libvirt] [PATCH] blockjob: add virsh blockjob --wait

Michal Privoznik mprivozn at redhat.com
Fri Apr 13 07:29:21 UTC 2012


On 12.04.2012 21:59, Eric Blake wrote:
> I'm tired of shell-scripting to wait for completion of a block pull,
> when virsh can be taught to do the same.  I couldn't quite reuse
> vshWatchJob, as this is not a case of a long-running command where
> a second thread must be used to probe job status (at least, not unless
> I make virsh start doing blocking waits for an event to fire), but it
> served as inspiration for my simpler single-threaded loop.  There is
> up to a half-second delay between sending SIGINT and the job being
> aborted, but I didn't think it worth the complexity of a second thread
> and use of poll() just to minimize that delay.
> 
> * tools/virsh.c (cmdBlockPull): Add new options to wait for
> completion.
> (blockJobImpl): Add argument.
> (cmdBlockJob): Adjust caller.
> * tools/virsh.pod (blockjob): Document new mode.
> ---
>  tools/virsh.c   |  122 +++++++++++++++++++++++++++++++++++++++++++++++++-----
>  tools/virsh.pod |   11 ++++-
>  2 files changed, 119 insertions(+), 14 deletions(-)
> 
> diff --git a/tools/virsh.c b/tools/virsh.c
> index 8ef57e0..c54add9 100644
> --- a/tools/virsh.c
> +++ b/tools/virsh.c
> @@ -7274,11 +7274,11 @@ repoll:
>              if (pollfd.revents & POLLIN &&
>                  saferead(pipe_fd, &retchar, sizeof(retchar)) > 0 &&
>                  retchar == '0') {
> -                    if (verbose) {
> -                        /* print [100 %] */
> -                        print_job_progress(label, 0, 1);
> -                    }
> -                    break;
> +                if (verbose) {
> +                    /* print [100 %] */
> +                    print_job_progress(label, 0, 1);
> +                }
> +                break;
>              }
>              goto cleanup;
>          }
> @@ -7295,8 +7295,9 @@ repoll:
>          }
> 
>          GETTIMEOFDAY(&curr);
> -        if ( timeout && ((int)(curr.tv_sec - start.tv_sec)  * 1000 + \
> -                         (int)(curr.tv_usec - start.tv_usec) / 1000) > timeout * 1000 ) {
> +        if (timeout && (((int)(curr.tv_sec - start.tv_sec)  * 1000 +
> +                         (int)(curr.tv_usec - start.tv_usec) / 1000) >
> +                        timeout * 1000)) {
>              /* suspend the domain when migration timeouts. */
>              vshDebug(ctl, VSH_ERR_DEBUG, "%s timeout", label);
>              if (timeout_func)

These two chunks are rather cosmetic but I'm okay with having them in
this patch not a separate one.

> @@ -7519,7 +7520,8 @@ typedef enum {
> 
>  static int
>  blockJobImpl(vshControl *ctl, const vshCmd *cmd,
> -              virDomainBlockJobInfoPtr info, int mode)
> +             virDomainBlockJobInfoPtr info, int mode,
> +             virDomainPtr *pdom)
>  {
>      virDomainPtr dom = NULL;
>      const char *name, *path;
> @@ -7560,7 +7562,9 @@ blockJobImpl(vshControl *ctl, const vshCmd *cmd,
>      }
> 
>  cleanup:
> -    if (dom)
> +    if (pdom && ret == 0)
> +        *pdom = dom;
> +    else if (dom)
>          virDomainFree(dom);
>      return ret;
>  }
> @@ -7580,15 +7584,109 @@ static const vshCmdOptDef opts_block_pull[] = {
>      {"bandwidth", VSH_OT_DATA, VSH_OFLAG_NONE, N_("Bandwidth limit in MB/s")},
>      {"base", VSH_OT_DATA, VSH_OFLAG_NONE,
>       N_("path of backing file in chain for a partial pull")},
> +    {"wait", VSH_OT_BOOL, 0, N_("wait for job to finish")},
> +    {"verbose", VSH_OT_BOOL, 0, N_("with --wait, display the progress")},
> +    {"timeout", VSH_OT_INT, VSH_OFLAG_NONE,
> +     N_("with --wait, abort if pull exceeds timeout (in seconds)")},
>      {NULL, 0, 0, NULL}
>  };
> 
>  static bool
>  cmdBlockPull(vshControl *ctl, const vshCmd *cmd)
>  {
> -    if (blockJobImpl(ctl, cmd, NULL, VSH_CMD_BLOCK_JOB_PULL) != 0)
> +    virDomainPtr dom = NULL;
> +    bool ret = false;
> +    bool blocking = vshCommandOptBool(cmd, "wait");
> +    bool verbose = vshCommandOptBool(cmd, "verbose");
> +    int timeout = 0;
> +    struct sigaction sig_action;
> +    struct sigaction old_sig_action;
> +    sigset_t sigmask;
> +    struct timeval start;
> +    struct timeval curr;
> +    const char *path = NULL;
> +    bool quit = false;
> +
> +    if (blocking) {
> +        if (vshCommandOptInt(cmd, "timeout", &timeout) > 0) {
> +            if (timeout < 1) {
> +                vshError(ctl, "%s", _("migrate: Invalid timeout"));
> +                return false;
> +            }
> +
> +            /* Ensure that we can multiply by 1000 without overflowing. */
> +            if (timeout > INT_MAX / 1000) {
> +                vshError(ctl, "%s", _("migrate: Timeout is too big"));
> +                return false;
> +            }
> +        }
> +        if (vshCommandOptString(cmd, "path", &path) < 0)
> +            return false;
> +
> +        sigemptyset(&sigmask);
> +        sigaddset(&sigmask, SIGINT);
> +
> +        intCaught = 0;
> +        sig_action.sa_sigaction = vshCatchInt;
> +        sigemptyset(&sig_action.sa_mask);
> +        sigaction(SIGINT, &sig_action, &old_sig_action);
> +
> +        GETTIMEOFDAY(&start);
> +    } else if (verbose || vshCommandOptBool(cmd, "timeout")) {
> +        vshError(ctl, "%s", _("blocking control options require --wait"));
>          return false;
> -    return true;
> +    }
> +
> +    if (blockJobImpl(ctl, cmd, NULL, VSH_CMD_BLOCK_JOB_PULL,
> +                     blocking ? &dom : NULL) != 0)
> +        goto cleanup;
> +
> +    while (blocking) {
> +        virDomainBlockJobInfo info;
> +        int result = virDomainGetBlockJobInfo(dom, path, &info, 0);
> +
> +        if (result < 0) {
> +            vshError(ctl, _("failed to query job for disk %s"), path);
> +            goto cleanup;
> +        }
> +        if (result == 0)
> +            break;
> +
> +        if (verbose)
> +            print_job_progress(_("Block Pull"), info.end - info.cur, info.end);
> +
> +        GETTIMEOFDAY(&curr);
> +        if (intCaught || (timeout &&
> +                          (((int)(curr.tv_sec - start.tv_sec)  * 1000 +
> +                            (int)(curr.tv_usec - start.tv_usec) / 1000) >
> +                           timeout * 1000))) {
> +            vshDebug(ctl, VSH_ERR_DEBUG,
> +                     intCaught ? "interrupted" : "timeout");
> +            intCaught = 0;
> +            timeout = 0;
> +            quit = true;
> +            if (virDomainBlockJobAbort(dom, path, 0) < 0) {
> +                vshError(ctl, _("failed to abort job for disk %s"), path);
> +                goto cleanup;
> +            }

Don't we need blocking = false; here? Otherwise we may spin another
rounds and issue GetBlockJobInfo API over and over again. But on the
other hand, I can imagine this is desired behavior. If BlockJobAbort
succeeds and returns 0 (we are enforcing synchronous operation here) it
should mean that block job has really been aborted. Therefore we may
issue GetBlockJobInfo to ascertain ourselves.

> +        }
> +
> +        usleep(500 * 1000);
> +    }
> +
> +    if (verbose && !quit) {
> +        /* printf [100 %] */
> +        print_job_progress(_("Block Pull"), 0, 1);
> +    }
> +    vshPrint(ctl, "\n%s\n", quit ? _("Pull aborted") : _("Pull complete"));
> +
> +    ret = true;
> +cleanup:
> +    if (dom)
> +        virDomainFree(dom);
> +    if (blocking)
> +        sigaction(SIGINT, &old_sig_action, NULL);
> +    return ret;
>  }
> 
>  /*
> @@ -7639,7 +7737,7 @@ cmdBlockJob(vshControl *ctl, const vshCmd *cmd)
>      else
>          mode = VSH_CMD_BLOCK_JOB_INFO;
> 
> -    ret = blockJobImpl(ctl, cmd, &info, mode);
> +    ret = blockJobImpl(ctl, cmd, &info, mode, NULL);
>      if (ret < 0)
>          return false;
> 
> diff --git a/tools/virsh.pod b/tools/virsh.pod
> index 5f3d9b1..fbc34f1 100644
> --- a/tools/virsh.pod
> +++ b/tools/virsh.pod
> @@ -640,6 +640,7 @@ address of virtual interface (such as I<detach-interface> or
>  I<domif-setlink>) will accept the MAC address printed by this command.
> 
>  =item B<blockpull> I<domain> I<path> [I<bandwidth>] [I<base>]
> +[I<--wait> [I<--verbose>] [I<--timeout> B<seconds>]]
> 
>  Populate a disk from its backing image chain. By default, this command
>  flattens the entire chain; but if I<base> is specified, containing the
> @@ -647,8 +648,14 @@ name of one of the backing files in the chain, then that file becomes
>  the new backing file and only the intermediate portion of the chain is
>  pulled.  Once all requested data from the backing image chain has been
>  pulled, the disk no longer depends on that portion of the backing chain.
> -It pulls data for the entire disk in the background, the process of the
> -operation can be checked with B<blockjob>.
> +
> +By default, this command returns as soon as possible, and data for
> +the entire disk is pulled in the background; the process of the
> +operation can be checked with B<blockjob>.  However, if I<--wait> is
> +specified, then this command will block until the operation completes,
> +or cancel the operation if the optional I<timeout> in seconds elapses
> +or SIGINT is sent (usually with C<Ctrl-C>).  Using I<--verbose> along
> +with I<--wait> will produce periodic status updates.
> 
>  I<path> specifies fully-qualified path of the disk; it corresponds
>  to a unique target name (<target dev='name'/>) or source file (<source


ACK

Michal




More information about the libvir-list mailing list