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(a)redhat.com>
---
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