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. + * + * 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); + static int virDomainEventsOnceInit(void) { @@ -358,6 +377,8 @@ virDomainEventsOnceInit(void) return -1; if (!VIR_CLASS_NEW(virDomainEventNICMACChange, virDomainEventClass)) return -1; + if (!VIR_CLASS_NEW(virDomainEventChannelLifecycle, virDomainEventClass)) + return -1; return 0; } @@ -600,6 +621,14 @@ virDomainEventNICMACChangeDispose(void *obj) g_free(event->newMAC); } +static void +virDomainEventChannelLifecycleDispose(void *obj) +{ + virDomainEventChannelLifecycle *event = obj; + + g_free(event->channelName); +} + static void * virDomainEventNew(virClass *klass, int eventID, @@ -1867,6 +1896,61 @@ virDomainEventNICMACChangeNewFromDom(virDomainPtr dom, } + +static virObjectEvent * +virDomainEventChannelLifecycleNew(int id, + const char *name, + const unsigned char *uuid, + const char *channelName, + int state, + int reason) +{ + virDomainEventChannelLifecycle *ev; + + if (virDomainEventsInitialize() < 0) + return NULL; + + if (!(ev = virDomainEventNew(virDomainEventChannelLifecycleClass, + VIR_DOMAIN_EVENT_ID_CHANNEL_LIFECYCLE, + id, name, uuid))) + return NULL; + + ev->channelName = g_strdup(channelName); + ev->state = state; + ev->reason = reason; + + return (virObjectEvent *)ev; +} + + +virObjectEvent * +virDomainEventChannelLifecycleNewFromObj(virDomainObj *obj, + const char *channelName, + int state, + int reason) +{ + return virDomainEventChannelLifecycleNew(obj->def->id, + obj->def->name, + obj->def->uuid, + channelName, + state, + reason); +} + +virObjectEvent * +virDomainEventChannelLifecycleNewFromDom(virDomainPtr dom, + const char *channelName, + int state, + int reason) +{ + return virDomainEventChannelLifecycleNew(dom->id, + dom->name, + dom->uuid, + channelName, + state, + reason); +} + static void virDomainEventDispatchDefaultFunc(virConnectPtr conn, virObjectEvent *event, @@ -2200,6 +2284,19 @@ virDomainEventDispatchDefaultFunc(virConnectPtr conn, goto cleanup; } + case VIR_DOMAIN_EVENT_ID_CHANNEL_LIFECYCLE: + { + virDomainEventChannelLifecycle *channelLifecycleEvent; + + channelLifecycleEvent = (virDomainEventChannelLifecycle *)event; + ((virConnectDomainEventChannelLifecycleCallback)cb)(conn, dom, + channelLifecycleEvent->channelName, + channelLifecycleEvent->state, + channelLifecycleEvent->reason, + cbopaque); + goto cleanup; + } + case VIR_DOMAIN_EVENT_ID_LAST: break; } diff --git a/src/conf/domain_event.h b/src/conf/domain_event.h index 930b66b13a..4678bec385 100644 --- a/src/conf/domain_event.h +++ b/src/conf/domain_event.h @@ -295,6 +295,18 @@ virDomainEventNICMACChangeNewFromDom(virDomainPtr dom, const char *oldMAC, const char *newMAC); +virObjectEvent * +virDomainEventChannelLifecycleNewFromObj(virDomainObj *obj, + const char *channelName, + int state, + int reason); + +virObjectEvent * +virDomainEventChannelLifecycleNewFromDom(virDomainPtr dom, + const char *channelName, + int state, + int reason); + int virDomainEventStateRegister(virConnectPtr conn, virObjectEventState *state, diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 7232b7c663..a76da45fb9 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -761,6 +761,8 @@ virDomainEventBlockJobNewFromDom; virDomainEventBlockJobNewFromObj; virDomainEventBlockThresholdNewFromDom; virDomainEventBlockThresholdNewFromObj; +virDomainEventChannelLifecycleNewFromDom; +virDomainEventChannelLifecycleNewFromObj; virDomainEventControlErrorNewFromDom; virDomainEventControlErrorNewFromObj; virDomainEventDeviceAddedNewFromDom; diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c index 55a1bd2af8..329853b6da 100644 --- a/src/remote/remote_daemon_dispatch.c +++ b/src/remote/remote_daemon_dispatch.c @@ -1379,6 +1379,39 @@ remoteRelayDomainEventNICMACChange(virConnectPtr conn, } +static int +remoteRelayDomainEventChannelLifecycle(virConnectPtr conn, + virDomainPtr dom, + const char *channelName, + int state, + int reason, + void *opaque) +{ + daemonClientEventCallback *callback = opaque; + remote_domain_event_callback_channel_lifecycle_msg data = { 0 }; + + if (callback->callbackID < 0 || + !remoteRelayDomainEventCheckACL(callback->client, conn, dom)) + return -1; + + VIR_DEBUG("Relaying domain channel lifecycle event %s %d, callback %d, " + "name %s, state %d, reason %d", + dom->name, dom->id, callback->callbackID, channelName, state, reason); + + data.callbackID = callback->callbackID; + make_nonnull_domain(&data.dom, dom); + data.channelName = g_strdup(channelName); + data.state = state; + data.reason = reason; + + remoteDispatchObjectEventSend(callback->client, remoteProgram, + REMOTE_PROC_DOMAIN_EVENT_CALLBACK_CHANNEL_LIFECYCLE, + (xdrproc_t)xdr_remote_domain_event_callback_channel_lifecycle_msg, + &data); + return 0; +} + + static virConnectDomainEventGenericCallback domainEventCallbacks[] = { VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventLifecycle), VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventReboot), @@ -1409,6 +1442,7 @@ static virConnectDomainEventGenericCallback domainEventCallbacks[] = { VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventMemoryDeviceSizeChange), VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventNICMACChange), VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventVcpuRemoved), + VIR_DOMAIN_EVENT_CALLBACK(remoteRelayDomainEventChannelLifecycle), }; G_STATIC_ASSERT(G_N_ELEMENTS(domainEventCallbacks) == VIR_DOMAIN_EVENT_ID_LAST); diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index e83b0abcbe..873e3d173c 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -441,6 +441,11 @@ remoteDomainBuildEventNICMACChange(virNetClientProgram *prog, virNetClient *client, void *evdata, void *opaque); +static void +remoteDomainBuildEventCallbackChannelLifecycle(virNetClientProgram *prog, + virNetClient *client, + void *evdata, void *opaque); + static virNetClientProgramEvent remoteEvents[] = { { REMOTE_PROC_DOMAIN_EVENT_LIFECYCLE, remoteDomainBuildEventLifecycle, @@ -667,6 +672,10 @@ static virNetClientProgramEvent remoteEvents[] = { remoteDomainBuildEventVcpuRemoved, sizeof(remote_domain_event_vcpu_removed_msg), (xdrproc_t)xdr_remote_domain_event_vcpu_removed_msg }, + { REMOTE_PROC_DOMAIN_EVENT_CALLBACK_CHANNEL_LIFECYCLE, + remoteDomainBuildEventCallbackChannelLifecycle, + sizeof(remote_domain_event_callback_channel_lifecycle_msg), + (xdrproc_t)xdr_remote_domain_event_callback_channel_lifecycle_msg }, }; static void @@ -5192,6 +5201,31 @@ remoteDomainBuildEventNICMACChange(virNetClientProgram *prog G_GNUC_UNUSED, } +static void +remoteDomainBuildEventCallbackChannelLifecycle(virNetClientProgram *prog G_GNUC_UNUSED, + virNetClient *client G_GNUC_UNUSED, + void *evdata, void *opaque) +{ + virConnectPtr conn = opaque; + remote_domain_event_callback_channel_lifecycle_msg *msg = evdata; + struct private_data *priv = conn->privateData; + virDomainPtr dom; + virObjectEvent *event = NULL; + + if (!(dom = get_nonnull_domain(conn, msg->dom))) + return; + + event = virDomainEventChannelLifecycleNewFromDom(dom, + msg->channelName, + msg->state, + msg->reason); + + virObjectUnref(dom); + + virObjectEventStateQueueRemote(priv->eventState, event, msg->callbackID); +} + + static int remoteStreamSend(virStreamPtr st, const char *data, diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 23699e99a6..4adba82f6d 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -4015,6 +4015,14 @@ struct remote_domain_event_nic_mac_change_msg { remote_nonnull_string newMAC; }; +struct remote_domain_event_callback_channel_lifecycle_msg { + int callbackID; + remote_nonnull_domain dom; + remote_nonnull_string channelName; + int state; + int reason; +}; + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -7132,5 +7140,11 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_DOMAIN_EVENT_VCPU_REMOVED = 454 + REMOTE_PROC_DOMAIN_EVENT_VCPU_REMOVED = 454, + + /** + * @generate: both + * @acl: none + */ + REMOTE_PROC_DOMAIN_EVENT_CALLBACK_CHANNEL_LIFECYCLE = 455 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 4c24245d2b..dd297bffff 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -3342,6 +3342,13 @@ struct remote_domain_event_nic_mac_change_msg { remote_nonnull_string oldMAC; remote_nonnull_string newMAC; }; +struct remote_domain_event_callback_channel_lifecycle_msg { + int callbackID; + remote_nonnull_domain dom; + remote_nonnull_string channelName; + int state; + int reason; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -3797,4 +3804,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_DEL_THROTTLE_GROUP = 452, REMOTE_PROC_DOMAIN_EVENT_NIC_MAC_CHANGE = 453, REMOTE_PROC_DOMAIN_EVENT_VCPU_REMOVED = 454, + REMOTE_PROC_DOMAIN_EVENT_CALLBACK_CHANNEL_LIFECYCLE = 455, }; diff --git a/tools/virsh-domain-event.c b/tools/virsh-domain-event.c index 4a9a831b45..a541b155f4 100644 --- a/tools/virsh-domain-event.c +++ b/tools/virsh-domain-event.c @@ -655,6 +655,39 @@ virshEventAgentLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED, virshEventPrint(opaque, &buf); } +VIR_ENUM_DECL(virshEventChannelLifecycleState); +VIR_ENUM_IMPL(virshEventChannelLifecycleState, + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_STATE_LAST, + N_("unknown"), + N_("connected"), + N_("disconnected")); + +VIR_ENUM_DECL(virshEventChannelLifecycleReason); +VIR_ENUM_IMPL(virshEventChannelLifecycleReason, + VIR_CONNECT_DOMAIN_EVENT_CHANNEL_LIFECYCLE_REASON_LAST, + N_("unknown"), + N_("domain started"), + N_("channel event")); + +static void +virshEventChannelLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED, + virDomainPtr dom, + const char *channelName, + int state, + int reason, + void *opaque) +{ + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + + virBufferAsprintf(&buf, + _("event 'channel-lifecycle' for domain '%1$s': channel name: '%2$s' state: '%3$s' reason: '%4$s'\n"), + virDomainGetName(dom), + channelName, + UNKNOWNSTR(virshEventChannelLifecycleStateTypeToString(state)), + UNKNOWNSTR(virshEventChannelLifecycleReasonTypeToString(reason))); + virshEventPrint(opaque, &buf); +} + static void virshEventMigrationIterationPrint(virConnectPtr conn G_GNUC_UNUSED, virDomainPtr dom, @@ -889,6 +922,8 @@ virshDomainEventCallback virshDomainEventCallbacks[] = { VIR_DOMAIN_EVENT_CALLBACK(virshEventNICMACChangePrint), }, { "vcpu-removed", VIR_DOMAIN_EVENT_CALLBACK(virshEventVcpuRemovedPrint), }, + { "channel-lifecycle", + VIR_DOMAIN_EVENT_CALLBACK(virshEventChannelLifecyclePrint), }, }; G_STATIC_ASSERT(VIR_DOMAIN_EVENT_ID_LAST == G_N_ELEMENTS(virshDomainEventCallbacks)); -- 2.43.0
