Rather than further overloading 'blockpull', I decided to create a new virsh command to expose the new flags of virDomainBlockRebase. Blocking until the command completes naturally is pointless, since the block copy job is intended to run indefinitely. Instead, I made the command support three --wait modes: by default, it runs until mirroring is started; with --pivot, it pivots as soon as mirroring is started; and with --finish, it aborts (for a clean copy) as soon as mirroring is started. * tools/virsh.c (VSH_CMD_BLOCK_JOB_COPY): New mode. (blockJobImpl): Support new flags. (cmdBlockCopy): New command. (cmdBlockJob): Support new job info, new abort flag. * tools/virsh.pod (blockcopy, blockjob): Document the new command and flags. --- was 6/18 in v4 v5: add --wait flag, consistent capitalization tools/virsh.c | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++---- tools/virsh.pod | 49 ++++++++++++- 2 files changed, 243 insertions(+), 18 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 95ed7bc..0f79022 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -7517,7 +7517,8 @@ typedef enum { VSH_CMD_BLOCK_JOB_INFO = 1, VSH_CMD_BLOCK_JOB_SPEED = 2, VSH_CMD_BLOCK_JOB_PULL = 3, -} VSH_CMD_BLOCK_JOB_MODE; + VSH_CMD_BLOCK_JOB_COPY = 4, +} vshCmdBlockJobMode; static int blockJobImpl(vshControl *ctl, const vshCmd *cmd, @@ -7528,6 +7529,7 @@ blockJobImpl(vshControl *ctl, const vshCmd *cmd, const char *name, *path; unsigned long bandwidth = 0; int ret = -1; + const char *base = NULL; unsigned int flags = 0; if (!vshConnectionUsability(ctl, ctl->conn)) @@ -7544,22 +7546,39 @@ blockJobImpl(vshControl *ctl, const vshCmd *cmd, goto cleanup; } - if (mode == VSH_CMD_BLOCK_JOB_ABORT) { + switch ((vshCmdBlockJobMode) mode) { + case VSH_CMD_BLOCK_JOB_ABORT: if (vshCommandOptBool(cmd, "async")) flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_ASYNC; + if (vshCommandOptBool(cmd, "pivot")) + flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT; ret = virDomainBlockJobAbort(dom, path, flags); - } else if (mode == VSH_CMD_BLOCK_JOB_INFO) { + break; + case VSH_CMD_BLOCK_JOB_INFO: ret = virDomainGetBlockJobInfo(dom, path, info, 0); - } else if (mode == VSH_CMD_BLOCK_JOB_SPEED) { + break; + case VSH_CMD_BLOCK_JOB_SPEED: ret = virDomainBlockJobSetSpeed(dom, path, bandwidth, 0); - } else if (mode == VSH_CMD_BLOCK_JOB_PULL) { - const char *base = NULL; + break; + case VSH_CMD_BLOCK_JOB_PULL: if (vshCommandOptString(cmd, "base", &base) < 0) goto cleanup; if (base) ret = virDomainBlockRebase(dom, path, base, bandwidth, 0); else ret = virDomainBlockPull(dom, path, bandwidth, 0); + break; + case VSH_CMD_BLOCK_JOB_COPY: + flags |= VIR_DOMAIN_BLOCK_REBASE_COPY; + if (vshCommandOptBool(cmd, "shallow")) + flags |= VIR_DOMAIN_BLOCK_REBASE_SHALLOW; + if (vshCommandOptBool(cmd, "reuse-external")) + flags |= VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT; + if (vshCommandOptBool(cmd, "raw")) + flags |= VIR_DOMAIN_BLOCK_REBASE_COPY_RAW; + if (vshCommandOptString(cmd, "dest", &base) < 0) + goto cleanup; + ret = virDomainBlockRebase(dom, path, base, bandwidth, flags); } cleanup: @@ -7571,6 +7590,158 @@ cleanup: } /* + * "blockcopy" command + */ +static const vshCmdInfo info_block_copy[] = { + {"help", N_("Start a block copy operation.")}, + {"desc", N_("Populate a disk from its backing image.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_block_copy[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("fully-qualified path of disk")}, + {"dest", VSH_OT_DATA, VSH_OFLAG_REQ, N_("path of the copy to create")}, + {"bandwidth", VSH_OT_DATA, VSH_OFLAG_NONE, N_("bandwidth limit in MB/s")}, + {"shallow", VSH_OT_BOOL, 0, N_("make the copy share a backing chain")}, + {"reuse-external", VSH_OT_BOOL, 0, N_("reuse existing destination")}, + {"raw", VSH_OT_BOOL, 0, N_("use raw destination file")}, + {"wait", VSH_OT_BOOL, 0, N_("wait for job to reach mirroring phase")}, + {"verbose", VSH_OT_BOOL, 0, N_("with --wait, display the progress")}, + {"timeout", VSH_OT_INT, VSH_OFLAG_NONE, + N_("with --wait, abort if copy exceeds timeout (in seconds)")}, + {"pivot", VSH_OT_BOOL, 0, N_("with --wait, pivot when mirroring starts")}, + {"finish", VSH_OT_BOOL, 0, N_("with --wait, quit when mirroring starts")}, + {"async", VSH_OT_BOOL, 0, + N_("with --wait, don't wait for cancel to finish")}, + {NULL, 0, 0, NULL} +}; + +static bool +cmdBlockCopy(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + bool ret = false; + bool blocking = vshCommandOptBool(cmd, "wait"); + bool verbose = vshCommandOptBool(cmd, "verbose"); + bool pivot = vshCommandOptBool(cmd, "pivot"); + bool finish = vshCommandOptBool(cmd, "finish"); + 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 (pivot && finish) { + vshError(ctl, "%s", _("cannot mix --pivot and --finish")); + return false; + } + 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; + 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") || pivot || finish) { + vshError(ctl, "%s", _("blocking control options require --wait")); + return false; + } + + if (blockJobImpl(ctl, cmd, NULL, VSH_CMD_BLOCK_JOB_COPY, &dom) < 0) + goto cleanup; + + if (!blocking) { + vshPrint(ctl, "%s", _("Block Copy 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 (verbose) + print_job_progress(_("Block Copy"), info.end - info.cur, info.end); + if (info.cur == info.end) + break; + + 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 (pivot) { + abort_flags |= VIR_DOMAIN_BLOCK_JOB_ABORT_PIVOT; + if (virDomainBlockJobAbort(dom, path, abort_flags) < 0) { + vshError(ctl, _("failed to pivot job for disk %s"), path); + goto cleanup; + } + } else if (finish && virDomainBlockJobAbort(dom, path, abort_flags) < 0) { + vshError(ctl, _("failed to finish job for disk %s"), path); + goto cleanup; + } + vshPrint(ctl, "\n%s", + quit ? _("Copy aborted") : + pivot ? _("Successfully pivoted") : + finish ? _("Successfully copied") : + _("Now in mirroring phase")); + + ret = true; +cleanup: + if (dom) + virDomainFree(dom); + if (blocking) + sigaction(SIGINT, &old_sig_action, NULL); + return ret; +} + +/* * "blockpull" command */ static const vshCmdInfo info_block_pull[] = { @@ -7581,8 +7752,8 @@ static const vshCmdInfo info_block_pull[] = { static const vshCmdOptDef opts_block_pull[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, - {"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("Fully-qualified path of disk")}, - {"bandwidth", VSH_OT_DATA, VSH_OFLAG_NONE, N_("Bandwidth limit in MB/s")}, + {"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("fully-qualified path of disk")}, + {"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")}, @@ -7714,15 +7885,17 @@ static const vshCmdInfo info_block_job[] = { static const vshCmdOptDef opts_block_job[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, - {"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("Fully-qualified path of disk")}, + {"path", VSH_OT_DATA, VSH_OFLAG_REQ, N_("fully-qualified path of disk")}, {"abort", VSH_OT_BOOL, VSH_OFLAG_NONE, - N_("Abort the active job on the specified disk")}, + N_("abort the active job on the specified disk")}, {"async", VSH_OT_BOOL, VSH_OFLAG_NONE, N_("don't wait for --abort to complete")}, + {"pivot", VSH_OT_BOOL, VSH_OFLAG_NONE, + N_("conclude and pivot a copy job")}, {"info", VSH_OT_BOOL, VSH_OFLAG_NONE, - N_("Get active job information for the specified disk")}, + N_("get active job information for the specified disk")}, {"bandwidth", VSH_OT_DATA, VSH_OFLAG_NONE, - N_("Set the Bandwidth limit in MB/s")}, + N_("set the Bandwidth limit in MB/s")}, {NULL, 0, 0, NULL} }; @@ -7734,7 +7907,8 @@ cmdBlockJob(vshControl *ctl, const vshCmd *cmd) const char *type; int ret; bool abortMode = (vshCommandOptBool(cmd, "abort") || - vshCommandOptBool(cmd, "async")); + vshCommandOptBool(cmd, "async") || + vshCommandOptBool(cmd, "pivot")); bool infoMode = vshCommandOptBool(cmd, "info"); bool bandwidth = vshCommandOptBool(cmd, "bandwidth"); @@ -7758,10 +7932,17 @@ cmdBlockJob(vshControl *ctl, const vshCmd *cmd) if (ret == 0 || mode != VSH_CMD_BLOCK_JOB_INFO) return true; - if (info.type == VIR_DOMAIN_BLOCK_JOB_TYPE_PULL) + switch (info.type) { + case VIR_DOMAIN_BLOCK_JOB_TYPE_PULL: type = _("Block Pull"); - else + break; + case VIR_DOMAIN_BLOCK_JOB_TYPE_COPY: + type = _("Block Copy"); + break; + default: type = _("Unknown job"); + break; + } print_job_progress(type, info.end - info.cur, info.end); if (info.bandwidth != 0) @@ -17240,6 +17421,7 @@ static const vshCmdDef domManagementCmds[] = { {"autostart", cmdAutostart, opts_autostart, info_autostart, 0}, {"blkdeviotune", cmdBlkdeviotune, opts_blkdeviotune, info_blkdeviotune, 0}, {"blkiotune", cmdBlkiotune, opts_blkiotune, info_blkiotune, 0}, + {"blockcopy", cmdBlockCopy, opts_block_copy, info_block_copy, 0}, {"blockjob", cmdBlockJob, opts_block_job, info_block_job, 0}, {"blockpull", cmdBlockPull, opts_block_pull, info_block_pull, 0}, {"blockresize", cmdBlockResize, opts_block_resize, info_block_resize, 0}, diff --git a/tools/virsh.pod b/tools/virsh.pod index 140d8e8..23324b2 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -639,6 +639,47 @@ currently in use by a running domain. Other contexts that require a MAC address of virtual interface (such as I<detach-interface> or I<domif-setlink>) will accept the MAC address printed by this command. +=item B<blockcopy> I<domain> I<path> I<dest> [I<bandwidth>] [I<--shallow>] +[I<--reuse-external>] [I<--raw>] [I<--wait> [I<--verbose] +[{I<--pivot> | I<--finish>}] [I<--timeout> B<seconds>] [I<--async>]] + +Copy a disk backing image chain to I<dest>. By default, this command +flattens the entire chain; but if I<--shallow> is specified, the copy +shares the backing chain. + +If I<--reuse-external> is specified, then I<dest> must exist and have +contents identical to the resulting backing file (that is, it must +start with contents matching the backing file I<disk> if I<--shallow> +is used, otherwise it must start empty); this option is typically used +to set up a relative backing file name in the destination. + +The format of the destination is determined by the first match in the +following list: if I<--raw> is specified, it will be raw; if +I<--reuse-external> is specified, the existing destination is probed +for a format; and in all other cases, the destination format will +match the source format. + +By default, the copy job runs in the background, and consists of two +phases. Initially, the job must copy all data from the source, and +during this phase, the job can only be canceled to revert back to the +source disk, with no guarantees about the destination. After this phase +completes, both the source and the destination remain mirrored until a +call to B<blockjob> with the I<--abort> and I<--pivot> flags pivots over +to the copy, or a call without I<--pivot> leaves the destination as a +faithful copy of that point in time. However, if I<--wait> is specified, +then this command will block until the mirroring phase begins, 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. Using I<--pivot> or I<--finish> +along with I<--wait> will additionally end the job cleanly rather than +leaving things in the mirroring phase. 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<path> specifies fully-qualified path of the disk. +I<bandwidth> specifies copying bandwidth limit in Mbps. + =item B<blockpull> I<domain> I<path> [I<bandwidth>] [I<base>] [I<--wait> [I<--verbose>] [I<--timeout> B<seconds>] [I<--async]] @@ -700,12 +741,12 @@ Both I<--live> and I<--current> flags may be given, but I<--current> is exclusive. If no flag is specified, behavior is different depending on hypervisor. -=item B<blockjob> I<domain> I<path> { [I<--abort>] [I<--async>] | +=item B<blockjob> I<domain> I<path> { [I<--abort>] [I<--async>] [I<--pivot>] | [I<--info>] | [I<bandwidth>] } Manage active block operations. There are three modes: I<--info>, I<bandwidth>, and I<--abort>; I<--info> is default except that I<--async> -implies I<--abort>. +or I<--pivot> implies I<--abort>. I<path> specifies fully-qualified path of the disk; it corresponds to a unique target name (<target dev='name'/>) or source file (<source @@ -714,7 +755,9 @@ also B<domblklist> for listing these names). If I<--abort> is specified, the active job on the specified disk will be aborted. If I<--async> is also specified, this command will return -immediately, rather than waiting for the cancelation to complete. +immediately, rather than waiting for the cancelation to complete. If +I<--pivot> is specified, this requests that an active copy job +be pivoted over to the new copy. If I<--info> is specified, the active job information on the specified disk will be printed. I<bandwidth> can be used to set bandwidth limit for the active job. -- 1.7.7.6 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list