From e7708a1c476202029c8c948941ec08a0ffa9b5ab Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Wed, 29 Jan 2014 15:30:44 -0700 Subject: [PATCH] qemu: wire up RPC for qemu monitor events These are the first async events in the qemu protocol, so this patch looks rather big compared to most RPC additions. However, a large majority of this patch is just mechanical copy-and-paste from recently-added network events. It didn't help that this is also the first virConnect rather than virDomain prefix associated with a qemu-specific API. * src/remote/qemu_protocol.x (qemu_*_domain_monitor_event_*): New structs and RPC messages. * src/rpc/gendispatch.pl: Adjust naming conventions. * daemon/libvirtd.h (daemonClientPrivate): Track qemu events. * daemon/remote.c (remoteClientFreeFunc): Likewise. (remoteRelayDomainQemuMonitorEvent) (qemuDispatchConnectDomainMonitorEventRegister) (qemuDispatchConnectDomainMonitorEventDeregister): New functions. * src/remote/remote_driver.c (qemuEvents): Handle qemu events. (doRemoteOpen): Register for events. (remoteNetworkBuildEventLifecycle) (remoteConnectDomainQemuMonitorEventRegister) (remoteConnectDomainQemuMonitorEventDeregister): New functions. * src/qemu_protocol-structs: Regenerate. Signed-off-by: Eric Blake --- daemon/libvirtd.h | 2 + daemon/remote.c | 209 +++++++++++++++++++++++++++++++++++++ src/qemu_protocol-structs | 22 ++++ src/remote/qemu_protocol.x | 50 ++++++++- src/remote/remote_driver.c | 143 ++++++++++++++++++++++++- src/rpc/gendispatch.pl | 13 +-- 6 files changed, 428 insertions(+), 11 deletions(-) diff --git a/daemon/libvirtd.h b/daemon/libvirtd.h index 650267ea4a..02d4101742 100644 --- a/daemon/libvirtd.h +++ b/daemon/libvirtd.h @@ -54,6 +54,8 @@ struct daemonClientPrivate { size_t ndomainEventCallbacks; daemonClientEventCallbackPtr *networkEventCallbacks; size_t nnetworkEventCallbacks; + daemonClientEventCallbackPtr *qemuEventCallbacks; + size_t nqemuEventCallbacks; # if WITH_SASL virNetSASLSessionPtr sasl; diff --git a/daemon/remote.c b/daemon/remote.c index 85ad16685a..50e258daf4 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -54,6 +54,7 @@ #include "network_conf.h" #include "virprobe.h" #include "viraccessapicheck.h" +#include "viraccessapicheckqemu.h" #define VIR_FROM_THIS VIR_FROM_RPC @@ -189,6 +190,33 @@ cleanup: } +static bool +remoteRelayDomainQemuMonitorEventCheckACL(virNetServerClientPtr client, + virConnectPtr conn, virDomainPtr dom) +{ + virDomainDef def; + virIdentityPtr identity = NULL; + bool ret = false; + + /* For now, we just create a virDomainDef with enough contents to + * satisfy what viraccessdriverpolkit.c references. This is a bit + * fragile, but I don't know of anything better. */ + def.name = dom->name; + memcpy(def.uuid, dom->uuid, VIR_UUID_BUFLEN); + + if (!(identity = virNetServerClientGetIdentity(client))) + goto cleanup; + if (virIdentitySetCurrent(identity) < 0) + goto cleanup; + ret = virConnectDomainQemuMonitorEventRegisterCheckACL(conn, &def); + +cleanup: + ignore_value(virIdentitySetCurrent(NULL)); + virObjectUnref(identity); + return ret; +} + + static int remoteRelayDomainEventLifecycle(virConnectPtr conn, virDomainPtr dom, @@ -961,6 +989,52 @@ static virConnectNetworkEventGenericCallback networkEventCallbacks[] = { verify(ARRAY_CARDINALITY(networkEventCallbacks) == VIR_NETWORK_EVENT_ID_LAST); +static void +remoteRelayDomainQemuMonitorEvent(virConnectPtr conn, + virDomainPtr dom, + const char *event, + long long seconds, + unsigned int micros, + const char *details, + void *opaque) +{ + daemonClientEventCallbackPtr callback = opaque; + qemu_domain_monitor_event_msg data; + char **details_p = NULL; + + if (callback->callbackID < 0 || + !remoteRelayDomainQemuMonitorEventCheckACL(callback->client, conn, + dom)) + return; + + VIR_DEBUG("Relaying qemu monitor event %s %s, callback %d", + event, details, callback->callbackID); + + /* build return data */ + memset(&data, 0, sizeof(data)); + data.callbackID = callback->callbackID; + if (VIR_STRDUP(data.event, event) < 0) + goto error; + data.seconds = seconds; + data.micros = micros; + if (details && + ((VIR_ALLOC(details_p) < 0) || + VIR_STRDUP(*details_p, details) < 0)) + goto error; + data.details = details_p; + make_nonnull_domain(&data.dom, dom); + + remoteDispatchObjectEventSend(callback->client, qemuProgram, + QEMU_PROC_DOMAIN_MONITOR_EVENT, + (xdrproc_t)xdr_qemu_domain_monitor_event_msg, + &data); + return; + +error: + VIR_FREE(data.event); + VIR_FREE(details_p); +} + /* * You must hold lock for at least the client * We don't free stuff here, merely disconnect the client's @@ -1008,6 +1082,21 @@ void remoteClientFreeFunc(void *data) } VIR_FREE(priv->networkEventCallbacks); + for (i = 0; i < priv->nqemuEventCallbacks; i++) { + int callbackID = priv->qemuEventCallbacks[i]->callbackID; + if (callbackID < 0) { + VIR_WARN("unexpected incomplete qemu monitor callback %zu", i); + continue; + } + VIR_DEBUG("Deregistering remote qemu monitor event relay %d", + callbackID); + priv->qemuEventCallbacks[i]->callbackID = -1; + if (virConnectDomainQemuMonitorEventDeregister(priv->conn, + callbackID) < 0) + VIR_WARN("unexpected qemu monitor event deregister failure"); + } + VIR_FREE(priv->qemuEventCallbacks); + virConnectClose(priv->conn); virIdentitySetCurrent(NULL); @@ -5869,6 +5958,126 @@ cleanup: } +static int +qemuDispatchConnectDomainMonitorEventRegister(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr ATTRIBUTE_UNUSED, + qemu_connect_domain_monitor_event_register_args *args, + qemu_connect_domain_monitor_event_register_ret *ret) +{ + int callbackID; + int rv = -1; + daemonClientEventCallbackPtr callback = NULL; + daemonClientEventCallbackPtr ref; + struct daemonClientPrivate *priv = + virNetServerClientGetPrivateData(client); + virDomainPtr dom = NULL; + const char *event = args->event ? *args->event : NULL; + + if (!priv->conn) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + virMutexLock(&priv->lock); + + if (args->dom && + !(dom = get_nonnull_domain(priv->conn, *args->dom))) + goto cleanup; + + /* If we call register first, we could append a complete callback + * to our array, but on OOM append failure, we'd have to then hope + * deregister works to undo our register. So instead we append an + * incomplete callback to our array, then register, then fix up + * our callback; but since VIR_APPEND_ELEMENT clears 'callback' on + * success, we use 'ref' to save a copy of the pointer. */ + if (VIR_ALLOC(callback) < 0) + goto cleanup; + callback->client = client; + callback->callbackID = -1; + ref = callback; + if (VIR_APPEND_ELEMENT(priv->qemuEventCallbacks, + priv->nqemuEventCallbacks, + callback) < 0) + goto cleanup; + + if ((callbackID = virConnectDomainQemuMonitorEventRegister(priv->conn, + dom, + event, + remoteRelayDomainQemuMonitorEvent, + ref, + remoteEventCallbackFree, + args->flags)) < 0) { + VIR_SHRINK_N(priv->qemuEventCallbacks, + priv->nqemuEventCallbacks, 1); + callback = ref; + goto cleanup; + } + + ref->callbackID = callbackID; + ret->callbackID = callbackID; + + rv = 0; + +cleanup: + VIR_FREE(callback); + if (rv < 0) + virNetMessageSaveError(rerr); + if (dom) + virDomainFree(dom); + virMutexUnlock(&priv->lock); + return rv; +} + + +static int +qemuDispatchConnectDomainMonitorEventDeregister(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr ATTRIBUTE_UNUSED, + qemu_connect_domain_monitor_event_deregister_args *args) +{ + int rv = -1; + size_t i; + struct daemonClientPrivate *priv = + virNetServerClientGetPrivateData(client); + + if (!priv->conn) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + virMutexLock(&priv->lock); + + for (i = 0; i < priv->nqemuEventCallbacks; i++) { + if (priv->qemuEventCallbacks[i]->callbackID == args->callbackID) + break; + } + if (i == priv->nqemuEventCallbacks) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("qemu monitor event callback %d not registered"), + args->callbackID); + goto cleanup; + } + + if (virConnectDomainQemuMonitorEventDeregister(priv->conn, + args->callbackID) < 0) + goto cleanup; + + VIR_DELETE_ELEMENT(priv->qemuEventCallbacks, i, + priv->nqemuEventCallbacks); + + rv = 0; + +cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + virMutexUnlock(&priv->lock); + return rv; +} + + /*----- Helpers. -----*/ /* get_nonnull_domain and get_nonnull_network turn an on-wire diff --git a/src/qemu_protocol-structs b/src/qemu_protocol-structs index 0dcd2c65a4..8501543cd9 100644 --- a/src/qemu_protocol-structs +++ b/src/qemu_protocol-structs @@ -28,8 +28,30 @@ struct qemu_domain_agent_command_args { struct qemu_domain_agent_command_ret { remote_string result; }; +struct qemu_connect_domain_monitor_event_register_args { + remote_domain dom; + remote_string event; + u_int flags; +}; +struct qemu_connect_domain_monitor_event_register_ret { + int callbackID; +}; +struct qemu_connect_domain_monitor_event_deregister_args { + int callbackID; +}; +struct qemu_domain_monitor_event_msg { + int callbackID; + remote_nonnull_domain dom; + remote_nonnull_string event; + int64_t seconds; + u_int micros; + remote_string details; +}; enum qemu_procedure { QEMU_PROC_DOMAIN_MONITOR_COMMAND = 1, QEMU_PROC_DOMAIN_ATTACH = 2, QEMU_PROC_DOMAIN_AGENT_COMMAND = 3, + QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER = 4, + QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER = 5, + QEMU_PROC_DOMAIN_MONITOR_EVENT = 6, }; diff --git a/src/remote/qemu_protocol.x b/src/remote/qemu_protocol.x index 1e7cf7c9ab..f6b88a984c 100644 --- a/src/remote/qemu_protocol.x +++ b/src/remote/qemu_protocol.x @@ -3,7 +3,7 @@ * remote_internal driver and libvirtd. This protocol is * internal and may change at any time. * - * Copyright (C) 2010-2012 Red Hat, Inc. + * Copyright (C) 2010-2014 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -58,6 +58,30 @@ struct qemu_domain_agent_command_ret { remote_string result; }; + +struct qemu_connect_domain_monitor_event_register_args { + remote_domain dom; + remote_string event; + unsigned int flags; +}; + +struct qemu_connect_domain_monitor_event_register_ret { + int callbackID; +}; + +struct qemu_connect_domain_monitor_event_deregister_args { + int callbackID; +}; + +struct qemu_domain_monitor_event_msg { + int callbackID; + remote_nonnull_domain dom; + remote_nonnull_string event; + hyper seconds; + unsigned int micros; + remote_string details; +}; + /* Define the program number, protocol version and procedure numbers here. */ const QEMU_PROGRAM = 0x20008087; const QEMU_PROTOCOL_VERSION = 1; @@ -108,5 +132,27 @@ enum qemu_procedure { * @priority: low * @acl: domain:write */ - QEMU_PROC_DOMAIN_AGENT_COMMAND = 3 + QEMU_PROC_DOMAIN_AGENT_COMMAND = 3, + + /** + * @generate: none + * @priority: high + * @acl: connect:search_domains + * @acl: connect:write + * @aclfilter: domain:getattr + */ + QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER = 4, + + /** + * @generate: none + * @priority: high + * @acl: connect:write + */ + QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER = 5, + + /** + * @generate: both + * @acl: none + */ + QEMU_PROC_DOMAIN_MONITOR_EVENT = 6 }; diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 92aff636ca..f6d52e2065 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -500,6 +500,19 @@ static virNetClientProgramEvent remoteEvents[] = { (xdrproc_t)xdr_remote_domain_event_callback_device_removed_msg }, }; + +static void +remoteDomainBuildQemuMonitorEvent(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque); + +static virNetClientProgramEvent qemuEvents[] = { + { QEMU_PROC_DOMAIN_MONITOR_EVENT, + remoteDomainBuildQemuMonitorEvent, + sizeof(qemu_domain_monitor_event_msg), + (xdrproc_t)xdr_qemu_domain_monitor_event_msg }, +}; + enum virDrvOpenRemoteFlags { VIR_DRV_OPEN_REMOTE_RO = (1 << 0), VIR_DRV_OPEN_REMOTE_USER = (1 << 1), /* Use the per-user socket path */ @@ -977,9 +990,9 @@ doRemoteOpen(virConnectPtr conn, goto failed; if (!(priv->qemuProgram = virNetClientProgramNew(QEMU_PROGRAM, QEMU_PROTOCOL_VERSION, - NULL, - 0, - NULL))) + qemuEvents, + ARRAY_CARDINALITY(qemuEvents), + conn))) goto failed; if (virNetClientAddProgram(priv->client, priv->remoteProgram) < 0 || @@ -3175,6 +3188,103 @@ done: } +static int +remoteConnectDomainQemuMonitorEventRegister(virConnectPtr conn, + virDomainPtr dom, + const char *event, + virConnectDomainQemuMonitorEventCallback callback, + void *opaque, + virFreeCallback freecb, + unsigned int flags) +{ + int rv = -1; + struct private_data *priv = conn->privateData; + qemu_connect_domain_monitor_event_register_args args; + qemu_connect_domain_monitor_event_register_ret ret; + int callbackID; + int count; + remote_nonnull_domain domain; + + remoteDriverLock(priv); + + if ((count = virDomainQemuMonitorEventStateRegisterID(conn, + priv->eventState, + dom, event, callback, + opaque, freecb, -1, + &callbackID)) < 0) + goto done; + + /* If this is the first callback for this event, we need to enable + * events on the server */ + if (count == 1) { + if (dom) { + make_nonnull_domain(&domain, dom); + args.dom = &domain; + } else { + args.dom = NULL; + } + args.event = event ? (char **) &event : NULL; + args.flags = flags; + + memset(&ret, 0, sizeof(ret)); + if (call(conn, priv, REMOTE_CALL_QEMU, QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER, + (xdrproc_t) xdr_qemu_connect_domain_monitor_event_register_args, (char *) &args, + (xdrproc_t) xdr_qemu_connect_domain_monitor_event_register_ret, (char *) &ret) == -1) { + virObjectEventStateDeregisterID(conn, priv->eventState, + callbackID); + goto done; + } + virObjectEventStateSetRemote(conn, priv->eventState, callbackID, + ret.callbackID); + } + + rv = callbackID; + +done: + remoteDriverUnlock(priv); + return rv; +} + + +static int +remoteConnectDomainQemuMonitorEventDeregister(virConnectPtr conn, + int callbackID) +{ + struct private_data *priv = conn->privateData; + int rv = -1; + qemu_connect_domain_monitor_event_deregister_args args; + int remoteID; + int count; + + remoteDriverLock(priv); + + if (virObjectEventStateEventID(conn, priv->eventState, + callbackID, &remoteID) < 0) + goto done; + + if ((count = virObjectEventStateDeregisterID(conn, priv->eventState, + callbackID)) < 0) + goto done; + + /* If that was the last callback for this event, we need to disable + * events on the server */ + if (count == 0) { + args.callbackID = remoteID; + + if (call(conn, priv, REMOTE_CALL_QEMU, QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER, + (xdrproc_t) xdr_qemu_connect_domain_monitor_event_deregister_args, (char *) &args, + (xdrproc_t) xdr_void, (char *) NULL) == -1) + goto done; + } + + rv = 0; + +done: + remoteDriverUnlock(priv); + return rv; +} + + static int remoteConnectListAllInterfaces(virConnectPtr conn, virInterfacePtr **ifaces, @@ -5410,6 +5520,31 @@ remoteNetworkBuildEventLifecycle(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, } +static void +remoteDomainBuildQemuMonitorEvent(virNetClientProgramPtr prog ATTRIBUTE_UNUSED, + virNetClientPtr client ATTRIBUTE_UNUSED, + void *evdata, void *opaque) +{ + virConnectPtr conn = opaque; + struct private_data *priv = conn->privateData; + qemu_domain_monitor_event_msg *msg = evdata; + virDomainPtr dom; + virObjectEventPtr event = NULL; + + dom = get_nonnull_domain(conn, msg->dom); + if (!dom) + return; + + event = virDomainQemuMonitorEventNew(dom->id, dom->name, dom->uuid, + msg->event, msg->seconds, + msg->micros, + msg->details ? *msg->details : NULL); + virDomainFree(dom); + + remoteEventQueue(priv, event, msg->callbackID); +} + + static virDrvOpenStatus ATTRIBUTE_NONNULL(1) remoteSecretOpen(virConnectPtr conn, virConnectAuthPtr auth, unsigned int flags) @@ -7620,6 +7755,8 @@ static virDriver remote_driver = { .domainQemuMonitorCommand = remoteDomainQemuMonitorCommand, /* 0.8.3 */ .domainQemuAttach = remoteDomainQemuAttach, /* 0.9.4 */ .domainQemuAgentCommand = remoteDomainQemuAgentCommand, /* 0.10.0 */ + .connectDomainQemuMonitorEventRegister = remoteConnectDomainQemuMonitorEventRegister, /* 1.2.3 */ + .connectDomainQemuMonitorEventDeregister = remoteConnectDomainQemuMonitorEventDeregister, /* 1.2.3 */ .domainOpenConsole = remoteDomainOpenConsole, /* 0.8.6 */ .domainOpenChannel = remoteDomainOpenChannel, /* 1.0.2 */ .domainOpenGraphics = remoteDomainOpenGraphics, /* 0.9.7 */ diff --git a/src/rpc/gendispatch.pl b/src/rpc/gendispatch.pl index ceb1ad8746..b76bbac3f7 100755 --- a/src/rpc/gendispatch.pl +++ b/src/rpc/gendispatch.pl @@ -1,6 +1,6 @@ #!/usr/bin/perl -w # -# Copyright (C) 2010-2013 Red Hat, Inc. +# Copyright (C) 2010-2014 Red Hat, Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -927,8 +927,9 @@ elsif ($mode eq "server") { push(@args_list, "priv->conn"); } - if ($structprefix eq "qemu" && $call->{ProcName} =~ /^Domain/) { - $proc_name =~ s/^(Domain)/${1}Qemu/; + if ($structprefix eq "qemu" && + $call->{ProcName} =~ /^(Connect)?Domain/) { + $proc_name =~ s/^((Connect)?Domain)/${1}Qemu/; } if ($structprefix eq "lxc" && $call->{ProcName} =~ /^Domain/) { $proc_name =~ s/^(Domain)/${1}Lxc/; @@ -1704,7 +1705,7 @@ elsif ($mode eq "client") { if ($mode eq "aclsym") { my $apiname = "vir" . $call->{ProcName}; if ($structprefix eq "qemu") { - $apiname =~ s/virDomain/virDomainQemu/; + $apiname =~ s/(vir(Connect)?Domain)/${1}Qemu/; } elsif ($structprefix eq "lxc") { $apiname =~ s/virDomain/virDomainLxc/; } @@ -1744,7 +1745,7 @@ elsif ($mode eq "client") { my $apiname = "vir" . $call->{ProcName}; if ($structprefix eq "qemu") { - $apiname =~ s/virDomain/virDomainQemu/; + $apiname =~ s/(vir(Connect)?Domain)/${1}Qemu/; } elsif ($structprefix eq "lxc") { $apiname =~ s/virDomain/virDomainLxc/; } @@ -1856,7 +1857,7 @@ elsif ($mode eq "client") { my $apiname = "vir" . $call->{ProcName}; if ($structprefix eq "qemu") { - $apiname =~ s/virDomain/virDomainQemu/; + $apiname =~ s/(vir(Connect)?Domain)/${1}Qemu/; } elsif ($structprefix eq "lxc") { $apiname =~ s/virDomain/virDomainLxc/; }