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

Reply via email to