From 3b96a892427ad2c617b192b569b8a98bfef0837b Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 12 Apr 2012 13:12:21 -0600 Subject: [PATCH] blockjob: add virsh blockpull --wait 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 | 120 ++++++++++++++++++++++++++++++++++++++++++++++-- tools/virsh.pod | 14 +++++- 2 files changed, 127 insertions(+), 7 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 5400487bf4..95ed7bc273 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -7521,7 +7521,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; @@ -7562,7 +7563,9 @@ blockJobImpl(vshControl *ctl, const vshCmd *cmd, } cleanup: - if (dom) + if (pdom && ret == 0) + *pdom = dom; + else if (dom) virDomainFree(dom); return ret; } @@ -7582,15 +7585,122 @@ 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)")}, + {"async", VSH_OT_BOOL, 0, + N_("with --wait, don't wait for cancel to finish")}, {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; + int abort_flags = 0; + + if (blocking) { + if (vshCommandOptInt(cmd, "timeout", &timeout) > 0) { + if (timeout < 1) { + vshError(ctl, "%s", _("invalid timeout")); + return false; + } + + /* Ensure that we can multiply by 1000 without overflowing. */ + if (timeout > INT_MAX / 1000) { + vshError(ctl, "%s", _("timeout is too big")); + return false; + } + } + if (vshCommandOptString(cmd, "path", &path) < 0) + return false; + if (vshCommandOptBool(cmd, "async")) + abort_flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC; + + 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") || + vshCommandOptBool(cmd, "async")) { + vshError(ctl, "%s", _("blocking control options require --wait")); return false; - return true; + } + + if (blockJobImpl(ctl, cmd, NULL, VSH_CMD_BLOCK_JOB_PULL, &dom) < 0) + goto cleanup; + + if (!blocking) { + vshPrint(ctl, "%s", _("Block Pull started")); + ret = true; + 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, abort_flags) < 0) { + vshError(ctl, _("failed to abort job for disk %s"), path); + goto cleanup; + } + if (abort_flags & VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC) + break; + } else { + usleep(500 * 1000); + } + } + + if (verbose && !quit) { + /* printf [100 %] */ + print_job_progress(_("Block Pull"), 0, 1); + } + vshPrint(ctl, "\n%s", quit ? _("Pull aborted") : _("Pull complete")); + + ret = true; +cleanup: + if (dom) + virDomainFree(dom); + if (blocking) + sigaction(SIGINT, &old_sig_action, NULL); + return ret; } /* @@ -7641,7 +7751,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 5f3d9b1ae1..140d8e82ca 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -640,6 +640,7 @@ address of virtual interface (such as I or I) will accept the MAC address printed by this command. =item B I I [I] [I] +[I<--wait> [I<--verbose>] [I<--timeout> B] [I<--async]] Populate a disk from its backing image chain. By default, this command flattens the entire chain; but if I is specified, containing the @@ -647,8 +648,17 @@ 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. + +By default, this command returns as soon as possible, and data for +the entire disk is pulled in the background; the progress of the +operation can be checked with B. However, if I<--wait> is +specified, then this command will block until the operation completes, +or cancel the operation if the optional I in seconds elapses +or SIGINT is sent (usually with C). Using I<--verbose> along +with I<--wait> will produce periodic status updates. If job cancellation +is triggered, I<--async> will return control to the user as fast as +possible, otherwise the command may continue to block a little while +longer until the job is done cleaning up. I specifies fully-qualified path of the disk; it corresponds to a unique target name () or source file (