[libvirt] [PATCH 0/4] add 'virsh event' and friends

Inspired by my work on qemu monitor events; Dan correctly argued that if we're going to expose those through virsh, we also need to expose regular events. Patch 5/4 still coming, which adds handlers for the remaining 15 domain event types. Eric Blake (4): virsh: common code for parsing --seconds virsh: common code for waiting for an event virsh: add event command, for lifecycle events virsh: add net-event command tools/virsh-domain.c | 410 +++++++++++++++++++++++++++++++++++++++++++------- tools/virsh-network.c | 171 ++++++++++++++++++++- tools/virsh.c | 186 ++++++++++++++++++++++- tools/virsh.h | 17 ++- tools/virsh.pod | 30 ++++ 5 files changed, 752 insertions(+), 62 deletions(-) -- 1.8.5.3

Several virsh commands ask for a --timeout parameter in seconds, then use it to control interfaces that operate on millisecond limits; I also plan on adding a 'virsh event' command that also does this. Factor this into a common function. * tools/virsh.h (vshCommandOptTimeoutToMs): New prototype. * tools/virsh.c (vshCommandOptTimeoutToMs): New function. * tools/virsh-domain.c (cmdBlockCommit, cmdBlockCopy) (cmdBlockPull, cmdMigrate): Use it. (vshWatchJob): Adjust timeout scale. Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh-domain.c | 72 ++++++++++------------------------------------------ tools/virsh.c | 26 ++++++++++++++++++- tools/virsh.h | 4 ++- 3 files changed, 42 insertions(+), 60 deletions(-) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index c3db94c..2c7bf66 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -1,7 +1,7 @@ /* * virsh-domain.c: Commands to manage domain * - * Copyright (C) 2005, 2007-2013 Red Hat, Inc. + * Copyright (C) 2005, 2007-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -1629,21 +1629,10 @@ cmdBlockCommit(vshControl *ctl, const vshCmd *cmd) const char *path = NULL; bool quit = false; int abort_flags = 0; - int rv; if (blocking) { - if ((rv = vshCommandOptInt(cmd, "timeout", &timeout)) < 0 || - (rv > 0 && timeout < 1)) { - vshError(ctl, "%s", _("invalid timeout")); + if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0) 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 (vshCommandOptStringReq(ctl, cmd, "path", &path) < 0) return false; if (vshCommandOptBool(cmd, "async")) @@ -1819,25 +1808,14 @@ cmdBlockCopy(vshControl *ctl, const vshCmd *cmd) const char *path = NULL; bool quit = false; int abort_flags = 0; - int rv; if (blocking) { if (pivot && finish) { vshError(ctl, "%s", _("cannot mix --pivot and --finish")); return false; } - if ((rv = vshCommandOptInt(cmd, "timeout", &timeout)) < 0 || - (rv > 0 && timeout < 1)) { - vshError(ctl, "%s", _("invalid timeout")); + if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0) 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 (vshCommandOptStringReq(ctl, cmd, "path", &path) < 0) return false; if (vshCommandOptBool(cmd, "async")) @@ -2100,21 +2078,10 @@ cmdBlockPull(vshControl *ctl, const vshCmd *cmd) const char *path = NULL; bool quit = false; int abort_flags = 0; - int rv; if (blocking) { - if ((rv = vshCommandOptInt(cmd, "timeout", &timeout)) < 0 || - (rv > 0 && timeout < 1)) { - vshError(ctl, "%s", _("invalid timeout")); + if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0) 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 (vshCommandOptStringReq(ctl, cmd, "path", &path) < 0) return false; if (vshCommandOptBool(cmd, "async")) @@ -3606,7 +3573,7 @@ vshWatchJob(vshControl *ctl, virDomainPtr dom, bool verbose, int pipe_fd, - int timeout, + int timeout_ms, jobWatchTimeoutFunc timeout_func, void *opaque, const char *label) @@ -3676,14 +3643,14 @@ vshWatchJob(vshControl *ctl, } GETTIMEOFDAY(&curr); - if (timeout && (((int)(curr.tv_sec - start.tv_sec) * 1000 + - (int)(curr.tv_usec - start.tv_usec) / 1000) > - timeout * 1000)) { + if (timeout_ms && (((int)(curr.tv_sec - start.tv_sec) * 1000 + + (int)(curr.tv_usec - start.tv_usec) / 1000) > + timeout_ms)) { /* suspend the domain when migration timeouts. */ vshDebug(ctl, VSH_ERR_DEBUG, "%s timeout", label); if (timeout_func) (timeout_func)(ctl, dom, opaque); - timeout = 0; + timeout_ms = 0; } if (verbose || !jobStarted) { @@ -8844,7 +8811,6 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) int timeout = 0; bool live_flag = false; vshCtrlData data; - int rv; if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) return false; @@ -8854,22 +8820,12 @@ cmdMigrate(vshControl *ctl, const vshCmd *cmd) if (vshCommandOptBool(cmd, "live")) live_flag = true; - if ((rv = vshCommandOptInt(cmd, "timeout", &timeout)) < 0 || - (rv > 0 && timeout < 1)) { - vshError(ctl, "%s", _("migrate: Invalid timeout")); + if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0) { + goto cleanup; + } else if (timeout > 0 && !live_flag) { + vshError(ctl, "%s", + _("migrate: Unexpected timeout for offline migration")); goto cleanup; - } else if (rv > 0) { - if (! live_flag) { - vshError(ctl, "%s", - _("migrate: Unexpected timeout for offline migration")); - goto cleanup; - } - - /* Ensure that we can multiply by 1000 without overflowing. */ - if (timeout > INT_MAX / 1000) { - vshError(ctl, "%s", _("migrate: Timeout is too big")); - goto cleanup; - } } if (pipe(p) < 0) diff --git a/tools/virsh.c b/tools/virsh.c index 9d07d3e..944c037 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1,7 +1,7 @@ /* * virsh.c: a shell to exercise the libvirt API * - * Copyright (C) 2005, 2007-2013 Red Hat, Inc. + * Copyright (C) 2005, 2007-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -1721,6 +1721,30 @@ vshCmdHasOption(vshControl *ctl, const vshCmd *cmd, const char *optname) return found; } +/* Parse an optional --timeout parameter in seconds, but store the + * value of the timeout in milliseconds. Return -1 on error, 0 if + * no timeout was requested, and 1 if timeout was set. */ +int +vshCommandOptTimeoutToMs(vshControl *ctl, const vshCmd *cmd, int *timeout) +{ + int rv = vshCommandOptInt(cmd, "timeout", timeout); + + if (rv < 0 || (rv > 0 && *timeout < 1)) { + vshError(ctl, "%s", _("invalid timeout")); + return -1; + } + 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 -1; + } + *timeout *= 1000; + } + return rv; +} + + static bool vshConnectionUsability(vshControl *ctl, virConnectPtr conn) { diff --git a/tools/virsh.h b/tools/virsh.h index b843788..4ee2d72 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -1,7 +1,7 @@ /* * virsh.h: a shell to exercise the libvirt API * - * Copyright (C) 2005, 2007-2013 Red Hat, Inc. + * Copyright (C) 2005, 2007-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -302,6 +302,8 @@ const vshCmdOpt *vshCommandOptArgv(const vshCmd *cmd, const vshCmdOpt *opt); bool vshCmdHasOption(vshControl *ctl, const vshCmd *cmd, const char *optname); +int vshCommandOptTimeoutToMs(vshControl *ctl, const vshCmd *cmd, int *timeout); + /* Filter flags for various vshCommandOpt*By() functions */ typedef enum { VSH_BYID = (1 << 1), -- 1.8.5.3

On Fri, Feb 14, 2014 at 05:21:38PM -0700, Eric Blake wrote:
Several virsh commands ask for a --timeout parameter in seconds, then use it to control interfaces that operate on millisecond limits; I also plan on adding a 'virsh event' command that also does this. Factor this into a common function.
* tools/virsh.h (vshCommandOptTimeoutToMs): New prototype. * tools/virsh.c (vshCommandOptTimeoutToMs): New function. * tools/virsh-domain.c (cmdBlockCommit, cmdBlockCopy) (cmdBlockPull, cmdMigrate): Use it. (vshWatchJob): Adjust timeout scale.
Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh-domain.c | 72 ++++++++++------------------------------------------ tools/virsh.c | 26 ++++++++++++++++++- tools/virsh.h | 4 ++- 3 files changed, 42 insertions(+), 60 deletions(-)
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

I plan to add 'virsh event' to virsh-domain.c and 'virsh net-event' to virsh-network.c; but as they will share quite a bit of common boilerplate, it's better to set that up now in virsh.c. * tools/virsh.h (_vshControl): Add fields. (vshEventStart, vshEventWait, vshEventDone, vshEventCleanup): New prototypes. * tools/virsh.c (vshEventFd, vshEventOldAction, vshEventInt) (vshEventTimeout): New helper variables and functions. (vshEventStart, vshEventWait, vshEventDone, vshEventCleanup): Implement new functions. (vshInit, vshDeinit, main): Manage event timeout. Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- tools/virsh.h | 13 +++++ 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/tools/virsh.c b/tools/virsh.c index 944c037..2d4aaff 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -42,6 +42,7 @@ #include <sys/stat.h> #include <inttypes.h> #include <strings.h> +#include <signal.h> #include <libxml/parser.h> #include <libxml/tree.h> @@ -85,6 +86,11 @@ #include "virsh-snapshot.h" #include "virsh-volume.h" +/* Gnulib doesn't guarantee SA_SIGINFO support. */ +#ifndef SA_SIGINFO +# define SA_SIGINFO 0 +#endif + static char *progname; static const vshCmdGrp cmdGroups[]; @@ -2435,6 +2441,149 @@ vshEventLoop(void *opaque) /* + * Helpers for waiting for a libvirt event. + */ + +/* We want to use SIGINT to cancel a wait; but as signal handlers + * don't have an opaque argument, we have to use static storage. */ +static int vshEventFd = -1; +static struct sigaction vshEventOldAction; + + +/* Signal handler installed in vshEventStart, removed in vshEventCleanup. */ +static void +vshEventInt(int sig ATTRIBUTE_UNUSED, + siginfo_t *siginfo ATTRIBUTE_UNUSED, + void *context ATTRIBUTE_UNUSED) +{ + char reason = VSH_EVENT_INTERRUPT; + if (vshEventFd >= 0) + ignore_value(safewrite(vshEventFd, &reason, 1)); +} + + +/* Event loop handler used to limit length of waiting for any other event. */ +static void +vshEventTimeout(int timer ATTRIBUTE_UNUSED, + void *opaque) +{ + vshControl *ctl = opaque; + char reason = VSH_EVENT_TIMEOUT; + + if (ctl->eventPipe[1] >= 0) + ignore_value(safewrite(ctl->eventPipe[1], &reason, 1)); +} + + +/** + * vshEventStart: + * @ctl virsh command struct + * @timeout_ms max wait time in milliseconds, or 0 for indefinite + * + * Set up a wait for a libvirt event. The wait can be canceled by + * SIGINT or by calling vshEventDone() in your event handler. If + * @timeout_ms is positive, the wait will also end if the timeout + * expires. Call vshEventWait() to block the main thread (the event + * handler runs in the event loop thread). When done (including if + * there was an error registering for an event), use vshEventCleanup() + * to quit waiting. Returns 0 on success, -1 on failure. */ +int +vshEventStart(vshControl *ctl, int timeout_ms) +{ + struct sigaction action; + + assert(ctl->eventPipe[0] == -1 && ctl->eventPipe[1] == -1 && + vshEventFd == -1 && ctl->eventTimerId >= 0); + if (pipe2(ctl->eventPipe, O_CLOEXEC) < 0) { + char ebuf[1024]; + + vshError(ctl, _("failed to create pipe: %s"), + virStrerror(errno, ebuf, sizeof(ebuf))); + return -1; + } + vshEventFd = ctl->eventPipe[1]; + + action.sa_sigaction = vshEventInt; + action.sa_flags = SA_SIGINFO; + sigemptyset(&action.sa_mask); + sigaction(SIGINT, &action, &vshEventOldAction); + + if (timeout_ms) + virEventUpdateTimeout(ctl->eventTimerId, timeout_ms); + + return 0; +} + + +/** + * vshEventDone: + * @ctl virsh command struct + * + * Call this from an event callback to let the main thread quit + * blocking on further events. + */ +void +vshEventDone(vshControl *ctl) +{ + char reason = VSH_EVENT_DONE; + + if (ctl->eventPipe[1] >= 0) + ignore_value(safewrite(ctl->eventPipe[1], &reason, 1)); +} + + +/** + * vshEventWait: + * @ctl virsh command struct + * + * Call this in the main thread after calling vshEventStart() then + * registering for one or more events. This call will block until + * SIGINT, the timeout registered at the start, or until one of your + * event handlers calls vshEventDone(). Returns an enum VSH_EVENT_* + * stating how the wait concluded, or -1 on error. + */ +int +vshEventWait(vshControl *ctl) +{ + char buf; + int rv; + + assert(ctl->eventPipe[0] >= 0); + while ((rv = read(ctl->eventPipe[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))); + return -1; + } + return buf; +} + + +/** + * vshEventCleanup: + * @ctl virsh command struct + * + * Call at the end of any function that has used vshEventStart(), to + * tear down any remaining SIGINT or timeout handlers. + */ +void +vshEventCleanup(vshControl *ctl) +{ + if (vshEventFd >= 0) { + sigaction(SIGINT, &vshEventOldAction, NULL); + vshEventFd = -1; + } + VIR_FORCE_CLOSE(ctl->eventPipe[0]); + VIR_FORCE_CLOSE(ctl->eventPipe[1]); + virEventUpdateTimeout(ctl->eventTimerId, -1); +} + + +/* * Initialize debug settings. */ static void @@ -2490,6 +2639,10 @@ vshInit(vshControl *ctl) return false; ctl->eventLoopStarted = true; + if ((ctl->eventTimerId = virEventAddTimeout(-1, vshEventTimeout, ctl, + NULL)) < 0) + return false; + if (ctl->name) { vshReconnect(ctl); /* Connecting to a named connection must succeed, but we delay @@ -2933,6 +3086,9 @@ vshDeinit(vshControl *ctl) if (timer != -1) virEventRemoveTimeout(timer); + if (ctl->eventTimerId != -1) + virEventRemoveTimeout(ctl->eventTimerId); + ctl->eventLoopStarted = false; } @@ -3334,7 +3490,9 @@ main(int argc, char **argv) ctl->log_fd = -1; /* Initialize log file descriptor */ ctl->debug = VSH_DEBUG_DEFAULT; ctl->escapeChar = "^]"; /* Same default as telnet */ - + ctl->eventPipe[0] = -1; + ctl->eventPipe[1] = -1; + ctl->eventTimerId = -1; if (!setlocale(LC_ALL, "")) { perror("setlocale"); diff --git a/tools/virsh.h b/tools/virsh.h index 4ee2d72..62a1eed 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -242,6 +242,9 @@ struct _vshControl { virMutex lock; bool eventLoopStarted; bool quit; + int eventPipe[2]; /* Write-to-self pipe to end waiting for an + * event to occur */ + int eventTimerId; /* id of event loop timeout registration */ const char *escapeChar; /* String representation of console escape character */ @@ -369,6 +372,16 @@ int vshTTYRestore(vshControl *ctl); int vshTTYMakeRaw(vshControl *ctl, bool report_errors); bool vshTTYAvailable(vshControl *ctl); +/* waiting for events */ +enum { + VSH_EVENT_INTERRUPT, + VSH_EVENT_TIMEOUT, + VSH_EVENT_DONE, +}; +int vshEventStart(vshControl *ctl, int timeout_ms); +void vshEventDone(vshControl *ctl); +int vshEventWait(vshControl *ctl); +void vshEventCleanup(vshControl *ctl); /* allocation wrappers */ void *_vshMalloc(vshControl *ctl, size_t sz, const char *filename, int line); -- 1.8.5.3

On Fri, Feb 14, 2014 at 05:21:39PM -0700, Eric Blake wrote:
I plan to add 'virsh event' to virsh-domain.c and 'virsh net-event' to virsh-network.c; but as they will share quite a bit of common boilerplate, it's better to set that up now in virsh.c.
* tools/virsh.h (_vshControl): Add fields. (vshEventStart, vshEventWait, vshEventDone, vshEventCleanup): New prototypes. * tools/virsh.c (vshEventFd, vshEventOldAction, vshEventInt) (vshEventTimeout): New helper variables and functions. (vshEventStart, vshEventWait, vshEventDone, vshEventCleanup): Implement new functions. (vshInit, vshDeinit, main): Manage event timeout.
Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- tools/virsh.h | 13 +++++ 2 files changed, 172 insertions(+), 1 deletion(-)
ACK Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Add 'virsh event --list' and 'virsh event [dom] --event=name [--loop] [--timeout]'. Borrows somewhat from event-test.c, but defaults to a one-shot notification, and takes advantage of the event loop integration to allow Ctrl-C to interrupt the wait for an event. For now, this just does lifecycle events. * tools/virsh.pod (event): Document new command. * tools/virsh-domain.c (vshDomainEventToString) (vshDomainEventDetailToString, vshDomEventData) (vshEventLifecyclePrint, cmdEvent): New struct and functions. Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh-domain.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 15 +++ 2 files changed, 353 insertions(+) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 2c7bf66..3548131 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -10295,6 +10295,338 @@ cmdEdit(vshControl *ctl, const vshCmd *cmd) return ret; } + +/* + * "event" command + */ +static const char * +vshDomainEventToString(int event) +{ + const char *ret = _("unknown"); + switch ((virDomainEventType) event) { + case VIR_DOMAIN_EVENT_DEFINED: + ret = _("Defined"); + break; + case VIR_DOMAIN_EVENT_UNDEFINED: + ret = _("Undefined"); + break; + case VIR_DOMAIN_EVENT_STARTED: + ret = _("Started"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED: + ret = _("Suspended"); + break; + case VIR_DOMAIN_EVENT_RESUMED: + ret = _("Resumed"); + break; + case VIR_DOMAIN_EVENT_STOPPED: + ret = _("Stopped"); + break; + case VIR_DOMAIN_EVENT_SHUTDOWN: + ret = _("Shutdown"); + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED: + ret = _("PMSuspended"); + break; + case VIR_DOMAIN_EVENT_CRASHED: + ret = _("Crashed"); + break; + case VIR_DOMAIN_EVENT_LAST: + break; + } + return ret; +} + +static const char * +vshDomainEventDetailToString(int event, int detail) +{ + const char *ret = _("unknown"); + switch ((virDomainEventType) event) { + case VIR_DOMAIN_EVENT_DEFINED: + switch ((virDomainEventDefinedDetailType) detail) { + case VIR_DOMAIN_EVENT_DEFINED_ADDED: + ret = _("Added"); + break; + case VIR_DOMAIN_EVENT_DEFINED_UPDATED: + ret = _("Updated"); + break; + case VIR_DOMAIN_EVENT_DEFINED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_UNDEFINED: + switch ((virDomainEventUndefinedDetailType) detail) { + case VIR_DOMAIN_EVENT_UNDEFINED_REMOVED: + ret = _("Removed"); + break; + case VIR_DOMAIN_EVENT_UNDEFINED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_STARTED: + switch ((virDomainEventStartedDetailType) detail) { + case VIR_DOMAIN_EVENT_STARTED_BOOTED: + ret = _("Booted"); + break; + case VIR_DOMAIN_EVENT_STARTED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_STARTED_RESTORED: + ret = _("Restored"); + break; + case VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_STARTED_WAKEUP: + ret = _("Event wakeup"); + break; + case VIR_DOMAIN_EVENT_STARTED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_SUSPENDED: + switch ((virDomainEventSuspendedDetailType) detail) { + case VIR_DOMAIN_EVENT_SUSPENDED_PAUSED: + ret = _("Paused"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_IOERROR: + ret = _("I/O Error"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG: + ret = _("Watchdog"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_RESTORED: + ret = _("Restored"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR: + ret = _("API error"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_RESUMED: + switch ((virDomainEventResumedDetailType) detail) { + case VIR_DOMAIN_EVENT_RESUMED_UNPAUSED: + ret = _("Unpaused"); + break; + case VIR_DOMAIN_EVENT_RESUMED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_RESUMED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_STOPPED: + switch ((virDomainEventStoppedDetailType) detail) { + case VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN: + ret = _("Shutdown"); + break; + case VIR_DOMAIN_EVENT_STOPPED_DESTROYED: + ret = _("Destroyed"); + break; + case VIR_DOMAIN_EVENT_STOPPED_CRASHED: + ret = _("Crashed"); + break; + case VIR_DOMAIN_EVENT_STOPPED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_STOPPED_SAVED: + ret = _("Saved"); + break; + case VIR_DOMAIN_EVENT_STOPPED_FAILED: + ret = _("Failed"); + break; + case VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_STOPPED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_SHUTDOWN: + switch ((virDomainEventShutdownDetailType) detail) { + case VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED: + ret = _("Finished"); + break; + case VIR_DOMAIN_EVENT_SHUTDOWN_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED: + switch ((virDomainEventPMSuspendedDetailType) detail) { + case VIR_DOMAIN_EVENT_PMSUSPENDED_MEMORY: + ret = _("Memory"); + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED_DISK: + ret = _("Disk"); + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_CRASHED: + switch ((virDomainEventCrashedDetailType) detail) { + case VIR_DOMAIN_EVENT_CRASHED_PANICKED: + ret = _("Panicked"); + break; + case VIR_DOMAIN_EVENT_CRASHED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_LAST: + break; + } + return ret; +} + +struct vshDomEventData { + vshControl *ctl; + bool loop; + int count; +}; +typedef struct vshDomEventData vshDomEventData; + +/* FIXME: Support all callbacks, not just lifecycle */ +VIR_ENUM_DECL(vshDomainEvent) +VIR_ENUM_IMPL(vshDomainEvent, + 1 /* VIR_DOMAIN_EVENT_LAST */, + "lifecycle") + +static void +vshEventLifecyclePrint(virConnectPtr conn ATTRIBUTE_UNUSED, + virDomainPtr dom, + int event, + int detail, + void *opaque) +{ + vshDomEventData *data = opaque; + + if (!data->loop && data->count) + return; + vshPrint(data->ctl, _("event 'lifecycle' for domain %s: %s %s\n"), + virDomainGetName(dom), vshDomainEventToString(event), + vshDomainEventDetailToString(event, detail)); + data->count++; + if (!data->loop) + vshEventDone(data->ctl); +} + +static const vshCmdInfo info_event[] = { + {.name = "event", + .data = N_("Domain Events") + }, + {.name = "desc", + .data = N_("List event types, or wait for domain events to occur") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_event[] = { + {.name = "domain", + .type = VSH_OT_DATA, + .help = N_("filter by domain name, id, or uuid") + }, + {.name = "event", + .type = VSH_OT_DATA, + .help = N_("which event type to wait for") + }, + {.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 = "list", + .type = VSH_OT_BOOL, + .help = N_("list valid event types") + }, + {.name = NULL} +}; + +static bool +cmdEvent(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + bool ret = false; + int eventId = -1; + int timeout = 0; + vshDomEventData data; + const char *eventName = NULL; + int event; + + if (vshCommandOptBool(cmd, "list")) { + size_t i; + + for (i = 0; i < 1 /* VIR_DOMAIN_EVENT_ID_LAST */; i++) + vshPrint(ctl, "%s\n", vshDomainEventTypeToString(i)); + return true; + } + + if (vshCommandOptString(cmd, "event", &eventName) < 0) + return false; + if (!eventName) { + vshError(ctl, "%s", _("either --list or event type is required")); + return false; + } + if ((event = vshDomainEventTypeFromString(eventName) < 0)) { + vshError(ctl, _("unknown event type %s"), eventName); + return false; + } + + data.ctl = ctl; + data.loop = vshCommandOptBool(cmd, "loop"); + data.count = 0; + if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0) + return false; + + if (vshCommandOptBool(cmd, "domain")) + dom = vshCommandOptDomain(ctl, cmd, NULL); + if (vshEventStart(ctl, timeout) < 0) + goto cleanup; + + if ((eventId = virConnectDomainEventRegisterAny(ctl->conn, dom, event, + VIR_DOMAIN_EVENT_CALLBACK(vshEventLifecyclePrint), + &data, NULL)) < 0) + goto cleanup; + switch (vshEventWait(ctl)) { + case VSH_EVENT_INTERRUPT: + vshPrint(ctl, "%s", _("event loop interrupted\n")); + break; + case VSH_EVENT_TIMEOUT: + vshPrint(ctl, "%s", _("event loop timed out\n")); + break; + case VSH_EVENT_DONE: + break; + default: + goto cleanup; + } + vshPrint(ctl, _("events received: %d\n"), data.count); + if (data.count) + ret = true; + +cleanup: + vshEventCleanup(ctl); + if (eventId >= 0 && + virConnectDomainEventDeregisterAny(ctl->conn, eventId) < 0) + ret = false; + if (dom) + virDomainFree(dom); + return ret; +} + + /* * "change-media" command */ @@ -10751,6 +11083,12 @@ const vshCmdDef domManagementCmds[] = { .info = info_edit, .flags = 0 }, + {.name = "event", + .handler = cmdEvent, + .opts = opts_event, + .info = info_event, + .flags = 0 + }, {.name = "inject-nmi", .handler = cmdInjectNMI, .opts = opts_inject_nmi, diff --git a/tools/virsh.pod b/tools/virsh.pod index f221475..0878778 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -1043,6 +1043,21 @@ except that it does some error checking. The editor used can be supplied by the C<$VISUAL> or C<$EDITOR> environment variables, and defaults to C<vi>. +=item B<event> {[I<domain>] I<event> [I<--loop>] [I<--timeout> I<seconds>] | +I<--list>} + +Wait for a class of domain events to occur, and print appropriate details +of events as they happen. The events can optionally be filtered by +I<domain>. Using I<--list> as the only argument will provide a list +of possible I<event> values known by this client, although the connection +might not allow registering for all these events. + +By default, tihs 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. + =item B<managedsave> I<domain> [I<--bypass-cache>] [{I<--running> | I<--paused>}] [I<--verbose>] -- 1.8.5.3

On Fri, Feb 14, 2014 at 05:21:40PM -0700, Eric Blake wrote:
Add 'virsh event --list' and 'virsh event [dom] --event=name [--loop] [--timeout]'. Borrows somewhat from event-test.c, but defaults to a one-shot notification, and takes advantage of the event loop integration to allow Ctrl-C to interrupt the wait for an event. For now, this just does lifecycle events.
* tools/virsh.pod (event): Document new command. * tools/virsh-domain.c (vshDomainEventToString) (vshDomainEventDetailToString, vshDomEventData) (vshEventLifecyclePrint, cmdEvent): New struct and functions.
ACK
Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh-domain.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 15 +++ 2 files changed, 353 insertions(+)
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 2c7bf66..3548131 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -10295,6 +10295,338 @@ cmdEdit(vshControl *ctl, const vshCmd *cmd) return ret; }
+ +/* + * "event" command + */ +static const char * +vshDomainEventToString(int event) +{ + const char *ret = _("unknown"); + switch ((virDomainEventType) event) { + case VIR_DOMAIN_EVENT_DEFINED: + ret = _("Defined"); + break; + case VIR_DOMAIN_EVENT_UNDEFINED: + ret = _("Undefined"); + break; + case VIR_DOMAIN_EVENT_STARTED: + ret = _("Started"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED: + ret = _("Suspended"); + break; + case VIR_DOMAIN_EVENT_RESUMED: + ret = _("Resumed"); + break; + case VIR_DOMAIN_EVENT_STOPPED: + ret = _("Stopped"); + break; + case VIR_DOMAIN_EVENT_SHUTDOWN: + ret = _("Shutdown"); + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED: + ret = _("PMSuspended"); + break; + case VIR_DOMAIN_EVENT_CRASHED: + ret = _("Crashed"); + break; + case VIR_DOMAIN_EVENT_LAST: + break; + } + return ret; +}
How about using VIR_ENUM ? We avoided it in the event-test.c file since we wanted it to be example code people can compile outside libvirt. Using enums would be fine for virsh though i think
+ +static const char * +vshDomainEventDetailToString(int event, int detail) +{ + const char *ret = _("unknown"); + switch ((virDomainEventType) event) { + case VIR_DOMAIN_EVENT_DEFINED: + switch ((virDomainEventDefinedDetailType) detail) { + case VIR_DOMAIN_EVENT_DEFINED_ADDED: + ret = _("Added"); + break; + case VIR_DOMAIN_EVENT_DEFINED_UPDATED: + ret = _("Updated"); + break; + case VIR_DOMAIN_EVENT_DEFINED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_UNDEFINED: + switch ((virDomainEventUndefinedDetailType) detail) { + case VIR_DOMAIN_EVENT_UNDEFINED_REMOVED: + ret = _("Removed"); + break; + case VIR_DOMAIN_EVENT_UNDEFINED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_STARTED: + switch ((virDomainEventStartedDetailType) detail) { + case VIR_DOMAIN_EVENT_STARTED_BOOTED: + ret = _("Booted"); + break; + case VIR_DOMAIN_EVENT_STARTED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_STARTED_RESTORED: + ret = _("Restored"); + break; + case VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_STARTED_WAKEUP: + ret = _("Event wakeup"); + break; + case VIR_DOMAIN_EVENT_STARTED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_SUSPENDED: + switch ((virDomainEventSuspendedDetailType) detail) { + case VIR_DOMAIN_EVENT_SUSPENDED_PAUSED: + ret = _("Paused"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_IOERROR: + ret = _("I/O Error"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG: + ret = _("Watchdog"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_RESTORED: + ret = _("Restored"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_API_ERROR: + ret = _("API error"); + break; + case VIR_DOMAIN_EVENT_SUSPENDED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_RESUMED: + switch ((virDomainEventResumedDetailType) detail) { + case VIR_DOMAIN_EVENT_RESUMED_UNPAUSED: + ret = _("Unpaused"); + break; + case VIR_DOMAIN_EVENT_RESUMED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_RESUMED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_RESUMED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_STOPPED: + switch ((virDomainEventStoppedDetailType) detail) { + case VIR_DOMAIN_EVENT_STOPPED_SHUTDOWN: + ret = _("Shutdown"); + break; + case VIR_DOMAIN_EVENT_STOPPED_DESTROYED: + ret = _("Destroyed"); + break; + case VIR_DOMAIN_EVENT_STOPPED_CRASHED: + ret = _("Crashed"); + break; + case VIR_DOMAIN_EVENT_STOPPED_MIGRATED: + ret = _("Migrated"); + break; + case VIR_DOMAIN_EVENT_STOPPED_SAVED: + ret = _("Saved"); + break; + case VIR_DOMAIN_EVENT_STOPPED_FAILED: + ret = _("Failed"); + break; + case VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT: + ret = _("Snapshot"); + break; + case VIR_DOMAIN_EVENT_STOPPED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_SHUTDOWN: + switch ((virDomainEventShutdownDetailType) detail) { + case VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED: + ret = _("Finished"); + break; + case VIR_DOMAIN_EVENT_SHUTDOWN_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED: + switch ((virDomainEventPMSuspendedDetailType) detail) { + case VIR_DOMAIN_EVENT_PMSUSPENDED_MEMORY: + ret = _("Memory"); + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED_DISK: + ret = _("Disk"); + break; + case VIR_DOMAIN_EVENT_PMSUSPENDED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_CRASHED: + switch ((virDomainEventCrashedDetailType) detail) { + case VIR_DOMAIN_EVENT_CRASHED_PANICKED: + ret = _("Panicked"); + break; + case VIR_DOMAIN_EVENT_CRASHED_LAST: + break; + } + break; + case VIR_DOMAIN_EVENT_LAST: + break; + }
And VIR_ENUM for each of the detail sets ?
+ return ret; +} +
diff --git a/tools/virsh.pod b/tools/virsh.pod index f221475..0878778 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod +By default, tihs command is one-shot, and returns success once an event
s/tihs/this/ Rare case of me spotting a typo in your code, instead of the reverse :-) Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 02/20/2014 10:33 AM, Daniel P. Berrange wrote:
On Fri, Feb 14, 2014 at 05:21:40PM -0700, Eric Blake wrote:
Add 'virsh event --list' and 'virsh event [dom] --event=name [--loop] [--timeout]'. Borrows somewhat from event-test.c, but defaults to a one-shot notification, and takes advantage of the event loop integration to allow Ctrl-C to interrupt the wait for an event. For now, this just does lifecycle events.
* tools/virsh.pod (event): Document new command. * tools/virsh-domain.c (vshDomainEventToString) (vshDomainEventDetailToString, vshDomEventData) (vshEventLifecyclePrint, cmdEvent): New struct and functions.
ACK
+ case VIR_DOMAIN_EVENT_DEFINED: + ret = _("Defined"); + break;
How about using VIR_ENUM ?
We avoided it in the event-test.c file since we wanted it to be example code people can compile outside libvirt. Using enums would be fine for virsh though i think
VIR_ENUM doesn't allow _("") translation. This output is human legible, so we want it to appear in the user's locale (see also vshDomainVcpuStateToString() and friends).
+++ b/tools/virsh.pod +By default, tihs command is one-shot, and returns success once an event
s/tihs/this/
Rare case of me spotting a typo in your code, instead of the reverse :-)
Hey - I have to inject errors somewhere, to make the review worthwhile, right? :) Fixed, and will push the series shortly as well as crank out patch 5/4 for the remaining events. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

On Thu, Feb 20, 2014 at 11:06:55AM -0700, Eric Blake wrote:
On 02/20/2014 10:33 AM, Daniel P. Berrange wrote:
On Fri, Feb 14, 2014 at 05:21:40PM -0700, Eric Blake wrote:
Add 'virsh event --list' and 'virsh event [dom] --event=name [--loop] [--timeout]'. Borrows somewhat from event-test.c, but defaults to a one-shot notification, and takes advantage of the event loop integration to allow Ctrl-C to interrupt the wait for an event. For now, this just does lifecycle events.
* tools/virsh.pod (event): Document new command. * tools/virsh-domain.c (vshDomainEventToString) (vshDomainEventDetailToString, vshDomEventData) (vshEventLifecyclePrint, cmdEvent): New struct and functions.
ACK
+ case VIR_DOMAIN_EVENT_DEFINED: + ret = _("Defined"); + break;
How about using VIR_ENUM ?
We avoided it in the event-test.c file since we wanted it to be example code people can compile outside libvirt. Using enums would be fine for virsh though i think
VIR_ENUM doesn't allow _("") translation. This output is human legible, so we want it to appear in the user's locale (see also vshDomainVcpuStateToString() and friends).
This is what the N_("") macro is for though isn't it. Marks the string for translation, but actual gettext call is done at time of use instead. Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 02/20/2014 11:14 AM, Daniel P. Berrange wrote:
+ case VIR_DOMAIN_EVENT_DEFINED: + ret = _("Defined"); + break;
How about using VIR_ENUM ?
We avoided it in the event-test.c file since we wanted it to be example code people can compile outside libvirt. Using enums would be fine for virsh though i think
VIR_ENUM doesn't allow _("") translation. This output is human legible, so we want it to appear in the user's locale (see also vshDomainVcpuStateToString() and friends).
This is what the N_("") macro is for though isn't it. Marks the string for translation, but actual gettext call is done at time of use instead.
That should work - I'll do it as a separate patch, since there's a lot more places that can be cleaned up. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

Add 'virsh net-event --list' and 'virsh net-event [net] --event=name [--loop] [--timeout]'. Very similar to 'virsh event'. * tools/virsh.pod (net-event): Document new command. * tools/virsh-network.c (vshNetworkEventToString, vshNetEventData) (vshEventLifecyclePrint, cmdNetworkEvent): New struct and functions. Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh-network.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++- tools/virsh.pod | 15 +++++ 2 files changed, 185 insertions(+), 1 deletion(-) diff --git a/tools/virsh-network.c b/tools/virsh-network.c index 44a676b..4377391 100644 --- a/tools/virsh-network.c +++ b/tools/virsh-network.c @@ -1,7 +1,7 @@ /* * virsh-network.c: Commands to manage network * - * Copyright (C) 2005, 2007-2013 Red Hat, Inc. + * Copyright (C) 2005, 2007-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -1130,6 +1130,169 @@ cmdNetworkEdit(vshControl *ctl, const vshCmd *cmd) return ret; } + +/* + * "net-event" command + */ +static const char * +vshNetworkEventToString(int event) +{ + const char *ret = _("unknown"); + switch ((virNetworkEventLifecycleType) event) { + case VIR_NETWORK_EVENT_DEFINED: + ret = _("Defined"); + break; + case VIR_NETWORK_EVENT_UNDEFINED: + ret = _("Undefined"); + break; + case VIR_NETWORK_EVENT_STARTED: + ret = _("Started"); + break; + case VIR_NETWORK_EVENT_STOPPED: + ret = _("Stopped"); + break; + case VIR_NETWORK_EVENT_LAST: + break; + } + return ret; +} + +struct vshNetEventData { + vshControl *ctl; + bool loop; + int count; +}; +typedef struct vshNetEventData vshNetEventData; + +VIR_ENUM_DECL(vshNetworkEvent) +VIR_ENUM_IMPL(vshNetworkEvent, + VIR_NETWORK_EVENT_ID_LAST, + "lifecycle") + +static void +vshEventLifecyclePrint(virConnectPtr conn ATTRIBUTE_UNUSED, + virNetworkPtr net, + int event, + int detail ATTRIBUTE_UNUSED, + void *opaque) +{ + vshNetEventData *data = opaque; + + if (!data->loop && data->count) + return; + vshPrint(data->ctl, _("event 'lifecycle' for network %s: %s\n"), + virNetworkGetName(net), vshNetworkEventToString(event)); + data->count++; + if (!data->loop) + vshEventDone(data->ctl); +} + +static const vshCmdInfo info_network_event[] = { + {.name = "net-event", + .data = N_("Network Events") + }, + {.name = "desc", + .data = N_("List event types, or wait for network events to occur") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_network_event[] = { + {.name = "network", + .type = VSH_OT_DATA, + .help = N_("filter by network name or uuid") + }, + {.name = "event", + .type = VSH_OT_DATA, + .help = N_("which event type to wait for") + }, + {.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 = "list", + .type = VSH_OT_BOOL, + .help = N_("list valid event types") + }, + {.name = NULL} +}; + +static bool +cmdNetworkEvent(vshControl *ctl, const vshCmd *cmd) +{ + virNetworkPtr net = NULL; + bool ret = false; + int eventId = -1; + int timeout = 0; + vshNetEventData data; + const char *eventName = NULL; + int event; + + if (vshCommandOptBool(cmd, "list")) { + size_t i; + + for (i = 0; i < VIR_NETWORK_EVENT_ID_LAST; i++) + vshPrint(ctl, "%s\n", vshNetworkEventTypeToString(i)); + return true; + } + + if (vshCommandOptString(cmd, "event", &eventName) < 0) + return false; + if (!eventName) { + vshError(ctl, "%s", _("either --list or event type is required")); + return false; + } + if ((event = vshNetworkEventTypeFromString(eventName) < 0)) { + vshError(ctl, _("unknown event type %s"), eventName); + return false; + } + + data.ctl = ctl; + data.loop = vshCommandOptBool(cmd, "loop"); + data.count = 0; + if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0) + return false; + + if (vshCommandOptBool(cmd, "network")) + net = vshCommandOptNetwork(ctl, cmd, NULL); + if (vshEventStart(ctl, timeout) < 0) + goto cleanup; + + if ((eventId = virConnectNetworkEventRegisterAny(ctl->conn, net, event, + VIR_NETWORK_EVENT_CALLBACK(vshEventLifecyclePrint), + &data, NULL)) < 0) + goto cleanup; + switch (vshEventWait(ctl)) { + case VSH_EVENT_INTERRUPT: + vshPrint(ctl, "%s", _("event loop interrupted\n")); + break; + case VSH_EVENT_TIMEOUT: + vshPrint(ctl, "%s", _("event loop timed out\n")); + break; + case VSH_EVENT_DONE: + break; + default: + goto cleanup; + } + vshPrint(ctl, _("events received: %d\n"), data.count); + if (data.count) + ret = true; + +cleanup: + vshEventCleanup(ctl); + if (eventId >= 0 && + virConnectNetworkEventDeregisterAny(ctl->conn, eventId) < 0) + ret = false; + if (net) + virNetworkFree(net); + return ret; +} + + const vshCmdDef networkCmds[] = { {.name = "net-autostart", .handler = cmdNetworkAutostart, @@ -1167,6 +1330,12 @@ const vshCmdDef networkCmds[] = { .info = info_network_edit, .flags = 0 }, + {.name = "net-event", + .handler = cmdNetworkEvent, + .opts = opts_network_event, + .info = info_network_event, + .flags = 0 + }, {.name = "net-info", .handler = cmdNetworkInfo, .opts = opts_network_info, diff --git a/tools/virsh.pod b/tools/virsh.pod index 0878778..7883231 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -2320,6 +2320,21 @@ except that it does some error checking. The editor used can be supplied by the C<$VISUAL> or C<$EDITOR> environment variables, and defaults to C<vi>. +=item B<net-event> {[I<network>] I<event> [I<--loop>] [I<--timeout> +I<seconds>] | I<--list>} + +Wait for a class of network events to occur, and print appropriate details +of events as they happen. The events can optionally be filtered by +I<network>. Using I<--list> as the only argument will provide a list +of possible I<event> values known by this client, although the connection +might not allow registering for all these events. + +By default, tihs 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. + =item B<net-info> I<network> Returns basic information about the I<network> object. -- 1.8.5.3

On Fri, Feb 14, 2014 at 05:21:41PM -0700, Eric Blake wrote:
Add 'virsh net-event --list' and 'virsh net-event [net] --event=name [--loop] [--timeout]'. Very similar to 'virsh event'.
* tools/virsh.pod (net-event): Document new command. * tools/virsh-network.c (vshNetworkEventToString, vshNetEventData) (vshEventLifecyclePrint, cmdNetworkEvent): New struct and functions.
Signed-off-by: Eric Blake <eblake@redhat.com> --- tools/virsh-network.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++- tools/virsh.pod | 15 +++++ 2 files changed, 185 insertions(+), 1 deletion(-)
+ +/* + * "net-event" command + */ +static const char * +vshNetworkEventToString(int event) +{ + const char *ret = _("unknown"); + switch ((virNetworkEventLifecycleType) event) { + case VIR_NETWORK_EVENT_DEFINED: + ret = _("Defined"); + break; + case VIR_NETWORK_EVENT_UNDEFINED: + ret = _("Undefined"); + break; + case VIR_NETWORK_EVENT_STARTED: + ret = _("Started"); + break; + case VIR_NETWORK_EVENT_STOPPED: + ret = _("Stopped"); + break; + case VIR_NETWORK_EVENT_LAST: + break; + } + return ret; +}
Another enum use case I think
+By default, tihs command is one-shot, and returns success once an event
Yay, copy+paste mistakes :-) Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 02/14/2014 05:21 PM, Eric Blake wrote:
Inspired by my work on qemu monitor events; Dan correctly argued that if we're going to expose those through virsh, we also need to expose regular events. Patch 5/4 still coming, which adds handlers for the remaining 15 domain event types.
Ping - I'd like to know if there's a chance of this making 1.2.2, and if so, if I should spend time writing 5/4 today.
Eric Blake (4): virsh: common code for parsing --seconds virsh: common code for waiting for an event virsh: add event command, for lifecycle events virsh: add net-event command
tools/virsh-domain.c | 410 +++++++++++++++++++++++++++++++++++++++++++------- tools/virsh-network.c | 171 ++++++++++++++++++++- tools/virsh.c | 186 ++++++++++++++++++++++- tools/virsh.h | 17 ++- tools/virsh.pod | 30 ++++ 5 files changed, 752 insertions(+), 62 deletions(-)
-- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
participants (2)
-
Daniel P. Berrange
-
Eric Blake