On 5/19/26 15:11, Lucas Kornicki wrote:
> Add support for a new domain event which can be used to track
> the state of any virtio channel.
>
> Previously one could only monitor the "org.qemu.guest_agent.0" channel
> which had a dedicated agent lifecycle event. The channel lifecycle event
> will be emitted alongside the agent specific one.
>
> Signed-off-by: Lucas Kornicki <[email protected]>
> ---
> examples/c/misc/event-test.c | 57 +++++++++++++++++
> include/libvirt/libvirt-domain.h | 65 +++++++++++++++++++
> src/conf/domain_event.c | 97 +++++++++++++++++++++++++++++
> src/conf/domain_event.h | 12 ++++
> src/libvirt_private.syms | 2 +
> src/remote/remote_daemon_dispatch.c | 34 ++++++++++
> src/remote/remote_driver.c | 34 ++++++++++
> src/remote/remote_protocol.x | 16 ++++-
> src/remote_protocol-structs | 8 +++
> tools/virsh-domain-event.c | 35 +++++++++++
> 10 files changed, 359 insertions(+), 1 deletion(-)
>
> diff --git a/examples/c/misc/event-test.c b/examples/c/misc/event-test.c
> index f9e65c55f0..601f5eafcf 100644
> --- a/examples/c/misc/event-test.c
> +++ b/examples/c/misc/event-test.c
> @@ -353,6 +353,45 @@ guestAgentLifecycleEventReasonToString(int event)
> return "unknown";
> }
>
> +
> +static const char *
> +guestChannelLifecycleEventStateToString(int event)
> +{
> + switch ((virConnectDomainEventChannelLifecycleState) event) {
> + case VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_DISCONNECTED:
> + return "Disconnected";
> +
> + case VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_CONNECTED:
> + return "Connected";
> +
> + case VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_LAST:
> + break;
> + }
> +
> + return "unknown";
> +}
> +
> +
> +static const char *
> +guestChannelLifecycleEventReasonToString(int event)
> +{
> + switch ((virConnectDomainEventChannelLifecycleReason) event) {
> + case VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_UNKNOWN:
> + return "Unknown";
> +
> + case VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_DOMAIN_STARTED:
> + return "Domain started";
> +
> + case VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_CHANNEL:
> + return "Channel event";
> +
> + case VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_LAST:
> + break;
> + }
> +
> + return "unknown";
> +}
> +
> static const char *
> storagePoolEventToString(int event)
> {
> @@ -869,6 +908,23 @@ myDomainEventAgentLifecycleCallback(virConnectPtr conn
> G_GNUC_UNUSED,
> }
>
>
> +static int
> +myDomainEventChannelLifecycleCallback(virConnectPtr conn G_GNUC_UNUSED,
> + virDomainPtr dom,
> + const char *channelName,
> + int state,
> + int reason,
> + void *opaque G_GNUC_UNUSED)
> +{
> + printf("%s EVENT: Domain %s(%d) guest channel(%s) state changed: %s
> reason: %s\n",
> + __func__, virDomainGetName(dom), virDomainGetID(dom), channelName,
> + guestChannelLifecycleEventStateToString(state),
> + guestChannelLifecycleEventReasonToString(reason));
> +
> + return 0;
> +}
> +
> +
> static int
> myDomainEventDeviceAddedCallback(virConnectPtr conn G_GNUC_UNUSED,
> virDomainPtr dom,
> @@ -1195,6 +1251,7 @@ struct domainEventData domainEvents[] = {
> DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_MEMORY_DEVICE_SIZE_CHANGE,
> myDomainEventMemoryDeviceSizeChangeCallback),
> DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_NIC_MAC_CHANGE,
> myDomainEventNICMACChangeCallback),
> DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_VCPU_REMOVED,
> myDomainEventVcpuRemovedCallback),
> + DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_CHANNEL_LIFECYCLE,
> myDomainEventChannelLifecycleCallback),
> };
>
> struct storagePoolEventData {
> diff --git a/include/libvirt/libvirt-domain.h
> b/include/libvirt/libvirt-domain.h
> index 1066a0b3f1..abc3be0252 100644
> --- a/include/libvirt/libvirt-domain.h
> +++ b/include/libvirt/libvirt-domain.h
> @@ -7673,6 +7673,70 @@ typedef void
> (*virConnectDomainEventNICMACChangeCallback)(virConnectPtr conn,
> const char *newMAC,
> void *opaque);
>
> +
> +/**
> + * virConnectDomainEventChannelLifecycleState:
> + *
> + * Since: 12.4.0
> + */
> +typedef enum {
> + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_CONNECTED = 1, /*
> channel connected (Since: 12.4.0) */
> + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_DISCONNECTED = 2, /*
> channel disconnected (Since: 12.4.0) */
> +
> +# ifdef VIR_ENUM_SENTINELS
> + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_LAST /* (Since: 12.4.0)
> */
> +# endif
> +} virConnectDomainEventChannelLifecycleState;
> +
> +/**
> + * virConnectDomainEventChannelLifecycleReason:
> + *
> + * The reason values are intentionally numerically aligned with
> + * virConnectDomainEventAgentLifecycleReason so that the qemu driver
> + * can pass the same int through both events.
True, but this is internal detail and I would not worry users with it.
They should use these enum values instead of those from
virConnectDomainEventAgentLifecycleReason enum.
> + *
> + * Since: 12.4.0
> + */
> +typedef enum {
> + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_UNKNOWN = 0, /*
> unknown state change reason (Since: 12.4.0) */
> + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_DOMAIN_STARTED = 1, /*
> state changed due to domain start (Since: 12.4.0) */
> + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_CHANNEL = 2, /*
> channel state changed (Since: 12.4.0) */
> +
> +# ifdef VIR_ENUM_SENTINELS
> + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_LAST /* (Since:
> 12.4.0) */
> +# endif
> +} virConnectDomainEventChannelLifecycleReason;
> +
> +/**
> + * virConnectDomainEventChannelLifecycleCallback:
> + * @conn: connection object
> + * @dom: domain on which the event occurred
> + * @channelName: the name of the channel on which the event occurred
> + * @state: new state of the guest channel, one of
> virConnectDomainEventChannelLifecycleState
> + * @reason: reason for state change, one of
> virConnectDomainEventChannelLifecycleReason
> + * @opaque: application specified data
> + *
> + * This callback occurs when libvirt detects a change in the state of a guest
> + * virtio-serial channel. Unlike VIR_DOMAIN_EVENT_ID_AGENT_LIFECYCLE which is
> + * tied to the QEMU guest agent channel ("org.qemu.guest_agent.0"), this
> event
> + * is emitted for every virtio-serial channel attached to the domain,
> + * including the guest agent channel.
> + *
> + * The hypervisor must support virtio-serial port state notifications for the
> + * event to be delivered.
> + *
> + * The callback signature to use when registering for an event of type
> + * VIR_DOMAIN_EVENT_ID_CHANNEL_LIFECYCLE with
> virConnectDomainEventRegisterAny()
> + *
> + * Since: 12.4.0
> + */
> +typedef void (*virConnectDomainEventChannelLifecycleCallback)(virConnectPtr
> conn,
> + virDomainPtr
> dom,
> + const char
> *channelName,
> + int state,
> + int reason,
> + void *opaque);
> +
> /**
> * VIR_DOMAIN_EVENT_CALLBACK:
> *
> @@ -7723,6 +7787,7 @@ typedef enum {
> VIR_DOMAIN_EVENT_ID_MEMORY_DEVICE_SIZE_CHANGE = 26, /*
> virConnectDomainEventMemoryDeviceSizeChangeCallback (Since: 7.9.0) */
> VIR_DOMAIN_EVENT_ID_NIC_MAC_CHANGE = 27, /*
> virConnectDomainEventNICMACChangeCallback (Since: 11.2.0) */
> VIR_DOMAIN_EVENT_ID_VCPU_REMOVED = 28, /*
> virConnectDomainEventVcpuRemovedCallback (Since: 12.4.0) */
> + VIR_DOMAIN_EVENT_ID_CHANNEL_LIFECYCLE = 29, /*
> virConnectDomainEventChannelLifecycleCallback (Since: 12.4.0) */
>
> # ifdef VIR_ENUM_SENTINELS
> VIR_DOMAIN_EVENT_ID_LAST
> diff --git a/src/conf/domain_event.c b/src/conf/domain_event.c
> index f09c6a9816..e44dae7922 100644
> --- a/src/conf/domain_event.c
> +++ b/src/conf/domain_event.c
> @@ -59,6 +59,7 @@ static virClass *virDomainEventBlockThresholdClass;
> static virClass *virDomainEventMemoryFailureClass;
> static virClass *virDomainEventMemoryDeviceSizeChangeClass;
> static virClass *virDomainEventNICMACChangeClass;
> +static virClass *virDomainEventChannelLifecycleClass;
>
> static void virDomainEventDispose(void *obj);
> static void virDomainEventLifecycleDispose(void *obj);
> @@ -85,6 +86,7 @@ static void virDomainEventBlockThresholdDispose(void *obj);
> static void virDomainEventMemoryFailureDispose(void *obj);
> static void virDomainEventMemoryDeviceSizeChangeDispose(void *obj);
> static void virDomainEventNICMACChangeDispose(void *obj);
> +static void virDomainEventChannelLifecycleDispose(void *obj);
>
> static void
> virDomainEventDispatchDefaultFunc(virConnectPtr conn,
> @@ -305,6 +307,23 @@ struct _virDomainEventNICMACChange {
> };
> typedef struct _virDomainEventNICMACChange virDomainEventNICMACChange;
>
> +struct _virDomainEventChannelLifecycle {
> + virDomainEvent parent;
> +
> + char *channelName;
> + int state;
> + int reason;
> +};
> +typedef struct _virDomainEventChannelLifecycle
> virDomainEventChannelLifecycle;
> +
> +/* Make sure the AGENT and CHANNEL lifecycle enums stay in sync with each
> other. */
> +G_STATIC_ASSERT((int)VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_DOMAIN_STARTED
> ==
> +
> (int)VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_DOMAIN_STARTED);
> +G_STATIC_ASSERT((int)VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL
> ==
> +
> (int)VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_CHANNEL);
> +G_STATIC_ASSERT((int)VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_LAST ==
> + (int)VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_LAST);
> +
What we are lacking is:
G_STATIC_ASSERT((int)VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED ==
(int)VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_CONNECTED);
G_STATIC_ASSERT((int)VIR_DOMAIN_CHR_DEVICE_STATE_DISCONNECTED ==
(int)VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_DISCONNECTED);
(I will post a patch for this shortly, as it is pre-existing)
And this patch should then have:
G_STATIC_ASSERT((int)VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED ==
(int)VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_CONNECTED);
G_STATIC_ASSERT((int)VIR_DOMAIN_CHR_DEVICE_STATE_DISCONNECTED ==
(int)VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_DISCONNECTED);
(both should be in src/conf/domain_conf.h)
The reason stems from processSerialChangedEvent() which declares a
variable like this:
virDomainChrDeviceState newstate;
and then uses it to create events:
event = virDomainEventAgentLifecycleNewFromObj(vm, newstate,
VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL);
Michal