From nobody Tue Sep 9 03:20:58 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass(p=reject dis=none) header.from=lists.libvirt.org ARC-Seal: i=1; a=rsa-sha256; t=1749111226; cv=none; d=zohomail.com; s=zohoarc; b=BraFSUCY0FRzf4GlWdthza3tTM3/4dIGKDdBzFZ+MBdWSepEjZcfZ/oRnCA59h8BEGk7ijMEneCGMnO2SXLP3B+nfakqCQBswjk2nnX8ktMXi5TQGFyEQmIurkvO1C6HOgwftkAapInrD/XC6MthQOWyfoAkg6bvAIy3xhBbZoo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1749111226; h=Content-Type:Content-Transfer-Encoding:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:References:Subject:Subject:To:To:Message-Id:Cc; bh=zBemTTqQjGWc2FWT0+KRKgYhsY9BI9N9SBmKH0tVZO4=; b=I/TETTuSy9sIXr/+0AJuwj/C9Z3b7bmXxexP2eCK4uG63/lEXaiD0DQx/INQAy9YKjqt/GuNWMyZLOVIHRJtHuswaQ0f5UZo70FTB0sGjPtJ8Y68DBa1/3LnmhNbM5R5Zazc+2fwi+oQaeEokvmk9hBI2wsbNnRWjBTTE5JsY/Q= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=pass header.from= (p=reject dis=none) Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1749111226302261.0653551351635; Thu, 5 Jun 2025 01:13:46 -0700 (PDT) Received: by lists.libvirt.org (Postfix, from userid 996) id 4AC751325; Thu, 5 Jun 2025 04:13:45 -0400 (EDT) Received: from lists.libvirt.org (localhost [IPv6:::1]) by lists.libvirt.org (Postfix) with ESMTP id 05810149D; Thu, 5 Jun 2025 04:11:17 -0400 (EDT) Received: by lists.libvirt.org (Postfix, from userid 996) id DB3AE1322; Thu, 5 Jun 2025 04:11:12 -0400 (EDT) Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 38EFC127F for ; Thu, 5 Jun 2025 04:11:00 -0400 (EDT) Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-647-8he8fv-CMiKNWo4JE7CRSg-1; Thu, 05 Jun 2025 04:10:58 -0400 Received: from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id B84C91955F41 for ; Thu, 5 Jun 2025 08:10:57 +0000 (UTC) Received: from speedmetal.redhat.com (unknown [10.44.22.3]) by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id F199118003FC for ; Thu, 5 Jun 2025 08:10:56 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=DKIM_INVALID,DKIM_SIGNED, MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_HELO_NONE autolearn=unavailable autolearn_force=no version=3.4.4 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1749111059; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=oZvHT1nFKRm/eDcDdb/sNekrd7QgNolTtwuHiPDaaQY=; b=MQErnFPp4XKRpCiCVXn00Dvc20j4fL/1rSh3UkvXFY+KKBjtgUCXsjlG4kyw9MHlugXJ9z lvKN7/dWE3RqMoDdaD84GwmGCgBZ+V4fGyCqi3tG01E0yByBftQ61l4L016we63lvj5o7u Bet30pQTHWYTMrWw4FU/+qnfhs3hts4= X-MC-Unique: 8he8fv-CMiKNWo4JE7CRSg-1 X-Mimecast-MFC-AGG-ID: 8he8fv-CMiKNWo4JE7CRSg_1749111057 To: devel@lists.libvirt.org Subject: [PATCH 6/7] virsh: Introduce 'await' command for waiting until target domain state is reached Date: Thu, 5 Jun 2025 10:10:46 +0200 Message-ID: <0b3da6419b31d8a297c7849b39808b387d7b1fd4.1749110902.git.pkrempa@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.93 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: ddZqpzTzXCXEfrke1K-QEOXnpTpj90PB83aHIvW86QY_1749111057 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable Message-ID-Hash: DPGU74H5RJRGTFHCDY6BTIJG4KRTRIEA X-Message-ID-Hash: DPGU74H5RJRGTFHCDY6BTIJG4KRTRIEA X-MailFrom: pkrempa@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; header-match-config-1; header-match-config-2; header-match-config-3; header-match-devel.lists.libvirt.org-0; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; suspicious-header X-Mailman-Version: 3.2.2 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Post: List-Subscribe: List-Unsubscribe: From: Peter Krempa via Devel Reply-To: Peter Krempa X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1749111226903116600 Content-Type: text/plain; charset="utf-8" From: Peter Krempa 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 --- 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 timesta= mp will be printed before the event. +await +----- + +**Syntax:** + +:: + + await --condition [--timeout seconds] + +Wait until the *--condition* for 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 conditio= n to +satisfy after *seconds* have elapsed. If SIGINT is delivered to virsh +(usually via ``Ctrl-C``) the wait is given up immediately. In non-interact= ive +mode virsh will return '2' if either of those cases instead of '1' which m= eans +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 virshDomEventAwaitCondi= tionData *data) +{ + g_auto(virLockGuard) name =3D virLockGuardLock(data->m); + /* signal that the handler was unregistered */ + data->done =3D 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 =3D opaque; + + if (data->cond->handler(data) < 1) + vshEventDone(data->ctl); +} + + +struct virshDomainEventAwaitCallbackTuple { + int event; + virConnectDomainEventGenericCallback eventCB; +}; + + +static const struct virshDomainEventAwaitCallbackTuple callbacks[] =3D +{ + { .event =3D VIR_DOMAIN_EVENT_ID_LIFECYCLE, + .eventCB =3D VIR_DOMAIN_EVENT_CALLBACK(virshDomainEventAwaitCallback= Lifecycle), + }, +}; + + +static int +virshDomainEventAwaitConditionDomainInactive(struct virshDomEventAwaitCond= itionData *data) +{ + int state =3D -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[] =3D { + { .name =3D "domain-inactive", + .event =3D VIR_DOMAIN_EVENT_ID_LIFECYCLE, + .handler =3D virshDomainEventAwaitConditionDomainInactive, + }, +}; + + +static char ** +virshDomainAwaitConditionNameCompleter(vshControl *ctl G_GNUC_UNUSED, + const vshCmd *cmd G_GNUC_UNUSED, + unsigned int flags) +{ + size_t i =3D 0; + GStrv ret =3D NULL; + + virCheckFlags(0, NULL); + + ret =3D g_new0(char *, G_N_ELEMENTS(conditions) + 1); + + for (i =3D 0; i < G_N_ELEMENTS(conditions); i++) + ret[i] =3D g_strdup(conditions[i].name); + + return ret; +} + + +static const vshCmdInfo info_await =3D { + .help =3D N_("await a domain event"), + .desc =3D N_("waits for a certain domain event to happen and then ter= minates"), +}; + +static const vshCmdOptDef opts_await[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "condition", + .type =3D VSH_OT_STRING, + .required =3D true, + .completer =3D virshDomainAwaitConditionNameCompleter, + .help =3D N_("which condition to wait until") + }, + {.name =3D "timeout", + .type =3D VSH_OT_INT, + .help =3D N_("timeout seconds") + }, + {.name =3D NULL} +}; + + +static int +cmdAwait(vshControl *ctl, + const vshCmd *cmd) +{ + g_autoptr(virshDomain) dom =3D NULL; + int timeout =3D 0; + size_t i; + const char *conditionName =3D NULL; + virshControl *priv =3D ctl->privData; + const struct virshDomainEventAwaitCallbackTuple *callback =3D NULL; + int evid =3D -1; + g_auto(virMutex) m =3D VIR_MUTEX_INITIALIZER; + g_auto(virCond) c =3D VIR_COND_INITIALIZER; + struct virshDomEventAwaitConditionData data =3D { + .ctl =3D ctl, + .m =3D &m, + .c =3D &c, + }; + int ret =3D -1; + + if (vshCommandOptString(ctl, cmd, "condition", &conditionName) < 0) + return -1; + + for (i =3D 0; i < G_N_ELEMENTS(conditions); i++) { + if (STREQ(conditionName, conditions[i].name)) { + data.cond =3D conditions + i; + break; + } + } + + if (!data.cond) { + vshError(ctl, _("Unsupported await condition name '%1$s'"), condit= ionName); + return -1; + } + + for (i =3D 0; i < G_N_ELEMENTS(callbacks); i++) { + if (data.cond->event =3D=3D callbacks[i].event) { + callback =3D 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 =3D dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + return -1; + + if (vshEventStart(ctl, timeout) < 0) + goto cleanup; + + if ((evid =3D virConnectDomainEventRegisterAny(priv->conn, dom, + callback->event, + callback->eventCB, + &data, + (virFreeCallback) virshDo= mEventAwaitConditionDataUnregistered)) < 0) + goto cleanup; + + /* invoke the handler to ensure initial state update */ + if ((ret =3D data.cond->handler(&data)) <=3D 0) + goto cleanup; + + switch (vshEventWait(ctl)) { + case VSH_EVENT_INTERRUPT: + vshPrintExtra(ctl, "%s", _("event loop interrupted\n")); + ret =3D 2; + break; + + case VSH_EVENT_TIMEOUT: + vshPrintExtra(ctl, "%s", _("event loop timed out\n")); + ret =3D 2; + break; + + case VSH_EVENT_DONE: + ret =3D 0; + break; + + default: + ret =3D -1; + goto cleanup; + } + + cleanup: + if (evid >=3D 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[] =3D { {.name =3D "event", .handler =3D cmdEvent, @@ -1071,5 +1310,11 @@ const vshCmdDef domEventCmds[] =3D { .info =3D &info_event, .flags =3D 0 }, + {.name =3D "await", + .handler_rv =3D cmdAwait, + .opts =3D opts_await, + .info =3D &info_await, + .flags =3D 0 + }, {.name =3D NULL} }; --=20 2.49.0