From: Peter Krempa <pkrempa(a)redhat.com>
The new command is meant as syntax sugar for event handling which blocks
virsh until the requested state condition is reached.
The initial implementation adds a condition 'domain-inactive' returning
if the domain is/becomes inactive for whatever reason.
This command is useful for simple scripts e.g. for debugging libvirt
when it allows responding to target state in shell without the need to
fuss too much with polling or writing handlers around 'virsh event'.
Signed-off-by: Peter Krempa <pkrempa(a)redhat.com>
---
docs/manpages/virsh.rst | 25 ++++
tools/virsh-domain-event.c | 245 +++++++++++++++++++++++++++++++++++++
2 files changed, 270 insertions(+)
diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst
index 895a905b08..1515f84063 100644
--- a/docs/manpages/virsh.rst
+++ b/docs/manpages/virsh.rst
@@ -3062,6 +3062,31 @@ When *--timestamp* is used, a human-readable timestamp will be
printed
before the event.
+await
+-----
+
+**Syntax:**
+
+::
+
+ await <domain> --condition <string> [--timeout seconds]
+
+Wait until the *--condition* for <domain> is satisfied. Uses events for
+efficient state updates.
+
+Supported conditions:
+
+ *domain-inactive*
+
+ domain is or becomes inactive
+
+If *--timeout* is specified, the command gives up waiting for the condition to
+satisfy after *seconds* have elapsed. If SIGINT is delivered to virsh
+(usually via ``Ctrl-C``) the wait is given up immediately. In non-interactive
+mode virsh will return '2' if either of those cases instead of '1' which
means
+an error happened.
+
+
get-user-sshkeys
----------------
diff --git a/tools/virsh-domain-event.c b/tools/virsh-domain-event.c
index 9aa21b2e78..2cd52933c7 100644
--- a/tools/virsh-domain-event.c
+++ b/tools/virsh-domain-event.c
@@ -1064,6 +1064,245 @@ cmdEvent(vshControl *ctl, const vshCmd *cmd)
}
+/* virsh event-await */
+
+struct virshDomEventAwaitConditionData;
+
+struct virshDomainEventAwaitCondition {
+ const char *name;
+ int event;
+ int (*handler)(struct virshDomEventAwaitConditionData *data);
+};
+
+struct virshDomEventAwaitConditionData {
+ vshControl *ctl;
+ virshDomain *dom;
+ const struct virshDomainEventAwaitCondition *cond;
+
+ virMutex *m; /* synchronization to ensure clean shutdown */
+ virCond *c;
+ bool done;
+};
+
+
+static void
+virshDomEventAwaitConditionDataUnregistered(struct virshDomEventAwaitConditionData
*data)
+{
+ g_auto(virLockGuard) name = virLockGuardLock(data->m);
+ /* signal that the handler was unregistered */
+ data->done = true;
+ virCondSignal(data->c);
+}
+
+
+static void
+virshDomainEventAwaitCallbackLifecycle(virConnectPtr conn G_GNUC_UNUSED,
+ virDomainPtr dom G_GNUC_UNUSED,
+ int event G_GNUC_UNUSED,
+ int detail G_GNUC_UNUSED,
+ void *opaque)
+{
+ struct virshDomEventAwaitConditionData *data = opaque;
+
+ if (data->cond->handler(data) < 1)
+ vshEventDone(data->ctl);
+}
+
+
+struct virshDomainEventAwaitCallbackTuple {
+ int event;
+ virConnectDomainEventGenericCallback eventCB;
+};
+
+
+static const struct virshDomainEventAwaitCallbackTuple callbacks[] =
+{
+ { .event = VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+ .eventCB = VIR_DOMAIN_EVENT_CALLBACK(virshDomainEventAwaitCallbackLifecycle),
+ },
+};
+
+
+static int
+virshDomainEventAwaitConditionDomainInactive(struct virshDomEventAwaitConditionData
*data)
+{
+ int state = -1;
+
+ if (virDomainGetState(data->dom, &state, NULL, 0) < 0) {
+ vshError(data->ctl, "%s", _("failed to update domain
state"));
+ return -1;
+ }
+
+ switch ((virDomainState) state) {
+ case VIR_DOMAIN_SHUTOFF:
+ case VIR_DOMAIN_CRASHED:
+ return 0;
+
+ case VIR_DOMAIN_NOSTATE:
+ case VIR_DOMAIN_RUNNING:
+ case VIR_DOMAIN_BLOCKED:
+ case VIR_DOMAIN_PAUSED:
+ case VIR_DOMAIN_SHUTDOWN:
+ case VIR_DOMAIN_PMSUSPENDED:
+ case VIR_DOMAIN_LAST:
+ break;
+ }
+
+ return 1;
+}
+
+
+static const struct virshDomainEventAwaitCondition conditions[] = {
+ { .name = "domain-inactive",
+ .event = VIR_DOMAIN_EVENT_ID_LIFECYCLE,
+ .handler = virshDomainEventAwaitConditionDomainInactive,
+ },
+};
+
+
+static char **
+virshDomainAwaitConditionNameCompleter(vshControl *ctl G_GNUC_UNUSED,
+ const vshCmd *cmd G_GNUC_UNUSED,
+ unsigned int flags)
+{
+ size_t i = 0;
+ GStrv ret = NULL;
+
+ virCheckFlags(0, NULL);
+
+ ret = g_new0(char *, G_N_ELEMENTS(conditions) + 1);
+
+ for (i = 0; i < G_N_ELEMENTS(conditions); i++)
+ ret[i] = g_strdup(conditions[i].name);
+
+ return ret;
+}
+
+
+static const vshCmdInfo info_await = {
+ .help = N_("await a domain event"),
+ .desc = N_("waits for a certain domain event to happen and then
terminates"),
+};
+
+static const vshCmdOptDef opts_await[] = {
+ VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+ {.name = "condition",
+ .type = VSH_OT_STRING,
+ .required = true,
+ .completer = virshDomainAwaitConditionNameCompleter,
+ .help = N_("which condition to wait until")
+ },
+ {.name = "timeout",
+ .type = VSH_OT_INT,
+ .help = N_("timeout seconds")
+ },
+ {.name = NULL}
+};
+
+
+static int
+cmdAwait(vshControl *ctl,
+ const vshCmd *cmd)
+{
+ g_autoptr(virshDomain) dom = NULL;
+ int timeout = 0;
+ size_t i;
+ const char *conditionName = NULL;
+ virshControl *priv = ctl->privData;
+ const struct virshDomainEventAwaitCallbackTuple *callback = NULL;
+ int evid = -1;
+ g_auto(virMutex) m = VIR_MUTEX_INITIALIZER;
+ g_auto(virCond) c = VIR_COND_INITIALIZER;
+ struct virshDomEventAwaitConditionData data = {
+ .ctl = ctl,
+ .m = &m,
+ .c = &c,
+ };
+ int ret = -1;
+
+ if (vshCommandOptString(ctl, cmd, "condition", &conditionName) < 0)
+ return -1;
+
+ for (i = 0; i < G_N_ELEMENTS(conditions); i++) {
+ if (STREQ(conditionName, conditions[i].name)) {
+ data.cond = conditions + i;
+ break;
+ }
+ }
+
+ if (!data.cond) {
+ vshError(ctl, _("Unsupported await condition name '%1$s'"),
conditionName);
+ return -1;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS(callbacks); i++) {
+ if (data.cond->event == callbacks[i].event) {
+ callback = callbacks + i;
+ break;
+ }
+ }
+
+ if (!callback) {
+ vshError(ctl, _("Missing callback definition for event type
'%1$d'"), data.cond->event);
+ return -1;
+ }
+
+ if (vshCommandOptTimeoutToMs(ctl, cmd, &timeout) < 0)
+ return -1;
+
+ if (!(data.dom = dom = virshCommandOptDomain(ctl, cmd, NULL)))
+ return -1;
+
+ if (vshEventStart(ctl, timeout) < 0)
+ goto cleanup;
+
+ if ((evid = virConnectDomainEventRegisterAny(priv->conn, dom,
+ callback->event,
+ callback->eventCB,
+ &data,
+ (virFreeCallback)
virshDomEventAwaitConditionDataUnregistered)) < 0)
+ goto cleanup;
+
+ /* invoke the handler to ensure initial state update */
+ if ((ret = data.cond->handler(&data)) <= 0)
+ goto cleanup;
+
+ switch (vshEventWait(ctl)) {
+ case VSH_EVENT_INTERRUPT:
+ vshPrintExtra(ctl, "%s", _("event loop interrupted\n"));
+ ret = 2;
+ break;
+
+ case VSH_EVENT_TIMEOUT:
+ vshPrintExtra(ctl, "%s", _("event loop timed out\n"));
+ ret = 2;
+ break;
+
+ case VSH_EVENT_DONE:
+ ret = 0;
+ break;
+
+ default:
+ ret = -1;
+ goto cleanup;
+ }
+
+ cleanup:
+ if (evid >= 0) {
+ virConnectDomainEventDeregisterAny(priv->conn, evid);
+
+ virMutexLock(&m);
+ while (!data.done) {
+ if (virCondWait(&c, &m) < 0)
+ break;
+ }
+ virMutexUnlock(&m);
+ }
+ vshEventCleanup(ctl);
+
+ return ret;
+}
+
const vshCmdDef domEventCmds[] = {
{.name = "event",
.handler = cmdEvent,
@@ -1071,5 +1310,11 @@ const vshCmdDef domEventCmds[] = {
.info = &info_event,
.flags = 0
},
+ {.name = "await",
+ .handler_rv = cmdAwait,
+ .opts = opts_await,
+ .info = &info_await,
+ .flags = 0
+ },
{.name = NULL}
};
--
2.49.0