[libvirt] [PATCH 2/2] Added network events API and unit tests

Cédric Bosdonnat cbosdonnat at suse.com
Tue Nov 19 15:43:14 UTC 2013


Network events are now supported by the bridge and test drivers.

To distinguish the network event IDs from the domain ones, the event
IDs are prefixed with namespace bits. So any event id follows this rule:
VIR_EVENT_NAMESPACE_XXX << 8 & EVENT_ID. With the namespace of domains
being 0, compatibility is kept for previous client code.

To use the new network events API, see the virConnectNetworkEvent*Any
functions. At the moment, the network events report about network
definition, undefinition, start and stop. Other events may be provided
later.
---
 .gitignore                            |   1 +
 daemon/libvirtd.h                     |   1 +
 daemon/remote.c                       | 175 +++++++++++++++++++++++---
 include/libvirt/libvirt.h.in          |  77 ++++++++++++
 python/generator.py                   |   2 +
 python/libvirt-override-virConnect.py |  34 +++++
 python/libvirt-override.c             | 150 ++++++++++++++++++++++
 src/conf/object_event.c               |  92 +++++++++++++-
 src/conf/object_event.h               |  21 ++++
 src/conf/object_event_private.h       |   7 ++
 src/driver.h                          |  14 +++
 src/libvirt.c                         | 133 ++++++++++++++++++++
 src/libvirt_private.syms              |   3 +
 src/libvirt_public.syms               |   6 +
 src/network/bridge_driver.c           |  91 +++++++++++++-
 src/network/bridge_driver_platform.h  |   3 +
 src/remote/remote_driver.c            | 123 ++++++++++++++++++
 src/remote/remote_protocol.x          |  46 ++++++-
 src/test/test_driver.c                | 116 +++++++++++++----
 tests/Makefile.am                     |   7 ++
 tests/objecteventtest.c               | 228 ++++++++++++++++++++++++++++++++++
 21 files changed, 1288 insertions(+), 42 deletions(-)
 create mode 100644 tests/objecteventtest.c

diff --git a/.gitignore b/.gitignore
index 05be8a7..fcf5d1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -174,6 +174,7 @@
 /tests/object-locking
 /tests/object-locking-files.txt
 /tests/object-locking.cm[ix]
+/tests/objecteventtest
 /tests/openvzutilstest
 /tests/qemuagenttest
 /tests/qemuargv2xmltest
