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