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) @@ -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; + } + } + + 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 -- 1.7.7.6 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list