Any new API deserves a good virsh wrapper :) qemu-monitor-event [<domain>] [<event>] [--pretty] [--loop] [--timeout <number>] It helps that we already have an event loop running in a dedicated thread, so a blocking read() on a pipe-to-self is sufficient to handle all three means of ending the command: SIGINT, timeout, and oneshot behavior of an event received. For an example session (once subsequent qemu patches are applied): $ virsh -c qemu:///system qemu-monitor-event --event SHUTDOWN & $ virsh -c qemu:///system start f18-live Domain f18-live started $ virsh -c qemu:///system destroy f18-live Domain f18-live destroyed event SHUTDOWN at 1391212552.026544 for domain f18-live: (null) events received: 1 [1]+ Done virsh -c qemu:///system qemu-monitor-event --event SHUTDOWN $ * tools/virsh-domain.c (cmdQemuMonitorEvent): New command. * tools/virsh.pod (qemu-monitor-event): Document it. Signed-off-by: Eric Blake <eblake@xxxxxxxxxx> --- tools/virsh-domain.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 27 +++++-- 2 files changed, 216 insertions(+), 4 deletions(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index ce7aea9..a6dc80a 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -7933,6 +7933,193 @@ cleanup: } /* + * "qemu-monitor-event" command + */ +static int vshEventFd = -1; + +static void +vshEventInt(int sig ATTRIBUTE_UNUSED, + siginfo_t *siginfo ATTRIBUTE_UNUSED, + void *context ATTRIBUTE_UNUSED) +{ + if (vshEventFd >= 0) + ignore_value(safewrite(vshEventFd, "i", 1)); +} + +static void +vshEventTimeout(int timer ATTRIBUTE_UNUSED, + void *opaque ATTRIBUTE_UNUSED) +{ + if (vshEventFd >= 0) + ignore_value(safewrite(vshEventFd, "t", 1)); +} + +struct vshEventData { + vshControl *ctl; + bool loop; + bool pretty; + int count; +}; +typedef struct vshEventData vshEventData; + +static void +vshEventPrint(virConnectPtr conn ATTRIBUTE_UNUSED, virDomainPtr dom, + const char *event, long long seconds, unsigned int micros, + const char *details, void *opaque) +{ + vshEventData *data = opaque; + virJSONValuePtr pretty = NULL; + char *str = NULL; + + if (data->pretty && details) { + pretty = virJSONValueFromString(details); + if (pretty && (str = virJSONValueToString(pretty, true))) + details = str; + } + vshPrint(data->ctl, "event %s at %lld.%06u for domain %s: %s\n", + event, seconds, micros, virDomainGetName(dom), NULLSTR(details)); + data->count++; + if (!data->loop && vshEventFd >= 0) + ignore_value(safewrite(vshEventFd, "done", 1)); + + VIR_FREE(str); +} + +static const vshCmdInfo info_qemu_monitor_event[] = { + {.name = "help", + .data = N_("QEMU Monitor Events") + }, + {.name = "desc", + .data = N_("Listen for QEMU Monitor Events") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_qemu_monitor_event[] = { + {.name = "domain", + .type = VSH_OT_DATA, + .help = N_("filter by domain name, id or uuid") + }, + {.name = "event", + .type = VSH_OT_DATA, + .help = N_("filter by event name") + }, + {.name = "pretty", + .type = VSH_OT_BOOL, + .help = N_("pretty-print any JSON output") + }, + {.name = "loop", + .type = VSH_OT_BOOL, + .help = N_("loop until timeout or interrupt, rather than one-shot") + }, + {.name = "timeout", + .type = VSH_OT_INT, + .help = N_("timeout seconds") + }, + {.name = NULL} +}; + +static bool +cmdQemuMonitorEvent(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + bool ret = false; + unsigned int flags = 0; + int timerId = -1; + int eventId = -1; + int fd[2] = { -1, -1 }; + int timeout = 0; + const char *event = NULL; + struct sigaction sig_action; + struct sigaction old_sig_action; + int rv; + char buf = '\0'; + vshEventData data; + + data.ctl = ctl; + data.loop = vshCommandOptBool(cmd, "loop"); + data.pretty = vshCommandOptBool(cmd, "pretty"); + data.count = 0; + if ((rv = vshCommandOptInt(cmd, "timeout", &timeout)) < 0 || + (rv > 0 && timeout < 1)) { + vshError(ctl, "%s", _("invalid timeout")); + return false; + } else if (rv > 0) { + /* Ensure that we can multiply by 1000 without overflowing. */ + if (timeout > INT_MAX / 1000) { + vshError(ctl, "%s", _("timeout is too big")); + return false; + } + timeout *= 1000; + } + if (vshCommandOptString(cmd, "event", &event) < 0) + return false; + + if (vshCommandOptBool(cmd, "domain")) + dom = vshCommandOptDomain(ctl, cmd, NULL); + + /* virsh.c already set up an event loop; this command blocks until + * a callback informs us that we are done. */ + if (pipe2(fd, O_CLOEXEC) < 0) + goto cleanup; + vshEventFd = fd[1]; + + if ((eventId = virConnectDomainQemuMonitorEventRegister(ctl->conn, dom, + event, + vshEventPrint, + &data, NULL, + flags)) < 0) + goto cleanup; + + sig_action.sa_sigaction = vshEventInt; + sig_action.sa_flags = SA_SIGINFO; + sigemptyset(&sig_action.sa_mask); + sigaction(SIGINT, &sig_action, &old_sig_action); + + if (timeout) + timerId = virEventAddTimeout(timeout, vshEventTimeout, NULL, NULL); + + while ((rv = read(fd[0], &buf, 1)) < 0 && errno == EINTR); + if (rv != 1) { + char ebuf[1024]; + + if (!rv) + errno = EPIPE; + vshError(ctl, _("failed to determine loop exit status: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + goto cleanup; + } + + switch (buf) { + case 'i': + vshPrint(ctl, _("event loop interrupted\n")); + break; + case 't': + vshPrint(ctl, _("event loop timed out\n")); + break; + } + vshPrint(ctl, _("events received: %d\n"), data.count); + if (data.count) + ret = true; + +cleanup: + if (eventId >= 0) { + sigaction(SIGINT, &old_sig_action, NULL); + if (virConnectDomainQemuMonitorEventDeregister(ctl->conn, eventId) < 0) + ret = false; + } + vshEventFd = -1; + VIR_FORCE_CLOSE(fd[0]); + VIR_FORCE_CLOSE(fd[1]); + if (timerId >= 0) + virEventRemoveTimeout(timerId); + if (dom) + virDomainFree(dom); + + return ret; +} + +/* * "qemu-attach" command */ static const vshCmdInfo info_qemu_attach[] = { @@ -10903,6 +11090,12 @@ const vshCmdDef domManagementCmds[] = { .info = info_qemu_monitor_command, .flags = 0 }, + {.name = "qemu-monitor-event", + .handler = cmdQemuMonitorEvent, + .opts = opts_qemu_monitor_event, + .info = info_qemu_monitor_event, + .flags = 0 + }, {.name = "qemu-agent-command", .handler = cmdQemuAgentCommand, .opts = opts_qemu_agent_command, diff --git a/tools/virsh.pod b/tools/virsh.pod index d052b3a..22b1373 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -3272,12 +3272,15 @@ variables, and defaults to C<vi>. =back -=head1 QEMU-SPECIFIC COMMANDS +=head1 HYPERVISOR-SPECIFIC COMMANDS NOTE: Use of the following commands is B<strongly> discouraged. They can cause libvirt to become confused and do the wrong thing on subsequent -operations. Once you have used this command, please do not report -problems to the libvirt developers; the reports will be ignored. +operations. Once you have used these commands, please do not report +problems to the libvirt developers; the reports will be ignored. If +you find that these commands are the only way to accomplish something, +then it is better to request that the feature be added as a first-class +citizen in the regular libvirt library. =over 4 @@ -3316,7 +3319,8 @@ and the monitor uses QMP, then the output will be pretty-printed. If more than one argument is provided for I<command>, they are concatenated with a space in between before passing the single command to the monitor. -=item B<qemu-agent-command> I<domain> [I<--timeout> I<seconds> | I<--async> | I<--block>] I<command>... +=item B<qemu-agent-command> I<domain> [I<--timeout> I<seconds> | I<--async> | +I<--block>] I<command>... Send an arbitrary guest agent command I<command> to domain I<domain> through qemu agent. @@ -3326,6 +3330,21 @@ When I<--aysnc> is given, the command waits for timeout whether success or failed. And when I<--block> is given, the command waits forever with blocking timeout. +=item B<qemu-monitor-event> [I<domain>] [I<--event> I<event-name>] [I<--loop>] +[I<--timeout> I<seconds>] [I<--pretty>] + +Wait for arbitrary QEMU monitor events to occur, and print out the +details of events as they happen. The events can optionally be filtered +by I<domain> or I<event-name>. The 'query-events' QMP command can be +used via I<qemu-monitor-command> to learn what events are supported. + +By default, this command is one-shot, and returns success once an event +occurs; you can send SIGINT (usually via C<Ctrl-C>) to quit immediately. +If I<--timeout> is specified, the command gives up waiting for events +after I<seconds> have elapsed. With I<--loop>, the command prints all +events until a timeout or interrupt key. If I<--pretty> is specified, +any JSON event details are pretty-printed for better legibility. + =item B<lxc-enter-namespace> I<domain> -- /path/to/binary [arg1, [arg2, ...]] Enter the namespace of I<domain> and execute the command C</path/to/binary> -- 1.8.5.3 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list