diff --git a/daemon/libvirtd.h b/daemon/libvirtd.h
index d0afdc8..47f2589 100644
--- a/daemon/libvirtd.h
+++ b/daemon/libvirtd.h
@@ -50,6 +50,7 @@ struct daemonClientPrivate {
     virMutex lock;
 
     int domainEventCallbackID[VIR_DOMAIN_EVENT_ID_LAST];
+    int networkEventCallbackID[VIR_NETWORK_EVENT_ID_LAST];
 
 # if WITH_SASL
     virNetSASLSessionPtr sasl;
diff --git a/daemon/remote.c b/daemon/remote.c
index decaecc..6281d08 100644
--- a/daemon/remote.c
+++ b/daemon/remote.c
@@ -108,7 +108,7 @@ remoteSerializeDomainDiskErrors(virDomainDiskErrorPtr errors,
 
 /* Prototypes */
 static void
-remoteDispatchDomainEventSend(virNetServerClientPtr client,
+remoteDispatchObjectEventSend(virNetServerClientPtr client,
                               virNetServerProgramPtr program,
                               int procnr,
                               xdrproc_t proc,
@@ -134,7 +134,7 @@ static int remoteRelayDomainEventLifecycle(virConnectPtr conn ATTRIBUTE_UNUSED,
     data.event = event;
     data.detail = detail;
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_LIFECYCLE,
                                   (xdrproc_t)xdr_remote_domain_event_lifecycle_msg, &data);
 
@@ -157,7 +157,7 @@ static int remoteRelayDomainEventReboot(virConnectPtr conn ATTRIBUTE_UNUSED,
     memset(&data, 0, sizeof(data));
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_REBOOT,
                                   (xdrproc_t)xdr_remote_domain_event_reboot_msg, &data);
 
@@ -183,7 +183,7 @@ static int remoteRelayDomainEventRTCChange(virConnectPtr conn ATTRIBUTE_UNUSED,
     make_nonnull_domain(&data.dom, dom);
     data.offset = offset;
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_RTC_CHANGE,
                                   (xdrproc_t)xdr_remote_domain_event_rtc_change_msg, &data);
 
@@ -209,7 +209,7 @@ static int remoteRelayDomainEventWatchdog(virConnectPtr conn ATTRIBUTE_UNUSED,
     make_nonnull_domain(&data.dom, dom);
     data.action = action;
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_WATCHDOG,
                                   (xdrproc_t)xdr_remote_domain_event_watchdog_msg, &data);
 
@@ -240,7 +240,7 @@ static int remoteRelayDomainEventIOError(virConnectPtr conn ATTRIBUTE_UNUSED,
     make_nonnull_domain(&data.dom, dom);
     data.action = action;
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_IO_ERROR,
                                   (xdrproc_t)xdr_remote_domain_event_io_error_msg, &data);
 
@@ -279,7 +279,7 @@ static int remoteRelayDomainEventIOErrorReason(virConnectPtr conn ATTRIBUTE_UNUS
 
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_IO_ERROR_REASON,
                                   (xdrproc_t)xdr_remote_domain_event_io_error_reason_msg, &data);
 
@@ -342,7 +342,7 @@ static int remoteRelayDomainEventGraphics(virConnectPtr conn ATTRIBUTE_UNUSED,
     }
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_GRAPHICS,
                                   (xdrproc_t)xdr_remote_domain_event_graphics_msg, &data);
 
@@ -388,7 +388,7 @@ static int remoteRelayDomainEventBlockJob(virConnectPtr conn ATTRIBUTE_UNUSED,
     data.status = status;
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_BLOCK_JOB,
                                   (xdrproc_t)xdr_remote_domain_event_block_job_msg, &data);
 
@@ -415,7 +415,7 @@ static int remoteRelayDomainEventControlError(virConnectPtr conn ATTRIBUTE_UNUSE
     memset(&data, 0, sizeof(data));
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_CONTROL_ERROR,
                                   (xdrproc_t)xdr_remote_domain_event_control_error_msg, &data);
 
@@ -461,7 +461,7 @@ static int remoteRelayDomainEventDiskChange(virConnectPtr conn ATTRIBUTE_UNUSED,
 
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_DISK_CHANGE,
                                   (xdrproc_t)xdr_remote_domain_event_disk_change_msg, &data);
 
@@ -497,7 +497,7 @@ static int remoteRelayDomainEventTrayChange(virConnectPtr conn ATTRIBUTE_UNUSED,
 
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_TRAY_CHANGE,
                                   (xdrproc_t)xdr_remote_domain_event_tray_change_msg, &data);
 
@@ -520,7 +520,7 @@ static int remoteRelayDomainEventPMWakeup(virConnectPtr conn ATTRIBUTE_UNUSED,
     memset(&data, 0, sizeof(data));
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_PMWAKEUP,
                                   (xdrproc_t)xdr_remote_domain_event_pmwakeup_msg, &data);
 
@@ -543,7 +543,7 @@ static int remoteRelayDomainEventPMSuspend(virConnectPtr conn ATTRIBUTE_UNUSED,
     memset(&data, 0, sizeof(data));
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_PMSUSPEND,
                                   (xdrproc_t)xdr_remote_domain_event_pmsuspend_msg, &data);
 
@@ -569,7 +569,7 @@ remoteRelayDomainEventBalloonChange(virConnectPtr conn ATTRIBUTE_UNUSED,
     make_nonnull_domain(&data.dom, dom);
     data.actual = actual;
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_BALLOON_CHANGE,
                                   (xdrproc_t)xdr_remote_domain_event_balloon_change_msg, &data);
 
@@ -593,7 +593,7 @@ static int remoteRelayDomainEventPMSuspendDisk(virConnectPtr conn ATTRIBUTE_UNUS
     memset(&data, 0, sizeof(data));
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_PMSUSPEND_DISK,
                                   (xdrproc_t)xdr_remote_domain_event_pmsuspend_disk_msg, &data);
 
@@ -623,7 +623,7 @@ remoteRelayDomainEventDeviceRemoved(virConnectPtr conn ATTRIBUTE_UNUSED,
 
     make_nonnull_domain(&data.dom, dom);
 
-    remoteDispatchDomainEventSend(client, remoteProgram,
+    remoteDispatchObjectEventSend(client, remoteProgram,
                                   REMOTE_PROC_DOMAIN_EVENT_DEVICE_REMOVED,
                                   (xdrproc_t)xdr_remote_domain_event_device_removed_msg,
                                   &data);
@@ -653,6 +653,37 @@ static virConnectDomainEventGenericCallback domainEventCallbacks[] = {
 
 verify(ARRAY_CARDINALITY(domainEventCallbacks) == VIR_DOMAIN_EVENT_ID_LAST);
 
+static int remoteRelayNetworkEventLifecycle(virConnectPtr conn ATTRIBUTE_UNUSED,
+                                           virNetworkPtr net,
+                                           int event,
+                                           void *opaque)
+{
+    virNetServerClientPtr client = opaque;
+    remote_network_event_lifecycle_msg data;
+
+    if (!client)
+        return -1;
+
+    VIR_DEBUG("Relaying network lifecycle event %d", event);
+
+    /* build return data */
+    memset(&data, 0, sizeof(data));
+    make_nonnull_network(&data.net, net);
+    data.event = event;
+
+    remoteDispatchObjectEventSend(client, remoteProgram,
+                                  REMOTE_PROC_NETWORK_EVENT_LIFECYCLE,
+                                  (xdrproc_t)xdr_remote_network_event_lifecycle_msg, &data);
+
+    return 0;
+}
+
+static virConnectNetworkEventGenericCallback networkEventCallbacks[] = {
+    VIR_NETWORK_EVENT_CALLBACK(remoteRelayNetworkEventLifecycle),
+};
+
+verify(ARRAY_CARDINALITY(networkEventCallbacks) == VIR_NETWORK_EVENT_ID_LAST);
+
 /*
  * You must hold lock for at least the client
  * We don't free stuff here, merely disconnect the client's
@@ -680,6 +711,15 @@ void remoteClientFreeFunc(void *data)
             priv->domainEventCallbackID[i] = -1;
         }
 
+        for (i = 0; i < VIR_NETWORK_EVENT_ID_LAST; i++) {
+            if (priv->networkEventCallbackID[i] != -1) {
+                VIR_DEBUG("Deregistering to relay remote events %zu", i);
+                virConnectNetworkEventDeregisterAny(priv->conn,
+                                                    priv->networkEventCallbackID[i]);
+            }
+            priv->networkEventCallbackID[i] = -1;
+        }
+
         virConnectClose(priv->conn);
 
         virIdentitySetCurrent(NULL);
@@ -716,6 +756,9 @@ void *remoteClientInitHook(virNetServerClientPtr client,
     for (i = 0; i < VIR_DOMAIN_EVENT_ID_LAST; i++)
         priv->domainEventCallbackID[i] = -1;
 
+    for (i = 0; i < VIR_NETWORK_EVENT_ID_LAST; i++)
+        priv->networkEventCallbackID[i] = -1;
+
     virNetServerClientSetCloseHook(client, remoteClientCloseFunc);
     return priv;
 }
@@ -3158,7 +3201,7 @@ cleanup:
 }
 
 static void
-remoteDispatchDomainEventSend(virNetServerClientPtr client,
+remoteDispatchObjectEventSend(virNetServerClientPtr client,
                               virNetServerProgramPtr program,
                               int procnr,
                               xdrproc_t proc,
@@ -5216,6 +5259,102 @@ cleanup:
 }
 
 
+static int
+remoteDispatchConnectNetworkEventRegisterAny(virNetServerPtr server ATTRIBUTE_UNUSED,
+                                             virNetServerClientPtr client ATTRIBUTE_UNUSED,
+                                             virNetMessagePtr msg ATTRIBUTE_UNUSED,
+                                             virNetMessageErrorPtr rerr ATTRIBUTE_UNUSED,
+                                             remote_connect_network_event_register_any_args *args,
+                                             remote_connect_network_event_register_any_ret *ret ATTRIBUTE_UNUSED)
+{
+    int callbackID;
+    int rv = -1;
+    struct daemonClientPrivate *priv =
+        virNetServerClientGetPrivateData(client);
+
+    if (!priv->conn) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open"));
+        goto cleanup;
+    }
+
+    virMutexLock(&priv->lock);
+
+    if ((args->eventID & 0xFF) >= VIR_NETWORK_EVENT_ID_LAST ||
+        ((args->eventID & 0xFF00) >> 8) != VIR_EVENT_NAMESPACE_NETWORK) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, _("unsupported event ID %d"), args->eventID);
+        goto cleanup;
+    }
+
+    if (priv->networkEventCallbackID[args->eventID & 0xFF] != -1)  {
+        virReportError(VIR_ERR_INTERNAL_ERROR, _("network event %d already registered"), args->eventID);
+        goto cleanup;
+    }
+
+    if ((callbackID = virConnectNetworkEventRegisterAny(priv->conn,
+                                                        NULL,
+                                                        args->eventID,
+                                                        networkEventCallbacks[args->eventID & 0xFF],
+                                                        client, NULL)) < 0)
+        goto cleanup;
+
+    priv->networkEventCallbackID[args->eventID & 0xFF] = callbackID;
+
+    rv = 0;
+
+cleanup:
+    if (rv < 0)
+        virNetMessageSaveError(rerr);
+    virMutexUnlock(&priv->lock);
+    return rv;
+}
+
+
+static int
+remoteDispatchConnectNetworkEventDeregisterAny(virNetServerPtr server ATTRIBUTE_UNUSED,
+                                               virNetServerClientPtr client ATTRIBUTE_UNUSED,
+                                               virNetMessagePtr msg ATTRIBUTE_UNUSED,
+                                               virNetMessageErrorPtr rerr ATTRIBUTE_UNUSED,
+                                               remote_connect_network_event_deregister_any_args *args,
+                                               remote_connect_network_event_deregister_any_ret *ret ATTRIBUTE_UNUSED)
+{
+    int callbackID = -1;
+    int rv = -1;
+    struct daemonClientPrivate *priv =
+        virNetServerClientGetPrivateData(client);
+
+    if (!priv->conn) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open"));
+        goto cleanup;
+    }
+
+    virMutexLock(&priv->lock);
+
+    if ((args->eventID & 0xFF) >= VIR_NETWORK_EVENT_ID_LAST ||
+        ((args->eventID & 0xFF00) >> 8) != VIR_EVENT_NAMESPACE_NETWORK) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, _("unsupported event ID %d"), args->eventID);
+        goto cleanup;
+    }
+
+    callbackID = priv->networkEventCallbackID[args->eventID & 0xFF];
+    if (callbackID < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, _("network event %d not registered"), args->eventID);
+        goto cleanup;
+    }
+
+    if (virConnectNetworkEventDeregisterAny(priv->conn, callbackID) < 0)
+        goto cleanup;
+
+    priv->networkEventCallbackID[args->eventID & 0xFF] = -1;
+
+    rv = 0;
+
+cleanup:
+    if (rv < 0)
+        virNetMessageSaveError(rerr);
+    virMutexUnlock(&priv->lock);
+    return rv;
+}
+
 
 /*----- Helpers. -----*/
 
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index b13118d..4e7f651 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -4964,6 +4964,7 @@ typedef enum {
   */
 typedef enum {
     VIR_EVENT_NAMESPACE_DOMAIN = 0, /* 0 to keep value of virDomainEventId unchanged */
+    VIR_EVENT_NAMESPACE_NETWORK = 1,
 } virEventNamespaceID;
 
 
@@ -4977,7 +4978,83 @@ int virConnectDomainEventRegisterAny(virConnectPtr conn,
 
 int virConnectDomainEventDeregisterAny(virConnectPtr conn,
                                        int callbackID);
+/**
+ * virNetworkEventLifecycleType:
+ *
+ * a virNetworkEventLifecycleType is emitted during network lifecycle events
+ */
+typedef enum {
+    VIR_NETWORK_EVENT_DEFINED = 0,
+    VIR_NETWORK_EVENT_UNDEFINED = 1,
+    VIR_NETWORK_EVENT_STARTED = 2,
+    VIR_NETWORK_EVENT_STOPPED = 3,
+
+#ifdef VIR_ENUM_SENTINELS
+    VIR_NETWORK_EVENT_LAST
+#endif
+} virNetworkEventLifecycleType;
+
+/**
+ * virConnectNetworkEventLifecycleCallback:
+ * @conn: connection object
+ * @net: network on which the event occurred
+ * @event: The specific virNetworkEventLifeCycleType which occurred
+ * @opaque: application specified data
+ *
+ * This callback occurs when the network is started or stopped.
+ *
+ * The callback signature to use when registering for an event of type
+ * VIR_NETWORK_EVENT_ID_LIFECYCLE with virConnectNetworkEventRegisterAny()
+ */
+typedef void (*virConnectNetworkEventLifecycleCallback)(virConnectPtr conn,
+                                                        virNetworkPtr net,
+                                                        int event,
+                                                        void *opaque);
+
+/**
+ * VIR_NETWORK_EVENT_CALLBACK:
+ *
+ * Used to cast the event specific callback into the generic one
+ * for use for virNetworkEventRegister
+ */
+#define VIR_NETWORK_EVENT_CALLBACK(cb) ((virConnectNetworkEventGenericCallback)(cb))
+
+typedef enum {
+    VIR_NETWORK_EVENT_ID_LIFECYCLE = 0,       /* virConnectNetworkEventLifecycleCallback */
+
+#ifdef VIR_ENUM_SENTINELS
+    VIR_NETWORK_EVENT_ID_LAST
+    /*
+     * NB: this enum value will increase over time as new events are
+     * added to the libvirt API. It reflects the last event ID supported
+     * by this version of the libvirt API.
+     */
+#endif
+} virNetworkEventID;
+
+/*
+ * virConnectNetworkEventGenericCallback:
+ * @conn: the connection pointer
+ * @net: the network pointer
+ * @opaque: application specified data
+ *
+ * A generic network event callback handler. Specific events usually
+ * have a customization with extra parameters
+ */
+typedef void (*virConnectNetworkEventGenericCallback)(virConnectPtr conn,
+                                                      virNetworkPtr net,
+                                                      void *opaque);
+
+/* Use VIR_NETWORK_EVENT_CALLBACK() to cast the 'cb' parameter  */
+int virConnectNetworkEventRegisterAny(virConnectPtr conn,
+                                      virNetworkPtr net, /* Optional, to filter */
+                                      int eventID,
+                                      virConnectNetworkEventGenericCallback cb,
+                                      void *opaque,
+                                      virFreeCallback freecb);
 
+int virConnectNetworkEventDeregisterAny(virConnectPtr conn,
+                                        int callbackID);
 
 /**
  * virNWFilter:
diff --git a/python/generator.py b/python/generator.py
index 87ecf5a..c2b3bad 100755
--- a/python/generator.py
+++ b/python/generator.py
@@ -482,6 +482,8 @@ skip_function = (
     'virConnectDomainEventDeregister', # overridden in virConnect.py
     'virConnectDomainEventRegisterAny',   # overridden in virConnect.py
     'virConnectDomainEventDeregisterAny', # overridden in virConnect.py
+    'virConnectNetworkEventRegisterAny',   # overridden in virConnect.py
+    'virConnectNetworkEventDeregisterAny', # overridden in virConnect.py
     'virSaveLastError', # We have our own python error wrapper
     'virFreeError', # Only needed if we use virSaveLastError
     'virConnectListAllDomains', # overridden in virConnect.py
diff --git a/python/libvirt-override-virConnect.py b/python/libvirt-override-virConnect.py
index 4ba3d30..65e8874 100644
--- a/python/libvirt-override-virConnect.py
+++ b/python/libvirt-override-virConnect.py
@@ -213,6 +213,40 @@
         self.domainEventCallbackID[ret] = opaque
         return ret
 
+    def _dispatchNetworkEventLifecycleCallback(self, net, event, cbData):
+        """Dispatches events to python user network lifecycle event callbacks
+        """
+        cb = cbData["cb"]
+        opaque = cbData["opaque"]
+
+        cb(self, virNetwork(self, _obj=net), event, opaque)
+        return 0
+
+    def networkEventDeregisterAny(self, callbackID):
+        """Removes a Network Event Callback. De-registering for a
+           network callback will disable delivery of this event type """
+        try:
+            ret = libvirtmod.virConnectNetworkEventDeregisterAny(self._o, callbackID)
+            if ret == -1: raise libvirtError ('virConnectNetworkEventDeregisterAny() failed', conn=self)
+            del self.networkEventCallbackID[callbackID]
+        except AttributeError:
+            pass
+
+    def networkEventRegisterAny(self, net, eventID, cb, opaque):
+        """Adds a Network Event Callback. Registering for a network
+           callback will enable delivery of the events """
+        if not hasattr(self, 'networkEventCallbackID'):
+            self.networkEventCallbackID = {}
+        cbData = { "cb": cb, "conn": self, "opaque": opaque }
+        if net is None:
+            ret = libvirtmod.virConnectNetworkEventRegisterAny(self._o, None, eventID, cbData)
+        else:
+            ret = libvirtmod.virConnectNetworkEventRegisterAny(self._o, net._o, eventID, cbData)
+        if ret == -1:
+            raise libvirtError ('virConnectNetworkEventRegisterAny() failed', conn=self)
+        self.networkEventCallbackID[ret] = opaque
+        return ret
+
     def listAllDomains(self, flags=0):
         """List all domains and returns a list of domain objects"""
         ret = libvirtmod.virConnectListAllDomains(self._o, flags)
diff --git a/python/libvirt-override.c b/python/libvirt-override.c
index 2e58bf9..26d8a0c 100644
--- a/python/libvirt-override.c
+++ b/python/libvirt-override.c
@@ -6452,6 +6452,154 @@ libvirt_virConnectDomainEventDeregisterAny(ATTRIBUTE_UNUSED PyObject * self,
     return py_retval;
 }
 
+static void
+libvirt_virConnectNetworkEventFreeFunc(void *opaque)
+{
+    PyObject *pyobj_conn = (PyObject*)opaque;
+    LIBVIRT_ENSURE_THREAD_STATE;
+    Py_DECREF(pyobj_conn);
+    LIBVIRT_RELEASE_THREAD_STATE;
+}
+
+static int
+libvirt_virConnectNetworkEventLifecycleCallback(virConnectPtr conn ATTRIBUTE_UNUSED,
+                                               virNetworkPtr net,
+                                               int event,
+                                               void *opaque)
+{
+    PyObject *pyobj_cbData = (PyObject*)opaque;
+    PyObject *pyobj_net;
+    PyObject *pyobj_ret;
+    PyObject *pyobj_conn;
+    PyObject *dictKey;
+    int ret = -1;
+
+    LIBVIRT_ENSURE_THREAD_STATE;
+
+    /* Create a python instance of this virNetworkPtr */
+    virNetworkRef(net);
+    pyobj_net = libvirt_virNetworkPtrWrap(net);
+    Py_INCREF(pyobj_cbData);
+
+    dictKey = libvirt_constcharPtrWrap("conn");
+    pyobj_conn = PyDict_GetItem(pyobj_cbData, dictKey);
+    Py_DECREF(dictKey);
+
+    /* Call the Callback Dispatcher */
+    pyobj_ret = PyObject_CallMethod(pyobj_conn,
+                                    (char*)"_dispatchNetworkEventLifecycleCallback",
+                                    (char*)"OiO",
+                                    pyobj_net,
+                                    event,
+                                    pyobj_cbData);
+
+    Py_DECREF(pyobj_cbData);
+    Py_DECREF(pyobj_net);
+
+    if (!pyobj_ret) {
+        DEBUG("%s - ret:%p\n", __FUNCTION__, pyobj_ret);
+        PyErr_Print();
+    } else {
+        Py_DECREF(pyobj_ret);
+        ret = 0;
+    }
+
+    LIBVIRT_RELEASE_THREAD_STATE;
+    return ret;
+}
+
+static PyObject *
+libvirt_virConnectNetworkEventRegisterAny(ATTRIBUTE_UNUSED PyObject * self,
+                                         PyObject * args)
+{
+    PyObject *py_retval;        /* return value */
+    PyObject *pyobj_conn;       /* virConnectPtr */
+    PyObject *pyobj_net;
+    PyObject *pyobj_cbData;     /* hash of callback data */
+    int eventID;
+    virConnectPtr conn;
+    int ret = 0;
+    virConnectNetworkEventGenericCallback cb = NULL;
+    virNetworkPtr net;
+    virNetworkEventID eventId = VIR_NETWORK_EVENT_ID_LAST;
+
+    if (!PyArg_ParseTuple
+        (args, (char *) "OOiO:virConnectNetworkEventRegisterAny",
+         &pyobj_conn, &pyobj_net, &eventID, &pyobj_cbData)) {
+        DEBUG("%s failed parsing tuple\n", __FUNCTION__);
+        return VIR_PY_INT_FAIL;
+    }
+
+    DEBUG("libvirt_virConnectNetworkEventRegister(%p %p %d %p) called\n",
+           pyobj_conn, pyobj_net, eventID, pyobj_cbData);
+    conn = PyvirConnect_Get(pyobj_conn);
+    if (pyobj_net == Py_None)
+        net = NULL;
+    else
+        net = PyvirNetwork_Get(pyobj_net);
+
+    if ( ((eventID & 0xFF00) >> 8) != VIR_EVENT_NAMESPACE_NETWORK) {
+        return VIR_PY_INT_FAIL;
+    }
+
+    eventId = (virNetworkEventID) (eventID & 0xFF);
+    switch (eventId) {
+    case VIR_NETWORK_EVENT_ID_LIFECYCLE:
+        cb = VIR_NETWORK_EVENT_CALLBACK(libvirt_virConnectNetworkEventLifecycleCallback);
+        break;
+
+    case VIR_NETWORK_EVENT_ID_LAST:
+        break;
+    }
+
+    if (!cb) {
+        return VIR_PY_INT_FAIL;
+    }
+
+    Py_INCREF(pyobj_cbData);
+
+    LIBVIRT_BEGIN_ALLOW_THREADS;
+    ret = virConnectNetworkEventRegisterAny(conn, net, eventID,
+                                           cb, pyobj_cbData,
+                                           libvirt_virConnectNetworkEventFreeFunc);
+    LIBVIRT_END_ALLOW_THREADS;
+
+    if (ret < 0) {
+        Py_DECREF(pyobj_cbData);
+    }
+
+    py_retval = libvirt_intWrap(ret);
+    return py_retval;
+}
+
+static PyObject *
+libvirt_virConnectNetworkEventDeregisterAny(ATTRIBUTE_UNUSED PyObject * self,
+                                           PyObject * args)
+{
+    PyObject *py_retval;
+    PyObject *pyobj_conn;
+    int callbackID;
+    virConnectPtr conn;
+    int ret = 0;
+
+    if (!PyArg_ParseTuple
+        (args, (char *) "Oi:virConnectNetworkEventDeregister",
+         &pyobj_conn, &callbackID))
+        return NULL;
+
+    DEBUG("libvirt_virConnectNetworkEventDeregister(%p) called\n", pyobj_conn);
+
+    conn   = (virConnectPtr) PyvirConnect_Get(pyobj_conn);
+
+    LIBVIRT_BEGIN_ALLOW_THREADS;
+
+    ret = virConnectNetworkEventDeregisterAny(conn, callbackID);
+
+    LIBVIRT_END_ALLOW_THREADS;
+    py_retval = libvirt_intWrap(ret);
+    return py_retval;
+}
+
 
 static void
 libvirt_virConnectCloseCallbackDispatch(virConnectPtr conn ATTRIBUTE_UNUSED,
@@ -7237,6 +7385,8 @@ static PyMethodDef libvirtMethods[] = {
     {(char *) "virConnectDomainEventDeregister", libvirt_virConnectDomainEventDeregister, METH_VARARGS, NULL},
     {(char *) "virConnectDomainEventRegisterAny", libvirt_virConnectDomainEventRegisterAny, METH_VARARGS, NULL},
     {(char *) "virConnectDomainEventDeregisterAny", libvirt_virConnectDomainEventDeregisterAny, METH_VARARGS, NULL},
+    {(char *) "virConnectNetworkEventRegisterAny", libvirt_virConnectNetworkEventRegisterAny, METH_VARARGS, NULL},
+    {(char *) "virConnectNetworkEventDeregisterAny", libvirt_virConnectNetworkEventDeregisterAny, METH_VARARGS, NULL},
     {(char *) "virConnectRegisterCloseCallback", libvirt_virConnectRegisterCloseCallback, METH_VARARGS, NULL},
     {(char *) "virConnectUnregisterCloseCallback", libvirt_virConnectUnregisterCloseCallback, METH_VARARGS, NULL},
     {(char *) "virStreamEventAddCallback", libvirt_virStreamEventAddCallback, METH_VARARGS, NULL},
diff --git a/src/conf/object_event.c b/src/conf/object_event.c
index 46eb677..59351f0 100644
--- a/src/conf/object_event.c
+++ b/src/conf/object_event.c
@@ -329,6 +329,11 @@ virDomainEventDataFree(virObjectEventPtr event)
     }
 }
 
+static void
+virNetworkEventDataFree(virObjectEventPtr event ATTRIBUTE_UNUSED)
+{
+}
+
 void virObjectEventFree(virObjectEventPtr event)
 {
     if (!event)
@@ -340,6 +345,8 @@ void virObjectEventFree(virObjectEventPtr event)
         case VIR_EVENT_NAMESPACE_DOMAIN:
             virDomainEventDataFree(event);
             break;
+        case VIR_EVENT_NAMESPACE_NETWORK:
+            virNetworkEventDataFree(event);
     }
 
     VIR_FREE(event->dom.name);
@@ -652,6 +659,35 @@ cleanup:
     virDomainFree(dom);
 }
 
+static void
+virNetworkEventDispatchDefaultFunc(virConnectPtr conn,
+                                   virObjectEventPtr event,
+                                   virConnectNetworkEventGenericCallback cb ATTRIBUTE_UNUSED,
+                                   void *cbopaque ATTRIBUTE_UNUSED,
+                                   void *opaque ATTRIBUTE_UNUSED)
+{
+    virNetworkPtr net = virGetNetwork(conn, event->dom.name, event->dom.uuid);
+    if (!net)
+        return;
+
+    switch ((virNetworkEventID) (event->eventID &0xFF) ) {
+    case VIR_DOMAIN_EVENT_ID_LIFECYCLE:
+        ((virConnectNetworkEventLifecycleCallback)cb)(conn, net,
+                                                      event->data.networkData.lifecycle.type,
+                                                      cbopaque);
+        goto cleanup;
+
+#ifdef VIR_ENUM_SENTINELS
+    case VIR_NETWORK_EVENT_ID_LAST:
+        break;
+#endif
+    }
+    VIR_WARN("Unexpected event ID %d", event->eventID);
+
+cleanup:
+    virNetworkFree(net);
+}
+
 
 static int virObjectEventDispatchMatchCallback(virObjectEventPtr event,
                                                virObjectEventCallbackPtr cb)
@@ -761,6 +797,10 @@ virObjectEventStateDispatchFunc(virConnectPtr conn,
             virDomainEventDispatchDefaultFunc(conn, event,
                     VIR_DOMAIN_EVENT_CALLBACK(cb), cbopaque, NULL);
             break;
+        case VIR_EVENT_NAMESPACE_NETWORK:
+            virNetworkEventDispatchDefaultFunc(conn, event,
+                    VIR_NETWORK_EVENT_CALLBACK(cb), cbopaque, NULL);
+            break;
     }
     virObjectEventStateLock(state);
 }
@@ -833,7 +873,7 @@ virObjectEventStateRegisterID(virConnectPtr conn,
                                            state,
                                            NULL)) < 0) {
         virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
-                       _("could not initialize domain event timer"));
+                       _("could not initialize object event timer"));
         goto cleanup;
     }
 
@@ -916,3 +956,53 @@ virObjectEventStateEventID(virConnectPtr conn,
     virObjectEventStateUnlock(state);
     return ret;
 }
+
+
+/**
+ * virNetworkEventStateRegisterID:
+ * @conn: connection to associate with callback
+ * @state: object event state
+ * @net: network to filter on or NULL for all networks
+ * @eventID: ID of the event type to register for
+ * @cb: function to add to event
+ * @opaque: data blob to pass to callback
+ * @freecb: callback to free @opaque
+ * @callbackID: filled with callback ID
+ *
+ * Register the function @callbackID with connection @conn,
+ * from @state, for events of type @eventID.
+ *
+ * Returns: the number of callbacks now registered, or -1 on error
+ */
+int
+virNetworkEventStateRegisterID(virConnectPtr conn,
+                              virObjectEventStatePtr state,
+                              virNetworkPtr net,
+                              int eventID,
+                              virConnectObjectEventGenericCallback cb,
+                              void *opaque,
+                              virFreeCallback freecb,
+                              int *callbackID)
+{
+    if (net)
+        return virObjectEventStateRegisterID(conn, state,
+                                             net->uuid, net->name, 0, eventID,
+                                             cb, opaque, freecb, callbackID);
+    else
+        return virObjectEventStateRegisterID(conn, state,
+                                             NULL, NULL, 0, eventID,
+                                             cb, opaque, freecb, callbackID);
+}
+
+virObjectEventPtr virNetworkEventLifecycleNew(const char *name, const unsigned char *uuid, int type)
+{
+    int eventId = (VIR_EVENT_NAMESPACE_NETWORK << 8) +
+                  VIR_NETWORK_EVENT_ID_LIFECYCLE;
+    virObjectEventPtr event = virObjectEventNewInternal(eventId, 0, name, uuid);
+
+    if (event) {
+        event->data.networkData.lifecycle.type = type;
+    }
+
+    return event;
+}
diff --git a/src/conf/object_event.h b/src/conf/object_event.h
index bf5727f..886dab7 100644
--- a/src/conf/object_event.h
+++ b/src/conf/object_event.h
@@ -64,6 +64,8 @@ typedef void (*virConnectObjectEventGenericCallback)(virConnectPtr conn,
                                                      void *obj,
                                                      void *opaque);
 
+#define VIR_OBJECT_EVENT_CALLBACK(cb) ((virConnectObjectEventGenericCallback)(cb))
+
 void
 virObjectEventStateQueue(virObjectEventStatePtr state,
                          virObjectEventPtr event)
@@ -90,4 +92,23 @@ virObjectEventStateEventID(virConnectPtr conn,
                            int callbackID)
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
 
+/*
+ * Network events
+ */
+
+int
+virNetworkEventStateRegisterID(virConnectPtr conn,
+                              virObjectEventStatePtr state,
+                              virNetworkPtr net,
+                              int eventID,
+                              virConnectObjectEventGenericCallback cb,
+                              void *opaque,
+                              virFreeCallback freecb,
+                              int *callbackID);
+
+virObjectEventPtr
+virNetworkEventLifecycleNew(const char *name,
+                            const unsigned char *uuid,
+                            int type);
+
 #endif
diff --git a/src/conf/object_event_private.h b/src/conf/object_event_private.h
index 8089155..684f800 100644
--- a/src/conf/object_event_private.h
+++ b/src/conf/object_event_private.h
@@ -70,6 +70,12 @@ struct _virObjectEventCallback {
     int deleted;
 };
 
+union virNetworkEventData {
+    struct {
+        int type;
+    } lifecycle;
+};
+
 union virDomainEventData {
     struct {
         int type;
@@ -125,6 +131,7 @@ struct _virObjectEvent {
 
     union {
         union virDomainEventData domainData;
+        union virNetworkEventData networkData;
     } data;
 };
 
diff --git a/src/driver.h b/src/driver.h
index 8cd164a..3c7b536 100644
--- a/src/driver.h
+++ b/src/driver.h
@@ -1366,6 +1366,18 @@ typedef int
                                 virNetworkPtr **nets,
                                 unsigned int flags);
 
+typedef int
+(*virDrvConnectNetworkEventRegisterAny)(virConnectPtr conn,
+                                        virNetworkPtr dom,
+                                        int eventID,
+                                        virConnectNetworkEventGenericCallback cb,
+                                        void *opaque,
+                                        virFreeCallback freecb);
+
+typedef int
+(*virDrvConnectNetworkEventDeregisterAny)(virConnectPtr conn,
+                                         int callbackID);
+
 typedef virNetworkPtr
 (*virDrvNetworkLookupByUUID)(virConnectPtr conn,
                              const unsigned char *uuid);
@@ -1444,6 +1456,8 @@ struct _virNetworkDriver {
     virDrvConnectNumOfDefinedNetworks connectNumOfDefinedNetworks;
     virDrvConnectListDefinedNetworks connectListDefinedNetworks;
     virDrvConnectListAllNetworks connectListAllNetworks;
+    virDrvConnectNetworkEventRegisterAny connectNetworkEventRegisterAny;
+    virDrvConnectNetworkEventDeregisterAny connectNetworkEventDeregisterAny;
     virDrvNetworkLookupByUUID networkLookupByUUID;
     virDrvNetworkLookupByName networkLookupByName;
     virDrvNetworkCreateXML networkCreateXML;
diff --git a/src/libvirt.c b/src/libvirt.c
index ae05300..b2e4edf 100644
--- a/src/libvirt.c
+++ b/src/libvirt.c
@@ -19171,6 +19171,139 @@ error:
 }
 
 /**
+ * virConnectNetworkEventRegisterAny:
+ * @conn: pointer to the connection
+ * @net: pointer to the network
+ * @eventID: the event type to receive
+ * @cb: callback to the function handling network events
+ * @opaque: opaque data to pass on to the callback
+ * @freecb: optional function to deallocate opaque when not used anymore
+ *
+ * Adds a callback to receive notifications of arbitrary network events
+ * occurring on a network.
+ *
+ * If net is NULL, then events will be monitored for any network. If net
+ * is non-NULL, then only the specific network will be monitored
+ *
+ * Most types of event have a callback providing a custom set of parameters
+ * for the event. When registering an event, it is thus necessary to use
+ * the VIR_NETWORK_EVENT_CALLBACK() macro to cast the supplied function pointer
+ * to match the signature of this method.
+ *
+ * The virNetworkPtr object handle passed into the callback upon delivery
+ * of an event is only valid for the duration of execution of the callback.
+ * If the callback wishes to keep the network object after the callback
+ * returns, it shall take a reference to it, by calling virNetworkRef.
+ * The reference can be released once the object is no longer required
+ * by calling virNetworkFree.
+ *
+ * The return value from this method is a positive integer identifier
+ * for the callback. To unregister a callback, this callback ID should
+ * be passed to the virNetworkEventUnregisterAny method
+ *
+ * Returns a callback identifier on success, -1 on failure
+ */
+int
+virConnectNetworkEventRegisterAny(virConnectPtr conn,
+                                  virNetworkPtr net,
+                                  int eventID,
+                                  virConnectNetworkEventGenericCallback cb,
+                                  void *opaque,
+                                  virFreeCallback freecb)
+{
+    VIR_DEBUG("conn=%p, eventID=%d, cb=%p, opaque=%p, freecb=%p",
+              conn, eventID, cb, opaque, freecb);
+
+    virResetLastError();
+
+    if (!VIR_IS_CONNECT(conn)) {
+        virLibConnError(VIR_ERR_INVALID_CONN, __FUNCTION__);
+        virDispatchError(NULL);
+        return -1;
+    }
+    if (net != NULL &&
+        !(VIR_IS_CONNECTED_NETWORK(net) && net->conn == conn)) {
+        virLibConnError(VIR_ERR_INVALID_CONN, __FUNCTION__);
+        virDispatchError(conn);
+        return -1;
+    }
+    virCheckNonNullArgGoto(cb, error);
+    virCheckNonNegativeArgGoto(eventID, error);
+    if (((eventID & 0xFF00) >> 8) != VIR_EVENT_NAMESPACE_NETWORK) {
+        virReportInvalidArg(eventID,
+                            _("eventID in %s must be have network namespace flags %d"),
+                            __FUNCTION__, VIR_EVENT_NAMESPACE_NETWORK << 8);
+        goto error;
+    }
+
+#ifdef VIR_ENUM_SENTINELS
+    if ((eventID & 0xFF) >= VIR_NETWORK_EVENT_ID_LAST) {
+        virReportInvalidArg(eventID,
+                            _("eventID in %s must be less than %d"),
+                            __FUNCTION__, VIR_NETWORK_EVENT_ID_LAST);
+        goto error;
+    }
+#endif
+
+    if ((conn->networkDriver) && (conn->networkDriver->connectNetworkEventRegisterAny)) {
+        int ret;
+        ret = conn->networkDriver->connectNetworkEventRegisterAny(conn, net,
+                                                                  eventID, cb,
+                                                                  opaque,
+                                                                  freecb);
+        if (ret < 0)
+            goto error;
+        return ret;
+    }
+
+    virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
+error:
+    virDispatchError(conn);
+    return -1;
+}
+
+/**
+ * virConnectNetworkEventDeregisterAny:
+ * @conn: pointer to the connection
+ * @callbackID: the callback identifier
+ *
+ * Removes an event callback. The callbackID parameter should be the
+ * vaule obtained from a previous virNetworkEventRegisterAny method.
+ *
+ * Returns 0 on success, -1 on failure
+ */
+int
+virConnectNetworkEventDeregisterAny(virConnectPtr conn,
+                                    int callbackID)
+{
+    VIR_DEBUG("conn=%p, callbackID=%d", conn, callbackID);
+
+    virResetLastError();
+
+    if (!VIR_IS_CONNECT(conn)) {
+        virLibConnError(VIR_ERR_INVALID_CONN, __FUNCTION__);
+        virDispatchError(NULL);
+        return -1;
+    }
+    virCheckNonNegativeArgGoto(callbackID, error);
+
+    if ((conn->networkDriver) &&
+            (conn->networkDriver->connectNetworkEventDeregisterAny)) {
+        int ret;
+        ret = conn->networkDriver->connectNetworkEventDeregisterAny(conn,
+                                                                    callbackID);
+        if (ret < 0)
+            goto error;
+        return ret;
+    }
+
+    virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
+error:
+    virDispatchError(conn);
+    return -1;
+}
+
+/**
  * virDomainManagedSave:
  * @dom: pointer to the domain
  * @flags: bitwise-OR of virDomainSaveRestoreFlags
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index dc3882b..9989d93 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -619,7 +619,10 @@ virNWFilterVarValueGetCardinality;
 virNWFilterVarValueGetNthValue;
 virNWFilterVarValueGetSimple;
 
+
 # conf/object_event.h
+virNetworkEventLifecycleNew;
+virNetworkEventStateRegisterID;
 virObjectEventFree;
 virObjectEventStateDeregisterID;
 virObjectEventStateEventID;
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index fe9b497..d940b2b 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -639,4 +639,10 @@ LIBVIRT_1.1.3 {
         virConnectGetCPUModelNames;
 } LIBVIRT_1.1.1;
 
+LIBVIRT_1.1.4 {
+    global:
+        virConnectNetworkEventRegisterAny;
+        virConnectNetworkEventDeregisterAny;
+} LIBVIRT_1.1.3;
+
 # .... define new API here using predicted next version number ....
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
index 4298576..de846ab 100644
--- a/src/network/bridge_driver.c
+++ b/src/network/bridge_driver.c
@@ -438,6 +438,8 @@ networkStateInitialize(bool privileged,
     networkReloadFirewallRules(driverState);
     networkRefreshDaemons(driverState);
 
+    driverState->networkEventState = virObjectEventStateNew();
+
     networkDriverUnlock(driverState);
 
 #ifdef HAVE_FIREWALLD
@@ -532,6 +534,8 @@ networkStateCleanup(void) {
 
     networkDriverLock(driverState);
 
+    virObjectEventStateFree(driverState->networkEventState);
+
     /* free inactive networks */
     virNetworkObjListFree(&driverState->networks);
 
@@ -2290,6 +2294,55 @@ cleanup:
     return ret;
 }
 
+static int
+networkConnectNetworkEventRegisterAny(virConnectPtr conn,
+                                      virNetworkPtr net,
+                                      int eventID,
+                                      virConnectNetworkEventGenericCallback callback,
+                                      void *opaque,
+                                      virFreeCallback freecb)
+{
+    virNetworkDriverStatePtr driver = conn->networkPrivateData;
+    int ret = -1;
+
+    networkDriverLock(driver);
+
+    if (virConnectNetworkEventRegisterAnyEnsureACL(conn) < 0)
+        goto cleanup;
+
+    if (virNetworkEventStateRegisterID(conn, driver->networkEventState,
+                                       net, eventID,
+                                       VIR_OBJECT_EVENT_CALLBACK(callback),
+                                       opaque, freecb, &ret) < 0)
+        ret = -1;
+
+    networkDriverUnlock(driver);
+
+cleanup:
+    return ret;
+}
+
+static int
+networkConnectNetworkEventDeregisterAny(virConnectPtr conn,
+                                        int callbackID)
+{
+    virNetworkDriverStatePtr driver = conn->networkPrivateData;
+    int ret = -1;
+
+    if (virConnectNetworkEventDeregisterAnyEnsureACL(conn) < 0)
+        goto cleanup;
+
+
+    networkDriverLock(driver);
+    ret = virObjectEventStateDeregisterID(conn,
+                                          driver->networkEventState,
+                                          callbackID);
+    networkDriverUnlock(driver);
+
+cleanup:
+    return ret;
+}
+
 static int networkIsActive(virNetworkPtr net)
 {
     virNetworkObjPtr obj;
@@ -2483,6 +2536,7 @@ static virNetworkPtr networkCreateXML(virConnectPtr conn, const char *xml) {
     virNetworkDefPtr def;
     virNetworkObjPtr network = NULL;
     virNetworkPtr ret = NULL;
+    virObjectEventPtr event = NULL;
 
     networkDriverLock(driver);
 
@@ -2509,11 +2563,17 @@ static virNetworkPtr networkCreateXML(virConnectPtr conn, const char *xml) {
         goto cleanup;
     }
 
+    event = virNetworkEventLifecycleNew(network->def->name,
+                                        network->def->uuid,
+                                        VIR_NETWORK_EVENT_STARTED);
+
     VIR_INFO("Creating network '%s'", network->def->name);
     ret = virGetNetwork(conn, network->def->name, network->def->uuid);
 
 cleanup:
     virNetworkDefFree(def);
+    if (event)
+        virObjectEventStateQueue(driver->networkEventState, event);
     if (network)
         virNetworkObjUnlock(network);
     networkDriverUnlock(driver);
@@ -2526,6 +2586,7 @@ static virNetworkPtr networkDefineXML(virConnectPtr conn, const char *xml) {
     bool freeDef = true;
     virNetworkObjPtr network = NULL;
     virNetworkPtr ret = NULL;
+    virObjectEventPtr event = NULL;
 
     networkDriverLock(driver);
 
@@ -2565,12 +2626,17 @@ static virNetworkPtr networkDefineXML(virConnectPtr conn, const char *xml) {
         goto cleanup;
     }
 
+    event = virNetworkEventLifecycleNew(def->name, def->uuid,
+                                        VIR_NETWORK_EVENT_DEFINED);
+
     VIR_INFO("Defining network '%s'", def->name);
     ret = virGetNetwork(conn, def->name, def->uuid);
 
 cleanup:
+    if (event)
+        virObjectEventStateQueue(driver->networkEventState, event);
     if (freeDef)
-       virNetworkDefFree(def);
+        virNetworkDefFree(def);
     if (network)
         virNetworkObjUnlock(network);
     networkDriverUnlock(driver);
@@ -2583,6 +2649,7 @@ networkUndefine(virNetworkPtr net) {
     virNetworkObjPtr network;
     int ret = -1;
     bool active = false;
+    virObjectEventPtr event = NULL;
 
     networkDriverLock(driver);
 
@@ -2610,6 +2677,10 @@ networkUndefine(virNetworkPtr net) {
     virNetworkDefFree(network->newDef);
     network->newDef = NULL;
 
+    event = virNetworkEventLifecycleNew(network->def->name,
+                                        network->def->uuid,
+                                        VIR_NETWORK_EVENT_UNDEFINED);
+
     VIR_INFO("Undefining network '%s'", network->def->name);
     if (!active) {
         if (networkRemoveInactive(driver, network) < 0) {
@@ -2622,6 +2693,8 @@ networkUndefine(virNetworkPtr net) {
     ret = 0;
 
 cleanup:
+    if (event)
+        virObjectEventStateQueue(driver->networkEventState, event);
     if (network)
         virNetworkObjUnlock(network);
     networkDriverUnlock(driver);
@@ -2785,6 +2858,7 @@ static int networkCreate(virNetworkPtr net) {
     virNetworkDriverStatePtr driver = net->conn->networkPrivateData;
     virNetworkObjPtr network;
     int ret = -1;
+    virObjectEventPtr event = NULL;
 
     networkDriverLock(driver);
     network = virNetworkFindByUUID(&driver->networks, net->uuid);
@@ -2800,7 +2874,13 @@ static int networkCreate(virNetworkPtr net) {
 
     ret = networkStartNetwork(driver, network);
 
+    event = virNetworkEventLifecycleNew(network->def->name,
+                                        network->def->uuid,
+                                        VIR_NETWORK_EVENT_STARTED);
+
 cleanup:
+    if (event)
+        virObjectEventStateQueue(driver->networkEventState, event);
     if (network)
         virNetworkObjUnlock(network);
     networkDriverUnlock(driver);
@@ -2811,6 +2891,7 @@ static int networkDestroy(virNetworkPtr net) {
     virNetworkDriverStatePtr driver = net->conn->networkPrivateData;
     virNetworkObjPtr network;
     int ret = -1;
+    virObjectEventPtr event = NULL;
 
     networkDriverLock(driver);
     network = virNetworkFindByUUID(&driver->networks, net->uuid);
@@ -2833,6 +2914,10 @@ static int networkDestroy(virNetworkPtr net) {
     if ((ret = networkShutdownNetwork(driver, network)) < 0)
         goto cleanup;
 
+    event = virNetworkEventLifecycleNew(network->def->name,
+                                        network->def->uuid,
+                                        VIR_NETWORK_EVENT_STOPPED);
+
     if (!network->persistent) {
         if (networkRemoveInactive(driver, network) < 0) {
             network = NULL;
@@ -2843,6 +2928,8 @@ static int networkDestroy(virNetworkPtr net) {
     }
 
 cleanup:
+    if (event)
+        virObjectEventStateQueue(driver->networkEventState, event);
     if (network)
         virNetworkObjUnlock(network);
     networkDriverUnlock(driver);
@@ -3001,6 +3088,8 @@ static virNetworkDriver networkDriver = {
     .connectNumOfDefinedNetworks = networkConnectNumOfDefinedNetworks, /* 0.2.0 */
     .connectListDefinedNetworks = networkConnectListDefinedNetworks, /* 0.2.0 */
     .connectListAllNetworks = networkConnectListAllNetworks, /* 0.10.2 */
+    .connectNetworkEventRegisterAny = networkConnectNetworkEventRegisterAny, /* 1.1.4 */
+    .connectNetworkEventDeregisterAny = networkConnectNetworkEventDeregisterAny, /* 1.1.4 */
     .networkLookupByUUID = networkLookupByUUID, /* 0.2.0 */
     .networkLookupByName = networkLookupByName, /* 0.2.0 */
     .networkCreateXML = networkCreateXML, /* 0.2.0 */
diff --git a/src/network/bridge_driver_platform.h b/src/network/bridge_driver_platform.h
index 289ab79..82d96f6 100644
--- a/src/network/bridge_driver_platform.h
+++ b/src/network/bridge_driver_platform.h
@@ -29,6 +29,7 @@
 # include "virthread.h"
 # include "virdnsmasq.h"
 # include "network_conf.h"
+# include "object_event.h"
 
 /* Main driver state */
 struct _virNetworkDriverState {
@@ -43,6 +44,8 @@ struct _virNetworkDriverState {
     char *dnsmasqStateDir;
     char *radvdStateDir;
     dnsmasqCapsPtr dnsmasqCaps;
+
+    virObjectEventStatePtr networkEventState;
 };
 
 typedef struct _virNetworkDriverState virNetworkDriverState;
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 0074f46..dfeac4f 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -272,6 +272,10 @@ static void
 remoteDomainBuildEventDeviceRemoved(virNetClientProgramPtr prog,
                                     virNetClientPtr client,
                                     void *evdata, void *opaque);
+static void
+remoteNetworkBuildEventLifecycle(virNetClientProgramPtr prog ATTRIBUTE_UNUSED,
+                                 virNetClientPtr client ATTRIBUTE_UNUSED,
+                                 void *evdata, void *opaque);
 
 static virNetClientProgramEvent remoteDomainEvents[] = {
     { REMOTE_PROC_DOMAIN_EVENT_RTC_CHANGE,
@@ -338,6 +342,10 @@ static virNetClientProgramEvent remoteDomainEvents[] = {
       remoteDomainBuildEventDeviceRemoved,
       sizeof(remote_domain_event_device_removed_msg),
       (xdrproc_t)xdr_remote_domain_event_device_removed_msg },
+    { REMOTE_PROC_NETWORK_EVENT_LIFECYCLE,
+      remoteNetworkBuildEventLifecycle,
+      sizeof(remote_network_event_lifecycle_msg),
+      (xdrproc_t)xdr_remote_network_event_lifecycle_msg },
 };
 
 enum virDrvOpenRemoteFlags {
@@ -2898,6 +2906,99 @@ done:
 }
 
 static int
+remoteConnectNetworkEventRegisterAny(virConnectPtr conn,
+                                     virNetworkPtr net,
+                                     int eventID,
+                                     virConnectNetworkEventGenericCallback callback,
+                                     void *opaque,
+                                     virFreeCallback freecb)
+{
+    int rv = -1;
+    struct private_data *priv = conn->privateData;
+    remote_connect_network_event_register_any_args args;
+    int callbackID;
+    int count;
+
+    remoteDriverLock(priv);
+
+    if ((count = virNetworkEventStateRegisterID(conn,
+                                               priv->domainEventState,
+                                               net, eventID,
+                                               VIR_OBJECT_EVENT_CALLBACK(callback),
+                                               opaque, freecb,
+                                               &callbackID)) < 0) {
+        virReportError(VIR_ERR_RPC, "%s", _("adding cb to list"));
+        goto done;
+    }
+
+    /* If this is the first callback for this eventID, we need to enable
+     * events on the server */
+    if (count == 1) {
+        args.eventID = eventID;
+
+        if (call(conn, priv, 0, REMOTE_PROC_CONNECT_NETWORK_EVENT_REGISTER_ANY,
+                 (xdrproc_t) xdr_remote_connect_network_event_register_any_args, (char *) &args,
+                 (xdrproc_t) xdr_void, (char *)NULL) == -1) {
+            virObjectEventStateDeregisterID(conn,
+                                            priv->domainEventState,
+                                            callbackID);
+            goto done;
+        }
+    }
+
+    rv = callbackID;
+
+done:
+    remoteDriverUnlock(priv);
+    return rv;
+}
+
+
+static int
+remoteConnectNetworkEventDeregisterAny(virConnectPtr conn,
+                                       int callbackID)
+{
+    struct private_data *priv = conn->privateData;
+    int rv = -1;
+    remote_connect_network_event_deregister_any_args args;
+    int eventID;
+    int count;
+
+    remoteDriverLock(priv);
+
+    if ((eventID = virObjectEventStateEventID(conn,
+                                              priv->domainEventState,
+                                              callbackID)) < 0) {
+        virReportError(VIR_ERR_RPC, _("unable to find callback ID %d"), callbackID);
+        goto done;
+    }
+
+    if ((count = virObjectEventStateDeregisterID(conn,
+                                                 priv->domainEventState,
+                                                 callbackID)) < 0) {
+        virReportError(VIR_ERR_RPC, _("unable to find callback ID %d"), callbackID);
+        goto done;
+    }
+
+    /* If that was the last callback for this eventID, we need to disable
+     * events on the server */
+    if (count == 0) {
+        args.eventID = callbackID;
+
+        if (call(conn, priv, 0, REMOTE_PROC_CONNECT_NETWORK_EVENT_DEREGISTER_ANY,
+                 (xdrproc_t) xdr_remote_connect_network_event_deregister_any_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,
                                unsigned int flags)
@@ -4788,6 +4889,26 @@ remoteDomainBuildEventDeviceRemoved(virNetClientProgramPtr prog ATTRIBUTE_UNUSED
     remoteDomainEventQueue(priv, event);
 }
 
+static void
+remoteNetworkBuildEventLifecycle(virNetClientProgramPtr prog ATTRIBUTE_UNUSED,
+                                 virNetClientPtr client ATTRIBUTE_UNUSED,
+                                 void *evdata, void *opaque)
+{
+    virConnectPtr conn = opaque;
+    struct private_data *priv = conn->privateData;
+    remote_network_event_lifecycle_msg *msg = evdata;
+    virNetworkPtr net;
+    virObjectEventPtr event = NULL;
+
+    net = get_nonnull_network(conn, msg->net);
+    if (!net)
+        return;
+
+    event = virNetworkEventLifecycleNew(net->name, net->uuid, msg->event);
+    virNetworkFree(net);
+
+    remoteDomainEventQueue(priv, event);
+}
 
 static virDrvOpenStatus ATTRIBUTE_NONNULL(1)
 remoteSecretOpen(virConnectPtr conn, virConnectAuthPtr auth,
@@ -7024,6 +7145,8 @@ static virNetworkDriver network_driver = {
     .connectNumOfDefinedNetworks = remoteConnectNumOfDefinedNetworks, /* 0.3.0 */
     .connectListDefinedNetworks = remoteConnectListDefinedNetworks, /* 0.3.0 */
     .connectListAllNetworks = remoteConnectListAllNetworks, /* 0.10.2 */
+    .connectNetworkEventDeregisterAny = remoteConnectNetworkEventDeregisterAny, /* 1.1.4 */
+    .connectNetworkEventRegisterAny = remoteConnectNetworkEventRegisterAny, /* 1.1.4 */
     .networkLookupByUUID = remoteNetworkLookupByUUID, /* 0.3.0 */
     .networkLookupByName = remoteNetworkLookupByName, /* 0.3.0 */
     .networkCreateXML = remoteNetworkCreateXML, /* 0.3.0 */
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index f942670..c3d544f 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -2849,6 +2849,30 @@ struct remote_connect_get_cpu_model_names_ret {
     int ret;
 };
 
+struct remote_connect_network_event_register_any_args {
+    int eventID;
+};
+
+struct remote_connect_network_event_register_any_ret {
+    int cb_registered;
+};
+
+struct remote_connect_network_event_deregister_any_args {
+    int eventID;
+};
+
+struct remote_connect_network_event_deregister_any_ret {
+    int cb_registered;
+};
+
+struct remote_network_event_lifecycle_msg {
+    remote_nonnull_network net;
+    int event;
+    int detail;
+};
+
+
+
 /*----- Protocol. -----*/
 
 /* Define the program number, protocol version and procedure numbers here. */
@@ -5018,5 +5042,25 @@ enum remote_procedure {
      * @generate: none
      * @acl: connect:read
      */
-    REMOTE_PROC_CONNECT_GET_CPU_MODEL_NAMES = 312
+    REMOTE_PROC_CONNECT_GET_CPU_MODEL_NAMES = 312,
+
+    /**
+     * @generate: none
+     * @priority: high
+     * @acl: connect:read
+     */
+    REMOTE_PROC_CONNECT_NETWORK_EVENT_REGISTER_ANY = 313,
+
+    /**
+     * @generate: none
+     * @priority: high
+     * @acl: connect:read
+     */
+    REMOTE_PROC_CONNECT_NETWORK_EVENT_DEREGISTER_ANY = 314,
+
+    /**
+     * @generate: both
+     * @acl: none
+     */
+    REMOTE_PROC_NETWORK_EVENT_LIFECYCLE = 315
 };
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index f472494..4419bb3 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -121,7 +121,7 @@ static const virNodeInfo defaultNodeInfo = {
 
 
 static int testConnectClose(virConnectPtr conn);
-static void testDomainEventQueue(testConnPtr driver,
+static void testObjectEventQueue(testConnPtr driver,
                                  virObjectEventPtr event);
 
 
@@ -1650,7 +1650,7 @@ cleanup:
     if (dom)
         virObjectUnlock(dom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     virDomainDefFree(def);
     testDriverUnlock(privconn);
     return ret;
@@ -1781,7 +1781,7 @@ cleanup:
     if (privdom)
         virObjectUnlock(privdom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -1821,7 +1821,7 @@ cleanup:
         virObjectUnlock(privdom);
     if (event) {
         testDriverLock(privconn);
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
         testDriverUnlock(privconn);
     }
     return ret;
@@ -1864,7 +1864,7 @@ cleanup:
 
     if (event) {
         testDriverLock(privconn);
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
         testDriverUnlock(privconn);
     }
     return ret;
@@ -1911,7 +1911,7 @@ cleanup:
     if (privdom)
         virObjectUnlock(privdom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -1987,7 +1987,7 @@ cleanup:
     if (privdom)
         virObjectUnlock(privdom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -2159,7 +2159,7 @@ cleanup:
     if (privdom)
         virObjectUnlock(privdom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -2265,7 +2265,7 @@ cleanup:
     if (dom)
         virObjectUnlock(dom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -2335,7 +2335,7 @@ cleanup:
     if (privdom)
         virObjectUnlock(privdom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -2819,7 +2819,7 @@ cleanup:
     if (dom)
         virObjectUnlock(dom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -2955,7 +2955,7 @@ cleanup:
     if (privdom)
         virObjectUnlock(privdom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -3030,7 +3030,7 @@ cleanup:
     if (privdom)
         virObjectUnlock(privdom);
     if (event)
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
     testDriverUnlock(privconn);
     return ret;
 }
@@ -3529,6 +3529,7 @@ static virNetworkPtr testNetworkCreateXML(virConnectPtr conn, const char *xml) {
     virNetworkDefPtr def;
     virNetworkObjPtr net = NULL;
     virNetworkPtr ret = NULL;
+    virObjectEventPtr event = NULL;
 
     testDriverLock(privconn);
     if ((def = virNetworkDefParseString(xml)) == NULL)
@@ -3539,10 +3540,15 @@ static virNetworkPtr testNetworkCreateXML(virConnectPtr conn, const char *xml) {
     def = NULL;
     net->active = 1;
 
+    event = virNetworkEventLifecycleNew(net->def->name, net->def->uuid,
+                                        VIR_NETWORK_EVENT_STARTED);
+
     ret = virGetNetwork(conn, net->def->name, net->def->uuid);
 
 cleanup:
     virNetworkDefFree(def);
+    if (event)
+        testObjectEventQueue(privconn, event);
     if (net)
         virNetworkObjUnlock(net);
     testDriverUnlock(privconn);
@@ -3556,6 +3562,7 @@ virNetworkPtr testNetworkDefineXML(virConnectPtr conn, const char *xml)
     virNetworkDefPtr def;
     virNetworkObjPtr net = NULL;
     virNetworkPtr ret = NULL;
+    virObjectEventPtr event = NULL;
 
     testDriverLock(privconn);
     if ((def = virNetworkDefParseString(xml)) == NULL)
@@ -3566,10 +3573,15 @@ virNetworkPtr testNetworkDefineXML(virConnectPtr conn, const char *xml)
     def = NULL;
     net->persistent = 1;
 
+    event = virNetworkEventLifecycleNew(net->def->name, net->def->uuid,
+                                        VIR_NETWORK_EVENT_DEFINED);
+
     ret = virGetNetwork(conn, net->def->name, net->def->uuid);
 
 cleanup:
     virNetworkDefFree(def);
+    if (event)
+        testObjectEventQueue(privconn, event);
     if (net)
         virNetworkObjUnlock(net);
     testDriverUnlock(privconn);
@@ -3580,6 +3592,7 @@ static int testNetworkUndefine(virNetworkPtr network) {
     testConnPtr privconn = network->conn->privateData;
     virNetworkObjPtr privnet;
     int ret = -1;
+    virObjectEventPtr event = NULL;
 
     testDriverLock(privconn);
     privnet = virNetworkFindByName(&privconn->networks,
@@ -3596,12 +3609,18 @@ static int testNetworkUndefine(virNetworkPtr network) {
         goto cleanup;
     }
 
+    event = virNetworkEventLifecycleNew(network->name, network->uuid,
+                                        VIR_NETWORK_EVENT_UNDEFINED);
+
     virNetworkRemoveInactive(&privconn->networks,
                              privnet);
+
     privnet = NULL;
     ret = 0;
 
 cleanup:
+    if (event)
+        testObjectEventQueue(privconn, event);
     if (privnet)
         virNetworkObjUnlock(privnet);
     testDriverUnlock(privconn);
@@ -3658,7 +3677,8 @@ cleanup:
 
 static int testNetworkCreate(virNetworkPtr network) {
     testConnPtr privconn = network->conn->privateData;
-    virNetworkObjPtr privnet;
+    virNetworkObjPtr privnet = NULL;
+    virObjectEventPtr event = NULL;
     int ret = -1;
 
     testDriverLock(privconn);
@@ -3678,9 +3698,13 @@ static int testNetworkCreate(virNetworkPtr network) {
     }
 
     privnet->active = 1;
+    event = virNetworkEventLifecycleNew(privnet->def->name, privnet->def->uuid,
+                                        VIR_NETWORK_EVENT_STARTED);
     ret = 0;
 
 cleanup:
+    if (event)
+        testObjectEventQueue(privconn, event);
     if (privnet)
         virNetworkObjUnlock(privnet);
     return ret;
@@ -3690,6 +3714,7 @@ static int testNetworkDestroy(virNetworkPtr network) {
     testConnPtr privconn = network->conn->privateData;
     virNetworkObjPtr privnet;
     int ret = -1;
+    virObjectEventPtr event = NULL;
 
     testDriverLock(privconn);
     privnet = virNetworkFindByName(&privconn->networks,
@@ -3701,6 +3726,8 @@ static int testNetworkDestroy(virNetworkPtr network) {
     }
 
     privnet->active = 0;
+    event = virNetworkEventLifecycleNew(privnet->def->name, privnet->def->uuid,
+                                        VIR_NETWORK_EVENT_STOPPED);
     if (!privnet->persistent) {
         virNetworkRemoveInactive(&privconn->networks,
                                  privnet);
@@ -3709,6 +3736,8 @@ static int testNetworkDestroy(virNetworkPtr network) {
     ret = 0;
 
 cleanup:
+    if (event)
+        testObjectEventQueue(privconn, event);
     if (privnet)
         virNetworkObjUnlock(privnet);
     testDriverUnlock(privconn);
@@ -6027,8 +6056,51 @@ testConnectDomainEventDeregisterAny(virConnectPtr conn,
 }
 
 
+static int
+testConnectNetworkEventRegisterAny(virConnectPtr conn,
+                                   virNetworkPtr net,
+                                   int eventID,
+                                   virConnectNetworkEventGenericCallback callback,
+                                   void *opaque,
+                                   virFreeCallback freecb)
+{
+    testConnPtr driver = conn->privateData;
+    int ret;
+
+    testDriverLock(driver);
+    if (net)
+    {
+        if (virObjectEventStateRegisterID(conn,
+                                          driver->domainEventState,
+                                          net->uuid, net->name, 0, eventID,
+                                          VIR_OBJECT_EVENT_CALLBACK(callback),
+                                          opaque, freecb, &ret) < 0)
+            ret = -1;
+    }
+    else
+    {
+        if (virObjectEventStateRegisterID(conn,
+                                          driver->domainEventState,
+                                          NULL, NULL, 0, eventID,
+                                          VIR_OBJECT_EVENT_CALLBACK(callback),
+                                          opaque, freecb, &ret) < 0)
+            ret = -1;
+    }
+    testDriverUnlock(driver);
+
+    return ret;
+}
+
+static int
+testConnectNetworkEventDeregisterAny(virConnectPtr conn,
+                                    int callbackID)
+{
+    return testConnectDomainEventDeregisterAny(conn, callbackID);
+}
+
+
 /* driver must be locked before calling */
-static void testDomainEventQueue(testConnPtr driver,
+static void testObjectEventQueue(testConnPtr driver,
                                  virObjectEventPtr event)
 {
     virObjectEventStateQueue(driver->domainEventState, event);
@@ -6191,7 +6263,7 @@ cleanup:
         virObjectUnlock(vm);
     if (event) {
         testDriverLock(privconn);
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
         testDriverUnlock(privconn);
     }
 
@@ -6743,7 +6815,7 @@ cleanup:
     }
     if (event) {
         testDriverLock(privconn);
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
         testDriverUnlock(privconn);
     }
     virDomainSnapshotDefFree(def);
@@ -6989,7 +7061,7 @@ testDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
                             VIR_DOMAIN_EVENT_STOPPED,
                             VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
                 if (event)
-                    testDomainEventQueue(privconn, event);
+                    testObjectEventQueue(privconn, event);
                 goto load;
             }
 
@@ -7069,7 +7141,7 @@ testDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
             bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0;
 
             if (event)
-                testDomainEventQueue(privconn, event);
+                testObjectEventQueue(privconn, event);
             event = virDomainEventNewFromObj(vm,
                             VIR_DOMAIN_EVENT_STARTED,
                             VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
@@ -7085,9 +7157,9 @@ testDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
     ret = 0;
 cleanup:
     if (event) {
-        testDomainEventQueue(privconn, event);
+        testObjectEventQueue(privconn, event);
         if (event2)
-            testDomainEventQueue(privconn, event2);
+            testObjectEventQueue(privconn, event2);
     } else {
         virObjectEventFree(event2);
     }
@@ -7205,6 +7277,8 @@ static virNetworkDriver testNetworkDriver = {
     .connectNumOfDefinedNetworks = testConnectNumOfDefinedNetworks, /* 0.3.2 */
     .connectListDefinedNetworks = testConnectListDefinedNetworks, /* 0.3.2 */
     .connectListAllNetworks = testConnectListAllNetworks, /* 0.10.2 */
+    .connectNetworkEventRegisterAny = testConnectNetworkEventRegisterAny, /* 1.1.4 */
+    .connectNetworkEventDeregisterAny = testConnectNetworkEventDeregisterAny, /* 1.1.4 */
     .networkLookupByUUID = testNetworkLookupByUUID, /* 0.3.2 */
     .networkLookupByName = testNetworkLookupByName, /* 0.3.2 */
     .networkCreateXML = testNetworkCreateXML, /* 0.3.2 */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e46d5f7..734ab50 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -300,6 +300,8 @@ EXTRA_DIST += 				\
 	$(NULL)
 endif ! WITH_LIBVIRTD
 
+test_programs += objecteventtest
+
 if WITH_SECDRIVER_APPARMOR
 test_scripts += virt-aa-helper-test
 else ! WITH_SECDRIVER_APPARMOR
@@ -888,6 +890,11 @@ fdstreamtest_SOURCES = \
 	fdstreamtest.c testutils.h testutils.c
 fdstreamtest_LDADD = $(LDADDS)
 
+objecteventtest_SOURCES = \
+	objecteventtest.c \
+	testutils.c testutils.h
+objecteventtest_LDADD = $(LDADDS)
+
 if WITH_LINUX
 fchosttest_SOURCES = \
        fchosttest.c testutils.h testutils.c
diff --git a/tests/objecteventtest.c b/tests/objecteventtest.c
new file mode 100644
index 0000000..af66155
--- /dev/null
+++ b/tests/objecteventtest.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2013 SUSE LINUX Products GmbH, Nuernberg, Germany.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library;  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cedric Bosdonnat <cbosdonnat at suse.com>
+ */
+
+#include <config.h>
+
+#include "testutils.h"
+
+#include "virerror.h"
+#include "virxml.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+
+static const char networkDef[] =
+"<network>\n"
+"  <name>test</name>\n"
+"  <bridge name=\"virbr0\"/>\n"
+"  <forward/>\n"
+"  <ip address=\"192.168.122.1\" netmask=\"255.255.255.0\">\n"
+"    <dhcp>\n"
+"      <range start=\"192.168.122.2\" end=\"192.168.122.254\"/>\n"
+"    </dhcp>\n"
+"  </ip>\n"
+"</network>\n";
+
+
+struct objecteventTest {
+    virConnectPtr conn;
+    virNetworkPtr net;
+};
+
+struct networkLifecycleEventCounter {
+    int startEvents;
+    int stopEvents;
+    int defineEvents;
+    int undefineEvents;
+};
+
+static void
+networkLifecycleCb(virConnectPtr conn ATTRIBUTE_UNUSED,
+                   virNetworkPtr net ATTRIBUTE_UNUSED,
+                   int event,
+                   void* opaque) {
+    struct networkLifecycleEventCounter *counter = opaque;
+
+    if (event == VIR_NETWORK_EVENT_STARTED)
+        counter->startEvents++;
+    else if (event == VIR_NETWORK_EVENT_STOPPED)
+        counter->stopEvents++;
+    else if (event == VIR_NETWORK_EVENT_DEFINED)
+        counter->defineEvents++;
+    else if (event == VIR_NETWORK_EVENT_UNDEFINED)
+        counter->undefineEvents++;
+}
+
+
+static int
+testNetworkCreateXML(const void *data)
+{
+    const struct objecteventTest *test = data;
+    struct networkLifecycleEventCounter counter;
+    int eventId = (VIR_EVENT_NAMESPACE_NETWORK << 8) + VIR_NETWORK_EVENT_ID_LIFECYCLE;
+    virNetworkPtr net;
+    int id;
+    int ret = 0;
+
+    counter.startEvents = 0;
+
+    id = virConnectNetworkEventRegisterAny(test->conn, NULL, eventId,
+                           VIR_NETWORK_EVENT_CALLBACK(&networkLifecycleCb),
+                           &counter, NULL);
+    net = virNetworkCreateXML(test->conn, networkDef);
+
+    if (virEventRunDefaultImpl() < 0) {
+        ret = -1;
+        goto cleanup;
+    }
+
+    if (counter.startEvents != 1) {
+        ret = -1;
+        goto cleanup;
+    }
+
+cleanup:
+    virConnectNetworkEventDeregisterAny(test->conn, id);
+    virNetworkDestroy(net);
+
+    virNetworkFree(net);
+
+    return ret;
+}
+
+static int
+testNetworkDefine(const void *data)
+{
+    const struct objecteventTest *test = data;
+    struct networkLifecycleEventCounter counter;
+    int eventId = (VIR_EVENT_NAMESPACE_NETWORK << 8) + VIR_NETWORK_EVENT_ID_LIFECYCLE;
+    virNetworkPtr net;
+    int id;
+    int ret = 0;
+
+    counter.defineEvents = 0;
+    counter.undefineEvents = 0;
+
+    id = virConnectNetworkEventRegisterAny(test->conn, NULL, eventId,
+                           VIR_NETWORK_EVENT_CALLBACK(&networkLifecycleCb),
+                           &counter, NULL);
+
+    /* Make sure the define event is triggered */
+    net = virNetworkDefineXML(test->conn, networkDef);
+
+    if (virEventRunDefaultImpl() < 0) {
+        ret = -1;
+        goto cleanup;
+    }
+
+    if (counter.defineEvents != 1) {
+        ret = -1;
+        goto cleanup;
+    }
+
+    /* Make sure the undefine event is triggered */
+    virNetworkUndefine(net);
+
+    if (virEventRunDefaultImpl() < 0) {
+        ret = -1;
+        goto cleanup;
+    }
+
+    if (counter.undefineEvents != 1) {
+        ret = -1;
+        goto cleanup;
+    }
+
+
+cleanup:
+    virConnectNetworkEventDeregisterAny(test->conn, id);
+    virNetworkFree(net);
+
+    return ret;
+}
+
+static int
+testNetworkStartStopEvent(const void *data)
+{
+    const struct objecteventTest *test = data;
+    struct networkLifecycleEventCounter counter;
+    int eventId = (VIR_EVENT_NAMESPACE_NETWORK << 8) + VIR_NETWORK_EVENT_ID_LIFECYCLE;
+    int id;
+    int ret = 0;
+
+    counter.startEvents = 0;
+
+    id = virConnectNetworkEventRegisterAny(test->conn, test->net, eventId,
+                           VIR_NETWORK_EVENT_CALLBACK(&networkLifecycleCb),
+                           &counter, NULL);
+    virNetworkCreate(test->net);
+    virNetworkDestroy(test->net);
+
+    if (virEventRunDefaultImpl() < 0) {
+        ret = -1;
+        goto cleanup;
+    }
+
+    if (counter.startEvents != 1 || counter.stopEvents != 1) {
+        ret = -1;
+        goto cleanup;
+    }
+
+cleanup:
+    virConnectNetworkEventDeregisterAny(test->conn, id);
+
+    return ret;
+}
+
+static int
+mymain(void)
+{
+    struct objecteventTest test;
+    int ret = EXIT_SUCCESS;
+
+    virEventRegisterDefaultImpl();
+
+    if (!(test.conn = virConnectOpen("test:///default")))
+        return EXIT_FAILURE;
+
+    /* Tests requiring the test network not to be set up*/
+    if (virtTestRun("Network createXML start event ", testNetworkCreateXML, &test) < 0)
+        ret = EXIT_FAILURE;
+    if (virtTestRun("Network (un)define events", testNetworkDefine, &test) < 0)
+        ret = EXIT_FAILURE;
+
+    /* Define a test network */
+    test.net = virNetworkDefineXML(test.conn, networkDef);
+
+    virtTestQuiesceLibvirtErrors(false);
+
+    if (virtTestRun("Network start stop events ", testNetworkStartStopEvent, &test) < 0)
+        ret = EXIT_FAILURE;
+
+    /* Undefine test network */
+    virNetworkUndefine(test.net);
+
+    virNetworkFree(test.net);
+    virConnectClose(test.conn);
+
+    return ret;
+}
+
+VIRT_TEST_MAIN(mymain)
-- 
1.8.4.2




More information about the libvir-list mailing list