[libvirt PATCH] Metadata support for Network Objects

K Shiva posted 1 patch 11 months, 1 week ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/libvirt tags/patchew/20230611194459.50485-1-shiva._5Fkr@riseup.net
There is a newer version of this series
include/libvirt/libvirt-domain.h    |   2 +-
include/libvirt/libvirt-network.h   |  51 ++++
include/libvirt/virterror.h         |   2 +
po/POTFILES                         |   1 +
src/conf/network_conf.c             |   3 +
src/conf/network_conf.h             |   2 +
src/conf/network_event.c            | 115 +++++++++
src/conf/network_event.h            |  11 +
src/conf/virnetworkobj.c            | 347 ++++++++++++++++++++++++++--
src/conf/virnetworkobj.h            |  56 +++++
src/driver-network.h                |  16 ++
src/libvirt-network.c               | 167 +++++++++++++
src/libvirt_public.syms             |   6 +
src/remote/remote_daemon_dispatch.c |  39 ++++
src/remote/remote_driver.c          |  32 +++
src/remote/remote_protocol.x        |  15 +-
src/remote_protocol-structs         |   6 +
src/test/test_driver.c              |  74 ++++++
src/util/virerror.c                 |   3 +
tests/meson.build                   |   1 +
tests/networkmetadatatest.c         | 297 ++++++++++++++++++++++++
tools/virsh-network.c               |  78 ++++++-
22 files changed, 1299 insertions(+), 25 deletions(-)
create mode 100644 tests/networkmetadatatest.c
[libvirt PATCH] Metadata support for Network Objects
Posted by K Shiva 11 months, 1 week ago
Adds support for the following to Network Object:
- <metadata>, <title> and <description> added to Network obj Schema.
- Public Get and Set APIs, being virNetworkSetMetadata() &
  virNetworkGetMetadata().
- An async callback that notifies of changes to Network metadata.

Resolves (GSoC 2023): https://wiki.libvirt.org/Google_Summer_of_Code_Ideas.html
Signed-off-by: K Shiva <shiva_kr@riseup.net>
---
 include/libvirt/libvirt-domain.h    |   2 +-
 include/libvirt/libvirt-network.h   |  51 ++++
 include/libvirt/virterror.h         |   2 +
 po/POTFILES                         |   1 +
 src/conf/network_conf.c             |   3 +
 src/conf/network_conf.h             |   2 +
 src/conf/network_event.c            | 115 +++++++++
 src/conf/network_event.h            |  11 +
 src/conf/virnetworkobj.c            | 347 ++++++++++++++++++++++++++--
 src/conf/virnetworkobj.h            |  56 +++++
 src/driver-network.h                |  16 ++
 src/libvirt-network.c               | 167 +++++++++++++
 src/libvirt_public.syms             |   6 +
 src/remote/remote_daemon_dispatch.c |  39 ++++
 src/remote/remote_driver.c          |  32 +++
 src/remote/remote_protocol.x        |  15 +-
 src/remote_protocol-structs         |   6 +
 src/test/test_driver.c              |  74 ++++++
 src/util/virerror.c                 |   3 +
 tests/meson.build                   |   1 +
 tests/networkmetadatatest.c         | 297 ++++++++++++++++++++++++
 tools/virsh-network.c               |  78 ++++++-
 22 files changed, 1299 insertions(+), 25 deletions(-)
 create mode 100644 tests/networkmetadatatest.c

diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
index a1902546bb..ea36805aa3 100644
--- a/include/libvirt/libvirt-domain.h
+++ b/include/libvirt/libvirt-domain.h
@@ -5184,7 +5184,7 @@ typedef void (*virConnectDomainEventDeviceRemovalFailedCallback)(virConnectPtr c
  * virConnectDomainEventMetadataChangeCallback:
  * @conn: connection object
  * @dom: domain on which the event occurred
- * @type: a value from virDomainMetadataTypea
+ * @type: a value from virDomainMetadataType
  * @nsuri: XML namespace URI
  * @opaque: application specified data
  *
diff --git a/include/libvirt/libvirt-network.h b/include/libvirt/libvirt-network.h
index 90cde0cf24..e5d25d699b 100644
--- a/include/libvirt/libvirt-network.h
+++ b/include/libvirt/libvirt-network.h
@@ -330,6 +330,7 @@ typedef void (*virConnectNetworkEventLifecycleCallback)(virConnectPtr conn,
  */
 typedef enum {
     VIR_NETWORK_EVENT_ID_LIFECYCLE = 0,       /* virConnectNetworkEventLifecycleCallback (Since: 1.2.1) */
+    VIR_NETWORK_EVENT_ID_METADATA_CHANGE = 1, /* virConnectNetworkEventMetadataChangeCallback (Since: 9.5.0) */
 
 # ifdef VIR_ENUM_SENTINELS
     VIR_NETWORK_EVENT_ID_LAST
@@ -547,4 +548,54 @@ virNetworkPortFree(virNetworkPortPtr port);
 int
 virNetworkPortRef(virNetworkPortPtr port);
 
+/**
+ * virNetworkMetadataType:
+ *
+ * Since: 9.5.0
+ */
+typedef enum {
+    VIR_NETWORK_METADATA_DESCRIPTION = 0, /* Operate on <description> (Since: 9.5.0) */
+    VIR_NETWORK_METADATA_TITLE       = 1, /* Operate on <title> (Since: 9.5.0) */
+    VIR_NETWORK_METADATA_ELEMENT     = 2, /* Operate on <metadata> (Since: 9.5.0) */
+
+# ifdef VIR_ENUM_SENTINELS
+    VIR_NETWORK_METADATA_LAST /* (Since: 9.5.0) */
+# endif
+} virNetworkMetadataType;
+
+int
+virNetworkSetMetadata(virNetworkPtr network,
+                          int type,
+                          const char *metadata,
+                          const char *key,
+                          const char *uri,
+                          unsigned int flags);
+
+char *
+virNetworkGetMetadata(virNetworkPtr network,
+                      int type,
+                      const char *uri,
+                      unsigned int flags);
+
+/**
+ * virConnectNetworkEventMetadataChangeCallback:
+ * @conn: connection object
+ * @net: network on which the event occurred
+ * @type: a value from virNetworkMetadataType
+ * @nsuri: XML namespace URI
+ * @opaque: application specified data
+ *
+ * This callback is triggered when the network XML metadata is changed
+ *
+ * The callback signature to use when registering for an event of type
+ * VIR_NETWORK_EVENT_ID_METADATA_CHANGE with virConnectNetworkEventRegisterAny().
+ *
+ * Since: 9.5.0
+ */
+typedef void (*virConnectNetworkEventMetadataChangeCallback)(virConnectPtr conn,
+                                                            virNetworkPtr net,
+                                                            int type,
+                                                            const char *nsuri,
+                                                            void *opaque);
+
 #endif /* LIBVIRT_NETWORK_H */
diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index df13e4f11e..2910ff03da 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -348,6 +348,8 @@ typedef enum {
     VIR_ERR_NO_HOSTNAME = 108,          /* no domain's hostname found (Since: 6.1.0) */
     VIR_ERR_CHECKPOINT_INCONSISTENT = 109, /* checkpoint can't be used (Since: 6.10.0) */
     VIR_ERR_MULTIPLE_DOMAINS = 110,     /* more than one matching domain found (Since: 7.1.0) */
+    VIR_ERR_NO_NETWORK_METADATA = 111,  /* Network metadata is not present (Since: 9.5.0) */
+
 
 # ifdef VIR_ENUM_SENTINELS
     VIR_ERR_NUMBER_LAST /* (Since: 5.0.0) */
diff --git a/po/POTFILES b/po/POTFILES
index 5d6ec195b4..933a2e07a4 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -39,6 +39,7 @@ src/conf/netdev_bandwidth_conf.c
 src/conf/netdev_vlan_conf.c
 src/conf/netdev_vport_profile_conf.c
 src/conf/network_conf.c
+src/conf/network_event.c
 src/conf/networkcommon_conf.c
 src/conf/node_device_conf.c
 src/conf/node_device_util.c
diff --git a/src/conf/network_conf.c b/src/conf/network_conf.c
index 73788b6d87..84952db041 100644
--- a/src/conf/network_conf.c
+++ b/src/conf/network_conf.c
@@ -2546,6 +2546,9 @@ virNetworkSaveXML(const char *configDir,
     char uuidstr[VIR_UUID_STRING_BUFLEN];
     g_autofree char *configFile = NULL;
 
+    if (!configDir)
+        return 0;
+
     if ((configFile = virNetworkConfigFile(configDir, def->name)) == NULL)
         return -1;
 
diff --git a/src/conf/network_conf.h b/src/conf/network_conf.h
index 2b2e9d15f0..5a1bdb1284 100644
--- a/src/conf/network_conf.h
+++ b/src/conf/network_conf.h
@@ -249,6 +249,8 @@ struct _virNetworkDef {
     unsigned char uuid[VIR_UUID_BUFLEN];
     bool uuid_specified;
     char *name;
+    char *title;
+    char *description;
     int   connections; /* # of guest interfaces connected to this network */
 
     char *bridge;       /* Name of bridge device */
diff --git a/src/conf/network_event.c b/src/conf/network_event.c
index 6f25e43711..0e12cc2687 100644
--- a/src/conf/network_event.c
+++ b/src/conf/network_event.c
@@ -26,6 +26,9 @@
 #include "object_event_private.h"
 #include "datatypes.h"
 #include "virlog.h"
+#include "virerror.h"
+
+#define VIR_FROM_THIS VIR_FROM_NETWORK
 
 VIR_LOG_INIT("conf.network_event");
 
@@ -45,10 +48,21 @@ struct _virNetworkEventLifecycle {
 };
 typedef struct _virNetworkEventLifecycle virNetworkEventLifecycle;
 
+struct _virNetworkEventMetadataChange {
+    virNetworkEvent parent;
+
+    int type;
+    char *nsuri;
+};
+typedef struct _virNetworkEventMetadataChange virNetworkEventMetadataChange;
+
 static virClass *virNetworkEventClass;
 static virClass *virNetworkEventLifecycleClass;
+static virClass *virNetworkEventMetadataChangeClass;
+
 static void virNetworkEventDispose(void *obj);
 static void virNetworkEventLifecycleDispose(void *obj);
+static void virNetworkEventMetadataChangeDispose(void *obj);
 
 static int
 virNetworkEventsOnceInit(void)
@@ -59,6 +73,9 @@ virNetworkEventsOnceInit(void)
     if (!VIR_CLASS_NEW(virNetworkEventLifecycle, virNetworkEventClass))
         return -1;
 
+    if (!VIR_CLASS_NEW(virNetworkEventMetadataChange, virNetworkEventClass))
+        return -1;
+
     return 0;
 }
 
@@ -104,9 +121,22 @@ virNetworkEventDispatchDefaultFunc(virConnectPtr conn,
             return;
         }
 
+    case VIR_NETWORK_EVENT_ID_METADATA_CHANGE:
+        {
+            virNetworkEventMetadataChange *metadataChangeEvent;
+
+            metadataChangeEvent = (virNetworkEventMetadataChange *)event;
+            ((virConnectNetworkEventMetadataChangeCallback)cb)(conn, net,
+                                                              metadataChangeEvent->type,
+                                                              metadataChangeEvent->nsuri,
+                                                              cbopaque);
+            return;
+        }
+
     case VIR_NETWORK_EVENT_ID_LAST:
         break;
     }
+
     VIR_WARN("Unexpected event ID %d", event->eventID);
 }
 
@@ -231,3 +261,88 @@ virNetworkEventLifecycleNew(const char *name,
 
     return (virObjectEvent *)event;
 }
+
+
+static void *
+virNetworkEventNew(virClass *klass,
+                  int eventID,
+                  const char *name,
+                  const unsigned char *uuid)
+{
+    virNetworkEvent *event;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+
+    if (virNetworkEventsInitialize() < 0)
+        return NULL;
+
+    if (!virClassIsDerivedFrom(klass, virNetworkEventClass)) {
+        virReportInvalidArg(klass,
+                            _("Class %1$s must derive from virNetworkEvent"),
+                            virClassName(klass));
+        return NULL;
+    }
+
+    /* We use uuid for matching key. We ignore 'name' because
+     * Xen sometimes renames guests during migration, thus
+     * 'uuid' is the only truly reliable key we can use. */
+    virUUIDFormat(uuid, uuidstr);
+    if (!(event = virObjectEventNew(klass,
+                                    virNetworkEventDispatchDefaultFunc,
+                                    eventID,
+                                    0, name, uuid, uuidstr)))
+        return NULL;
+
+    return (virObjectEvent *)event;
+}
+
+
+static void
+virNetworkEventMetadataChangeDispose(void *obj)
+{
+    virNetworkEventMetadataChange *event = obj;
+    VIR_DEBUG("obj=%p", event);
+
+    g_free(event->nsuri);
+}
+
+
+static virObjectEvent *
+virNetworkEventMetadataChangeNew(const char *name,
+                                 unsigned char *uuid,
+                                 int type,
+                                 const char *nsuri)
+{
+    virNetworkEventMetadataChange *ev;
+
+    if (virNetworkEventsInitialize() < 0)
+        return NULL;
+
+    if (!(ev = virNetworkEventNew(virNetworkEventMetadataChangeClass,
+                                 VIR_NETWORK_EVENT_ID_METADATA_CHANGE,
+                                 name, uuid)))
+        return NULL;
+
+    ev->type = type;
+    if (nsuri)
+        ev->nsuri = g_strdup(nsuri);
+
+    return (virObjectEvent *)ev;
+}
+
+virObjectEvent *
+virNetworkEventMetadataChangeNewFromObj(virNetworkObj *obj,
+                                       int type,
+                                       const char *nsuri)
+{
+    return virNetworkEventMetadataChangeNew(obj->def->name,
+                                           obj->def->uuid, type, nsuri);
+}
+
+virObjectEvent *
+virNetworkEventMetadataChangeNewFromNet(virNetworkPtr net,
+                                       int type,
+                                       const char *nsuri)
+{
+    return virNetworkEventMetadataChangeNew(net->name, net->uuid,
+                                           type, nsuri);
+}
diff --git a/src/conf/network_event.h b/src/conf/network_event.h
index 4502bfcaef..7c98a6ac92 100644
--- a/src/conf/network_event.h
+++ b/src/conf/network_event.h
@@ -23,6 +23,7 @@
 
 #include "internal.h"
 #include "object_event.h"
+#include "virnetworkobj.h"
 
 int
 virNetworkEventStateRegisterID(virConnectPtr conn,
@@ -53,3 +54,13 @@ virNetworkEventLifecycleNew(const char *name,
                             const unsigned char *uuid,
                             int type,
                             int detail);
+
+virObjectEvent *
+virNetworkEventMetadataChangeNewFromObj(virNetworkObj *obj,
+                                       int type,
+                                       const char *nsuri);
+
+virObjectEvent *
+virNetworkEventMetadataChangeNewFromNet(virNetworkPtr net,
+                                       int type,
+                                       const char *nsuri);
diff --git a/src/conf/virnetworkobj.c b/src/conf/virnetworkobj.c
index b8b86da06f..f413b54425 100644
--- a/src/conf/virnetworkobj.c
+++ b/src/conf/virnetworkobj.c
@@ -39,28 +39,6 @@ VIR_LOG_INIT("conf.virnetworkobj");
  * that big. */
 #define INIT_CLASS_ID_BITMAP_SIZE (1<<4)
 
-struct _virNetworkObj {
-    virObjectLockable parent;
-
-    pid_t dnsmasqPid;
-    bool active;
-    bool autostart;
-    bool persistent;
-
-    virNetworkDef *def; /* The current definition */
-    virNetworkDef *newDef; /* New definition to activate at shutdown */
-
-    virBitmap *classIdMap; /* bitmap of class IDs for QoS */
-    unsigned long long floor_sum; /* sum of all 'floor'-s of attached NICs */
-
-    unsigned int taint;
-
-    /* Immutable pointer, self locking APIs */
-    virMacMap *macmap;
-
-    GHashTable *ports; /* uuid -> virNetworkPortDef **/
-};
-
 struct _virNetworkObjList {
     virObjectRWLockable parent;
 
@@ -1822,3 +1800,328 @@ virNetworkObjLoadAllPorts(virNetworkObj *net,
 
     return 0;
 }
+
+
+/**
+ * virNetworkObjUpdateModificationImpact:
+ *
+ * @net: network object
+ * @flags: flags to update the modification impact on
+ *
+ * Resolves virNetworkModificationImpact flags in @flags so that they correctly
+ * apply to the actual state of @net. @flags may be modified after call to this
+ * function.
+ *
+ * Returns 0 on success if @flags point to a valid combination for @net or -1 on
+ * error.
+ */
+int
+virNetworkObjUpdateModificationImpact(virNetworkObj *net,
+                                     unsigned int *flags)
+{
+    bool isActive = virNetworkObjIsActive(net);
+
+    if ((*flags & (VIR_NETWORK_UPDATE_AFFECT_LIVE | VIR_NETWORK_UPDATE_AFFECT_CONFIG)) ==
+        VIR_NETWORK_UPDATE_AFFECT_CURRENT) {
+        if (isActive)
+            *flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE;
+        else
+            *flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG;
+    }
+
+    if (!isActive && (*flags & VIR_NETWORK_UPDATE_AFFECT_LIVE)) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("network is not running"));
+        return -1;
+    }
+
+    if (!net->persistent && (*flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG)) {
+        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
+                       _("transient networks do not have any "
+                         "persistent config"));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+/**
+ * virNetworkObjGetDefs:
+ *
+ * @net: network object
+ * @flags: for virNetworkModificationImpact
+ * @liveDef: Set the pointer to the live definition of @net.
+ * @persDef: Set the pointer to the config definition of @net.
+ *
+ * Helper function to resolve @flags and retrieve correct network pointer
+ * objects. This function should be used only when the network driver
+ * creates net->newDef once the network has started.
+ *
+ * If @liveDef or @persDef are set it implies that @flags request modification
+ * thereof.
+ *
+ * Returns 0 on success and sets @liveDef and @persDef; -1 if @flags are
+ * inappropriate.
+ */
+int
+virNetworkObjGetDefs(virNetworkObj *net,
+                    unsigned int flags,
+                    virNetworkDef **liveDef,
+                    virNetworkDef **persDef)
+{
+    if (liveDef)
+        *liveDef = NULL;
+
+    if (persDef)
+        *persDef = NULL;
+
+    if (virNetworkObjUpdateModificationImpact(net, &flags) < 0)
+        return -1;
+
+    if (virNetworkObjIsActive(net)) {
+        if (liveDef && (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE))
+            *liveDef = net->def;
+
+        if (persDef && (flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG))
+            *persDef = net->newDef;
+    } else {
+        if (persDef)
+            *persDef = net->def;
+    }
+
+    return 0;
+}
+
+
+/**
+ * virNetworkObjGetOneDefState:
+ *
+ * @net: Network object
+ * @flags: for virNetworkModificationImpact
+ * @live: set to true if live config was returned (may be omitted)
+ *
+ * Helper function to resolve @flags and return the correct network pointer
+ * object. This function returns one of @net->def or @net->persistentDef
+ * according to @flags. @live is set to true if the live net config will be
+ * returned. This helper should be used only in APIs that guarantee
+ * that @flags contains exactly one of VIR_NETWORK_UPDATE_AFFECT_LIVE or
+ * VIR_NETWORK_UPDATE_AFFECT_CONFIG and not both.
+ *
+ * Returns the correct definition pointer or NULL on error.
+ */
+virNetworkDef *
+virNetworkObjGetOneDefState(virNetworkObj *net,
+                           unsigned int flags,
+                           bool *live)
+{
+    if (flags & VIR_NETWORK_UPDATE_AFFECT_LIVE &&
+        flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG) {
+        virReportInvalidArg(flags, "%s",
+                            _("Flags 'VIR_NETWORK_UPDATE_AFFECT_LIVE' and "
+                              "'VIR_NETWORK_UPDATE_AFFECT_CONFIG' are mutually "
+                              "exclusive"));
+        return NULL;
+    }
+
+    if (virNetworkObjUpdateModificationImpact(net, &flags) < 0)
+        return NULL;
+
+    if (live)
+        *live = flags & VIR_NETWORK_UPDATE_AFFECT_LIVE;
+
+    if (virNetworkObjIsActive(net) && flags & VIR_NETWORK_UPDATE_AFFECT_CONFIG)
+        return net->newDef;
+
+    return net->def;
+}
+
+
+/**
+ * virNetworkObjGetOneDef:
+ *
+ * @net: Network object
+ * @flags: for virNetworkModificationImpact
+ *
+ * Helper function to resolve @flags and return the correct network pointer
+ * object. This function returns one of @net->def or @net->persistentDef
+ * according to @flags. This helper should be used only in APIs that guarantee
+ * that @flags contains exactly one of VIR_NETWORK_UPDATE_AFFECT_LIVE or
+ * VIR_NETWORK_UPDATE_AFFECT_CONFIG and not both.
+ *
+ * Returns the correct definition pointer or NULL on error.
+ */
+virNetworkDef *
+virNetworkObjGetOneDef(virNetworkObj *net,
+                      unsigned int flags)
+{
+    return virNetworkObjGetOneDefState(net, flags, NULL);
+}
+
+
+char *
+virNetworkObjGetMetadata(virNetworkObj *net,
+                        int type,
+                        const char *uri,
+                        unsigned int flags)
+{
+    virNetworkDef *def;
+    char *ret = NULL;
+
+    virCheckFlags(VIR_NETWORK_UPDATE_AFFECT_LIVE |
+                  VIR_NETWORK_UPDATE_AFFECT_CONFIG, NULL);
+
+    if (type >= VIR_NETWORK_METADATA_LAST) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("unknown metadata type '%1$d'"), type);
+        return NULL;
+    }
+
+    if (!(def = virNetworkObjGetOneDef(net, flags)))
+        return NULL;
+
+    switch ((virNetworkMetadataType) type) {
+    case VIR_NETWORK_METADATA_DESCRIPTION:
+        ret = g_strdup(def->description);
+        break;
+
+    case VIR_NETWORK_METADATA_TITLE:
+        ret = g_strdup(def->title);
+        break;
+
+    case VIR_NETWORK_METADATA_ELEMENT:
+        if (!def->metadata)
+            break;
+
+        if (virXMLExtractNamespaceXML(def->metadata, uri, &ret) < 0)
+            return NULL;
+        break;
+
+    case VIR_NETWORK_METADATA_LAST:
+        break;
+    }
+
+    if (!ret)
+        virReportError(VIR_ERR_NO_NETWORK_METADATA, "%s",
+                       _("Requested metadata element is not present"));
+
+    return ret;
+}
+
+
+static int
+virNetworkDefSetMetadata(virNetworkDef *def,
+                        int type,
+                        const char *metadata,
+                        const char *key,
+                        const char *uri)
+{
+    g_autoptr(xmlDoc) doc = NULL;
+    xmlNodePtr old;
+    g_autoptr(xmlNode) new = NULL;
+
+    if (type >= VIR_NETWORK_METADATA_LAST) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("unknown metadata type '%1$d'"), type);
+        return -1;
+    }
+
+    switch ((virNetworkMetadataType) type) {
+    case VIR_NETWORK_METADATA_DESCRIPTION:
+        g_clear_pointer(&def->description, g_free);
+
+        if (STRNEQ_NULLABLE(metadata, ""))
+            def->description = g_strdup(metadata);
+        break;
+
+    case VIR_NETWORK_METADATA_TITLE:
+        g_clear_pointer(&def->title, g_free);
+
+        if (STRNEQ_NULLABLE(metadata, ""))
+            def->title = g_strdup(metadata);
+        break;
+
+    case VIR_NETWORK_METADATA_ELEMENT:
+        if (metadata) {
+
+            /* parse and modify the xml from the user */
+            if (!(doc = virXMLParseStringCtxt(metadata, _("(metadata_xml)"), NULL)))
+                return -1;
+
+            if (virXMLInjectNamespace(doc->children, uri, key) < 0)
+                return -1;
+
+            /* create the root node if needed */
+            if (!def->metadata)
+                def->metadata = virXMLNewNode(NULL, "metadata");
+
+            if (!(new = xmlCopyNode(doc->children, 1))) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("Failed to copy XML node"));
+                return -1;
+            }
+        }
+
+        /* remove possible other nodes sharing the namespace */
+        while ((old = virXMLFindChildNodeByNs(def->metadata, uri))) {
+            xmlUnlinkNode(old);
+            xmlFreeNode(old);
+        }
+
+        if (new) {
+            if (!(xmlAddChild(def->metadata, new))) {
+                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                               _("failed to add metadata to XML document"));
+                return -1;
+            }
+            new = NULL;
+        }
+        break;
+
+    case VIR_NETWORK_METADATA_LAST:
+        break;
+    }
+
+    return 0;
+}
+
+
+int
+virNetworkObjSetMetadata(virNetworkObj *net,
+                        int type,
+                        const char *metadata,
+                        const char *key,
+                        const char *uri,
+                        virNetworkXMLOption *xmlopt,
+                        const char *stateDir,
+                        const char *configDir,
+                        unsigned int flags)
+{
+    virNetworkDef *def;
+    virNetworkDef *persistentDef;
+
+    virCheckFlags(VIR_NETWORK_UPDATE_AFFECT_LIVE |
+                  VIR_NETWORK_UPDATE_AFFECT_CONFIG, -1);
+
+    if (virNetworkObjGetDefs(net, flags, &def, &persistentDef) < 0)
+        return -1;
+
+    if (def) {
+        if (virNetworkDefSetMetadata(def, type, metadata, key, uri) < 0)
+            return -1;
+
+        if (virNetworkObjSaveStatus(stateDir, net, xmlopt) < 0)
+            return -1;
+    }
+
+    if (persistentDef) {
+        if (virNetworkDefSetMetadata(persistentDef, type, metadata, key,
+                                    uri) < 0)
+            return -1;
+
+        if (virNetworkSaveConfig(configDir, persistentDef, xmlopt) < 0)
+            return -1;
+    }
+
+    return 0;
+}
diff --git a/src/conf/virnetworkobj.h b/src/conf/virnetworkobj.h
index 7d34fa3204..d17a43d7bb 100644
--- a/src/conf/virnetworkobj.h
+++ b/src/conf/virnetworkobj.h
@@ -26,6 +26,28 @@
 
 typedef struct _virNetworkObj virNetworkObj;
 
+struct _virNetworkObj {
+    virObjectLockable parent;
+
+    pid_t dnsmasqPid;
+    bool active;
+    bool autostart;
+    bool persistent;
+
+    virNetworkDef *def; /* The current definition */
+    virNetworkDef *newDef; /* New definition to activate at shutdown */
+
+    virBitmap *classIdMap; /* bitmap of class IDs for QoS */
+    unsigned long long floor_sum; /* sum of all 'floor'-s of attached NICs */
+
+    unsigned int taint;
+
+    /* Immutable pointer, self locking APIs */
+    virMacMap *macmap;
+
+    GHashTable *ports; /* uuid -> virNetworkPortDef **/
+};
+
 virNetworkObj *
 virNetworkObjNew(void);
 
@@ -258,3 +280,37 @@ virNetworkObjListNumOfNetworks(virNetworkObjList *nets,
 void
 virNetworkObjListPrune(virNetworkObjList *nets,
                        unsigned int flags);
+
+int virNetworkObjUpdateModificationImpact(virNetworkObj *net,
+                                          unsigned int *flags);
+
+int
+virNetworkObjGetDefs(virNetworkObj *net,
+                    unsigned int flags,
+                    virNetworkDef **liveDef,
+                    virNetworkDef **persDef);
+
+virNetworkDef *
+virNetworkObjGetOneDefState(virNetworkObj *net,
+                            unsigned int flags,
+                            bool *state);
+virNetworkDef *
+virNetworkObjGetOneDef(virNetworkObj *net,
+                       unsigned int flags);
+
+char *
+virNetworkObjGetMetadata(virNetworkObj *network,
+                         int type,
+                         const char *uri,
+                         unsigned int flags);
+
+int
+virNetworkObjSetMetadata(virNetworkObj *network,
+                         int type,
+                         const char *metadata,
+                         const char *key,
+                         const char *uri,
+                         virNetworkXMLOption *xmlopt,
+                         const char *stateDir,
+                         const char *configDir,
+                         unsigned int flags);
diff --git a/src/driver-network.h b/src/driver-network.h
index 99efd4c8aa..1d19b013c9 100644
--- a/src/driver-network.h
+++ b/src/driver-network.h
@@ -161,6 +161,20 @@ typedef int
                              virNetworkPortPtr **ports,
                              unsigned int flags);
 
+typedef int
+(*virDrvNetworkSetMetadata)(virNetworkPtr network,
+                            int type,
+                            const char *metadata,
+                            const char *key,
+                            const char *uri,
+                            unsigned int flags);
+
+typedef char *
+(*virDrvNetworkGetMetadata)(virNetworkPtr network,
+                            int type,
+                            const char *uri,
+                            unsigned int flags);
+
 typedef struct _virNetworkDriver virNetworkDriver;
 
 /**
@@ -202,4 +216,6 @@ struct _virNetworkDriver {
     virDrvNetworkPortGetParameters networkPortGetParameters;
     virDrvNetworkPortDelete networkPortDelete;
     virDrvNetworkListAllPorts networkListAllPorts;
+    virDrvNetworkSetMetadata networkSetMetadata;
+    virDrvNetworkGetMetadata networkGetMetadata;
 };
diff --git a/src/libvirt-network.c b/src/libvirt-network.c
index 236dfe2f5d..5f4e088421 100644
--- a/src/libvirt-network.c
+++ b/src/libvirt-network.c
@@ -1915,3 +1915,170 @@ virNetworkPortRef(virNetworkPortPtr port)
     virObjectRef(port);
     return 0;
 }
+
+
+/**
+ * virNetworkSetMetadata:
+ * @network: a network object
+ * @type: type of metadata, from virNetworkMetadataType
+ * @metadata: new metadata text
+ * @key: XML namespace key, or NULL
+ * @uri: XML namespace URI, or NULL
+ * @flags: bitwise-OR of virDomainModificationImpact
+ *
+ * Sets the appropriate network element given by @type to the
+ * value of @metadata.  A @type of VIR_NETWORK_METADATA_DESCRIPTION
+ * is free-form text; VIR_NETWORK_METADATA_TITLE is free-form, but no
+ * newlines are permitted, and should be short (although the length is
+ * not enforced). For these two options @key and @uri are irrelevant and
+ * must be set to NULL.
+ *
+ * For type VIR_NETWORK_METADATA_ELEMENT @metadata  must be well-formed
+ * XML belonging to namespace defined by @uri with local name @key.
+ *
+ * Passing NULL for @metadata says to remove that element from the
+ * domain XML (passing the empty string leaves the element present).
+ *
+ * The resulting metadata will be present in virNetworkGetXMLDesc(),
+ * as well as quick access through virNetworkGetMetadata().
+ *
+ * @flags controls whether the live domain, persistent configuration,
+ * or both will be modified.
+ *
+ * Returns 0 on success, -1 in case of failure.
+ *
+ * Since: 9.5.0
+ */
+int
+virNetworkSetMetadata(virNetworkPtr network,
+                     int type,
+                     const char *metadata,
+                     const char *key,
+                     const char *uri,
+                     unsigned int flags)
+{
+    virConnectPtr conn;
+
+    VIR_DEBUG("network=%p, type=%d, metadata='%s', key='%s', uri='%s', flags=0x%x",
+              network, type, NULLSTR(metadata), NULLSTR(key), NULLSTR(uri),
+              flags);
+
+    virResetLastError();
+
+    virCheckNetworkReturn(network, -1);
+    conn = network->conn;
+
+    virCheckReadOnlyGoto(conn->flags, error);
+
+    switch (type) {
+    case VIR_NETWORK_METADATA_TITLE:
+        if (metadata && strchr(metadata, '\n')) {
+            virReportInvalidArg(metadata, "%s",
+                                _("metadata title can't contain "
+                                  "newlines"));
+            goto error;
+        }
+        G_GNUC_FALLTHROUGH;
+    case VIR_NETWORK_METADATA_DESCRIPTION:
+        virCheckNullArgGoto(uri, error);
+        virCheckNullArgGoto(key, error);
+        break;
+    case VIR_NETWORK_METADATA_ELEMENT:
+        virCheckNonNullArgGoto(uri, error);
+        if (metadata)
+            virCheckNonNullArgGoto(key, error);
+        break;
+    default:
+        /* For future expansion */
+        break;
+    }
+
+    if (conn->networkDriver->networkSetMetadata) {
+        int ret;
+        ret = conn->networkDriver->networkSetMetadata(network, type, metadata, key, uri,
+                                               flags);
+        if (ret < 0)
+            goto error;
+        return ret;
+    }
+
+    virReportUnsupportedError();
+
+ error:
+    virDispatchError(network->conn);
+    return -1;
+}
+
+
+/**
+ * virNetworkGetMetadata:
+ * @network: a network object
+ * @type: type of metadata, from virNetworkMetadataType
+ * @uri: XML namespace identifier
+ * @flags: bitwise-OR of virDomainModificationImpact
+ *
+ * Retrieves the appropriate network element given by @type.
+ * If VIR_NETWORK_METADATA_ELEMENT is requested parameter @uri
+ * must be set to the name of the namespace the requested elements
+ * belong to, otherwise must be NULL.
+ *
+ * If an element of the network XML is not present, the resulting
+ * error will be VIR_ERR_NO_NETWORK_METADATA.  This method forms
+ * a shortcut for seeing information from virNetworkSetMetadata()
+ * without having to go through virNetworkGetXMLDesc().
+ *
+ * @flags controls whether the live domain or persistent
+ * configuration will be queried.
+ *
+ * Returns the metadata string on success (caller must free),
+ * or NULL in case of failure.
+ *
+ * Since: 9.5.0
+ */
+char *
+virNetworkGetMetadata(virNetworkPtr network,
+                     int type,
+                     const char *uri,
+                     unsigned int flags)
+{
+    virConnectPtr conn;
+
+    VIR_DEBUG("network=%p, type=%d, uri='%s', flags=0x%x",
+               network, type, NULLSTR(uri), flags);
+
+    virResetLastError();
+
+    virCheckNetworkReturn(network, NULL);
+
+    VIR_EXCLUSIVE_FLAGS_GOTO(VIR_NETWORK_UPDATE_AFFECT_LIVE,
+                             VIR_NETWORK_UPDATE_AFFECT_CONFIG,
+                             error);
+
+    switch (type) {
+    case VIR_NETWORK_METADATA_TITLE:
+    case VIR_NETWORK_METADATA_DESCRIPTION:
+        virCheckNullArgGoto(uri, error);
+        break;
+    case VIR_NETWORK_METADATA_ELEMENT:
+        virCheckNonNullArgGoto(uri, error);
+        break;
+    default:
+        /* For future expansion */
+        break;
+    }
+
+    conn = network->conn;
+
+    if (conn->networkDriver->networkGetMetadata) {
+        char *ret;
+        if (!(ret = conn->networkDriver->networkGetMetadata(network, type, uri, flags)))
+            goto error;
+        return ret;
+    }
+
+    virReportUnsupportedError();
+
+ error:
+    virDispatchError(network->conn);
+    return NULL;
+}
diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
index 80742f268e..d21fe49caa 100644
--- a/src/libvirt_public.syms
+++ b/src/libvirt_public.syms
@@ -932,4 +932,10 @@ LIBVIRT_9.0.0 {
         virDomainFDAssociate;
 } LIBVIRT_8.5.0;
 
+LIBVIRT_9.5.0 {
+    global:
+        virNetworkGetMetadata;
+        virNetworkSetMetadata;
+} LIBVIRT_9.0.0;
+
 # .... define new API here using predicted next version number ....
diff --git a/src/remote/remote_daemon_dispatch.c b/src/remote/remote_daemon_dispatch.c
index 7144e9e7ca..3e5eaec9e6 100644
--- a/src/remote/remote_daemon_dispatch.c
+++ b/src/remote/remote_daemon_dispatch.c
@@ -1420,8 +1420,47 @@ remoteRelayNetworkEventLifecycle(virConnectPtr conn,
     return 0;
 }
 
+static int
+remoteRelayNetworkEventMetadataChange(virConnectPtr conn,
+                                     virNetworkPtr net,
+                                     int type,
+                                     const char *nsuri,
+                                     void *opaque)
+{
+    daemonClientEventCallback *callback = opaque;
+    remote_network_event_callback_metadata_change_msg data;
+
+    if (callback->callbackID < 0 ||
+        !remoteRelayNetworkEventCheckACL(callback->client, conn, net))
+        return -1;
+
+    VIR_DEBUG("Relaying network metadata change %s %d %s, callback %d",
+              net->name, type, NULLSTR(nsuri), callback->callbackID);
+
+    /* build return data */
+    memset(&data, 0, sizeof(data));
+
+    data.type = type;
+    if (nsuri) {
+        data.nsuri = g_new0(remote_nonnull_string, 1);
+        *(data.nsuri) = g_strdup(nsuri);
+    }
+
+    make_nonnull_network(&data.net, net);
+    data.callbackID = callback->callbackID;
+
+    remoteDispatchObjectEventSend(callback->client, callback->program,
+                                  REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE,
+                                  (xdrproc_t)xdr_remote_network_event_callback_metadata_change_msg,
+                                  &data);
+
+    return 0;
+}
+
+
 static virConnectNetworkEventGenericCallback networkEventCallbacks[] = {
     VIR_NETWORK_EVENT_CALLBACK(remoteRelayNetworkEventLifecycle),
+    VIR_NETWORK_EVENT_CALLBACK(remoteRelayNetworkEventMetadataChange),
 };
 
 G_STATIC_ASSERT(G_N_ELEMENTS(networkEventCallbacks) == VIR_NETWORK_EVENT_ID_LAST);
diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c
index 65ec239fb7..310f53fe5e 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -378,6 +378,12 @@ remoteNetworkBuildEventLifecycle(virNetClientProgram *prog G_GNUC_UNUSED,
                                  virNetClient *client G_GNUC_UNUSED,
                                  void *evdata, void *opaque);
 
+static void
+remoteNetworkBuildEventCallbackMetadataChange(virNetClientProgram *prog,
+                                             virNetClient *client,
+                                             void *evdata, void *opaque);
+
+
 static void
 remoteStoragePoolBuildEventLifecycle(virNetClientProgram *prog G_GNUC_UNUSED,
                                      virNetClient *client G_GNUC_UNUSED,
@@ -505,6 +511,10 @@ static virNetClientProgramEvent remoteEvents[] = {
       remoteNetworkBuildEventLifecycle,
       sizeof(remote_network_event_lifecycle_msg),
       (xdrproc_t)xdr_remote_network_event_lifecycle_msg },
+    { REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE,
+      remoteNetworkBuildEventCallbackMetadataChange,
+      sizeof(remote_network_event_callback_metadata_change_msg),
+      (xdrproc_t)xdr_remote_network_event_callback_metadata_change_msg },
     { REMOTE_PROC_DOMAIN_EVENT_CALLBACK_LIFECYCLE,
       remoteDomainBuildEventCallbackLifecycle,
       sizeof(remote_domain_event_callback_lifecycle_msg),
@@ -4951,6 +4961,28 @@ remoteNetworkBuildEventLifecycle(virNetClientProgram *prog G_GNUC_UNUSED,
     virObjectEventStateQueueRemote(priv->eventState, event, msg->callbackID);
 }
 
+static void
+remoteNetworkBuildEventCallbackMetadataChange(virNetClientProgram *prog G_GNUC_UNUSED,
+                                             virNetClient *client G_GNUC_UNUSED,
+                                             void *evdata, void *opaque)
+{
+    virConnectPtr conn = opaque;
+    remote_network_event_callback_metadata_change_msg *msg = evdata;
+    struct private_data *priv = conn->privateData;
+    virNetworkPtr net;
+    virObjectEvent *event = NULL;
+
+    if (!(net = get_nonnull_network(conn, msg->net)))
+        return;
+
+    event = virNetworkEventMetadataChangeNewFromNet(net, msg->type, msg->nsuri ? *msg->nsuri : NULL);
+
+    virObjectUnref(net);
+
+    virObjectEventStateQueueRemote(priv->eventState, event, msg->callbackID);
+}
+
+
 static void
 remoteStoragePoolBuildEventLifecycle(virNetClientProgram *prog G_GNUC_UNUSED,
                                      virNetClient *client G_GNUC_UNUSED,
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 5d86a51116..72aa69e580 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -3323,6 +3323,13 @@ struct remote_network_event_lifecycle_msg {
     int detail;
 };
 
+struct remote_network_event_callback_metadata_change_msg {
+    int callbackID;
+    remote_nonnull_network net;
+    int type;
+    remote_string nsuri;
+};
+
 struct remote_connect_storage_pool_event_register_any_args {
     int eventID;
     remote_storage_pool pool;
@@ -6974,5 +6981,11 @@ enum remote_procedure {
      * @generate: none
      * @acl: domain:write
      */
-    REMOTE_PROC_DOMAIN_FD_ASSOCIATE = 443
+    REMOTE_PROC_DOMAIN_FD_ASSOCIATE = 443,
+
+    /**
+     * @generate: both
+     * @acl: none
+     */
+    REMOTE_PROC_NETWORK_EVENT_CALLBACK_METADATA_CHANGE = 444
 };
diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs
index 3c6c230a16..3f7256051e 100644
--- a/src/remote_protocol-structs
+++ b/src/remote_protocol-structs
@@ -2687,6 +2687,12 @@ struct remote_network_event_lifecycle_msg {
         int                        event;
         int                        detail;
 };
+struct remote_network_event_callback_metadata_change_msg {
+        int                        callbackID;
+        remote_nonnull_network     net;
+        int                        type;
+        remote_string              nsuri;
+};
 struct remote_connect_storage_pool_event_register_any_args {
         int                        eventID;
         remote_storage_pool        pool;
diff --git a/src/test/test_driver.c b/src/test/test_driver.c
index e7fce053b4..7294766d6e 100644
--- a/src/test/test_driver.c
+++ b/src/test/test_driver.c
@@ -633,6 +633,25 @@ static int testStoragePoolObjSetDefaults(virStoragePoolObj *obj);
 static int testNodeGetInfo(virConnectPtr conn, virNodeInfoPtr info);
 static virNetworkObj *testNetworkObjFindByName(testDriver *privconn, const char *name);
 
+static virNetworkObj *
+testNetworkObjFromNetwork(virNetworkPtr network)
+{
+    virNetworkObj *net;
+    testDriver *driver = network->conn->privateData;
+    char uuidstr[VIR_UUID_STRING_BUFLEN];
+
+    net = virNetworkObjFindByUUID(driver->networks, network->uuid);
+    if (!net) {
+        virUUIDFormat(network->uuid, uuidstr);
+        virReportError(VIR_ERR_NO_NETWORK,
+                       _("no network with matching uuid '%1$s' (%2$s)"),
+                       uuidstr, network->name);
+    }
+
+    return net;
+}
+
+
 static virDomainObj *
 testDomObjFromDomain(virDomainPtr domain)
 {
@@ -9948,6 +9967,59 @@ testConnectGetAllDomainStats(virConnectPtr conn,
     return ret;
 }
 
+static char *
+testNetworkGetMetadata(virNetworkPtr net,
+                       int type,
+                       const char *uri,
+                       unsigned int flags)
+{
+    virNetworkObj *privnet;
+    char *ret;
+
+    virCheckFlags(VIR_NETWORK_UPDATE_AFFECT_LIVE |
+                  VIR_NETWORK_UPDATE_AFFECT_CONFIG, NULL);
+
+    if (!(privnet = testNetworkObjFromNetwork(net)))
+        return NULL;
+
+    ret = virNetworkObjGetMetadata(privnet, type, uri, flags);
+
+    virNetworkObjEndAPI(&privnet);
+    return ret;
+}
+
+static int
+testNetworkSetMetadata(virNetworkPtr net,
+                       int type,
+                       const char *metadata,
+                       const char *key,
+                       const char *uri,
+                       unsigned int flags)
+{
+    testDriver *privconn = net->conn->privateData;
+    virNetworkObj *privnet;
+    int ret;
+
+    virCheckFlags(VIR_NETWORK_UPDATE_AFFECT_LIVE |
+                  VIR_NETWORK_UPDATE_AFFECT_CONFIG, -1);
+
+    if (!(privnet = testNetworkObjFromNetwork(net)))
+        return -1;
+
+    ret = virNetworkObjSetMetadata(privnet, type, metadata,
+                                   key, uri, NULL,
+                                   NULL, NULL, flags);
+
+    if (ret == 0) {
+        virObjectEvent *ev = NULL;
+        ev = virNetworkEventMetadataChangeNewFromObj(privnet, type, uri);
+        virObjectEventStateQueue(privconn->eventState, ev);
+    }
+
+    virNetworkObjEndAPI(&privnet);
+    return ret;
+}
+
 /*
  * Test driver
  */
@@ -10141,6 +10213,8 @@ static virNetworkDriver testNetworkDriver = {
     .networkSetAutostart = testNetworkSetAutostart, /* 0.3.2 */
     .networkIsActive = testNetworkIsActive, /* 0.7.3 */
     .networkIsPersistent = testNetworkIsPersistent, /* 0.7.3 */
+    .networkSetMetadata = testNetworkSetMetadata, /* 9.5.0 */
+    .networkGetMetadata = testNetworkGetMetadata, /* 9.5.0 */
 };
 
 static virInterfaceDriver testInterfaceDriver = {
diff --git a/src/util/virerror.c b/src/util/virerror.c
index 453f19514e..227a182417 100644
--- a/src/util/virerror.c
+++ b/src/util/virerror.c
@@ -1287,6 +1287,9 @@ static const virErrorMsgTuple virErrorMsgStrings[] = {
     [VIR_ERR_MULTIPLE_DOMAINS] = {
         N_("multiple matching domains found"),
         N_("multiple matching domains found: %1$s") },
+    [VIR_ERR_NO_NETWORK_METADATA] = {
+        N_("metadata not found"),
+        N_("metadata not found: %1$s") },
 };
 
 G_STATIC_ASSERT(G_N_ELEMENTS(virErrorMsgStrings) == VIR_ERR_NUMBER_LAST);
diff --git a/tests/meson.build b/tests/meson.build
index 0082446029..d083548c0a 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -258,6 +258,7 @@ tests += [
   { 'name': 'genericxml2xmltest' },
   { 'name': 'interfacexml2xmltest' },
   { 'name': 'metadatatest' },
+  { 'name': 'networkmetadatatest' },
   { 'name': 'networkxml2xmlupdatetest' },
   { 'name': 'nodedevxml2xmltest' },
   { 'name': 'nwfilterxml2xmltest' },
diff --git a/tests/networkmetadatatest.c b/tests/networkmetadatatest.c
new file mode 100644
index 0000000000..4448472776
--- /dev/null
+++ b/tests/networkmetadatatest.c
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2013 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
+ * 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/>.
+ */
+
+#include <config.h>
+
+#include "testutils.h"
+
+#include "virerror.h"
+#include "virxml.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+static const char metadata1[] =
+"<derp xmlns:foobar='http://foo.bar/'>\n"
+"  <bar>foobar</bar>\n"
+"  <foo fooish='blurb'>foofoo</foo>\n"
+"  <foobar:baz>zomg</foobar:baz>\n"
+"</derp>";
+
+
+static const char metadata1_ns[] =
+"<herp:derp xmlns:foobar='http://foo.bar/' xmlns:herp='http://herp.derp/'>\n"
+"  <herp:bar>foobar</herp:bar>\n"
+"  <herp:foo fooish='blurb'>foofoo</herp:foo>\n"
+"  <foobar:baz>zomg</foobar:baz>\n"
+"</herp:derp>";
+
+
+static const char metadata2[] =
+"<foo>\n"
+"  <bar>baz</bar>\n"
+"</foo>";
+
+
+static const char metadata2_ns[] =
+"<blurb:foo xmlns:blurb='http://herp.derp/'>\n"
+"  <blurb:bar>baz</blurb:bar>\n"
+"</blurb:foo>";
+
+
+static char *
+getMetadataFromXML(virNetworkPtr net)
+{
+    g_autoptr(xmlDoc) doc = NULL;
+    g_autoptr(xmlXPathContext) ctxt = NULL;
+    xmlNodePtr node;
+
+    g_autofree char *xml = NULL;
+
+    if (!(xml = virNetworkGetXMLDesc(net, 0)))
+        return NULL;
+
+    if (!(doc = virXMLParseStringCtxt(xml, "(network_definition)", &ctxt)))
+        return NULL;
+
+    if (!(node = virXPathNode("//metadata/*", ctxt)))
+        return NULL;
+
+    return virXMLNodeToString(node->doc, node);
+}
+
+
+static void
+metadataXMLConvertApostrophe(char *str)
+{
+    do {
+        if (*str == '\"')
+            *str = '\'';
+    } while ((*++str) != '\0');
+}
+
+
+static bool
+verifyMetadata(virNetworkPtr net,
+               const char *expectXML,
+               const char *expectAPI,
+               const char *uri)
+{
+    g_autofree char *metadataXML = NULL;
+    g_autofree char *metadataAPI = NULL;
+
+    if (!expectAPI) {
+        if ((metadataAPI = virNetworkGetMetadata(net,
+                                                VIR_NETWORK_METADATA_ELEMENT,
+                                                uri, 0))) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           "expected no metadata in API, but got:\n[%s]",
+                           metadataAPI);
+            return false;
+        }
+    } else {
+        if (!(metadataAPI = virNetworkGetMetadata(net,
+                                                 VIR_NETWORK_METADATA_ELEMENT,
+                                                 uri, 0)))
+            return false;
+
+        metadataXMLConvertApostrophe(metadataAPI);
+
+        if (STRNEQ(metadataAPI, expectAPI)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           "XML metadata in API doesn't match expected metadata: "
+                           "expected:\n[%s]\ngot:\n[%s]",
+                           expectAPI, metadataAPI);
+            return false;
+        }
+
+    }
+
+    if (!expectXML) {
+        if ((metadataXML = getMetadataFromXML(net))) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           "expected no metadata in XML, but got:\n[%s]",
+                           metadataXML);
+            return false;
+        }
+    } else {
+        if (!(metadataXML = getMetadataFromXML(net)))
+            return false;
+
+        metadataXMLConvertApostrophe(metadataXML);
+
+        if (STRNEQ(metadataXML, expectXML)) {
+            virReportError(VIR_ERR_INTERNAL_ERROR,
+                           "XML in dump doesn't match expected metadata: "
+                           "expected:\n[%s]\ngot:\n[%s]",
+                           expectXML, metadataXML);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+
+struct metadataTest {
+    virConnectPtr conn;
+    virNetworkPtr net;
+
+    const char *data;
+    const char *expect;
+    int type;
+    bool fail;
+};
+
+
+static int
+testAssignMetadata(const void *data)
+{
+    const struct metadataTest *test = data;
+
+    if (virNetworkSetMetadata(test->net, VIR_NETWORK_METADATA_ELEMENT,
+                             metadata1, "herp", "http://herp.derp/", 0) < 0)
+        return -1;
+
+    if (!verifyMetadata(test->net, metadata1_ns, metadata1, "http://herp.derp/"))
+        return -1;
+
+    return 0;
+}
+
+static int
+testRewriteMetadata(const void *data)
+{
+    const struct metadataTest *test = data;
+
+    if (virNetworkSetMetadata(test->net, VIR_NETWORK_METADATA_ELEMENT,
+                             metadata2, "blurb", "http://herp.derp/", 0) < 0)
+        return -1;
+
+    if (!verifyMetadata(test->net, metadata2_ns, metadata2, "http://herp.derp/"))
+        return -1;
+
+    return 0;
+}
+
+static int
+testEraseMetadata(const void *data)
+{
+    const struct metadataTest *test = data;
+
+    if (virNetworkSetMetadata(test->net, VIR_NETWORK_METADATA_ELEMENT,
+                             NULL, NULL, "http://herp.derp/", 0) < 0)
+        return -1;
+
+    if (!verifyMetadata(test->net, NULL, NULL, "http://herp.derp/"))
+        return -1;
+
+    return 0;
+}
+
+static int
+testTextMetadata(const void *data)
+{
+    const struct metadataTest *test = data;
+    g_autofree char *actual = NULL;
+
+    if (virNetworkSetMetadata(test->net, test->type, test->data, NULL, NULL, 0) < 0) {
+        if (test->fail)
+            return 0;
+        return -1;
+    }
+
+    actual = virNetworkGetMetadata(test->net, test->type, NULL, 0);
+
+    if (STRNEQ_NULLABLE(test->expect, actual)) {
+        virReportError(VIR_ERR_INTERNAL_ERROR,
+                       "expected metadata doesn't match actual: "
+                       "expected:'%s'\ngot: '%s'",
+                       NULLSTR(test->data), NULLSTR(actual));
+        return -1;
+    }
+
+    return 0;
+}
+
+#define TEST_TEXT_METADATA(INDEX, TYPE, DATA, EXPECT, FAIL) \
+    do { \
+        test.type = VIR_NETWORK_METADATA_ ## TYPE; \
+        test.data = DATA; \
+        test.expect = EXPECT; \
+        test.fail = FAIL; \
+ \
+        if (virTestRun("text metadata: " #TYPE " " INDEX " ", \
+                       testTextMetadata, &test) < 0) \
+            ret = EXIT_FAILURE; \
+    } while (0)
+
+#define TEST_TITLE(INDEX, DATA) \
+    TEST_TEXT_METADATA(INDEX, TITLE, DATA, DATA, false)
+#define TEST_TITLE_EXPECT(INDEX, DATA, EXPECT) \
+    TEST_TEXT_METADATA(INDEX, TITLE, DATA, EXPECT, false)
+#define TEST_TITLE_FAIL(INDEX, DATA) \
+    TEST_TEXT_METADATA(INDEX, TITLE, DATA, DATA, true)
+#define TEST_DESCR(INDEX, DATA) \
+    TEST_TEXT_METADATA(INDEX, DESCRIPTION, DATA, DATA, false)
+#define TEST_DESCR_EXPECT(INDEX, DATA, EXPECT) \
+    TEST_TEXT_METADATA(INDEX, DESCRIPTION, DATA, EXPECT, false)
+
+static int
+mymain(void)
+{
+    struct metadataTest test = { 0 };
+    int ret = EXIT_SUCCESS;
+
+    if (!(test.conn = virConnectOpen("test:///default")))
+        return EXIT_FAILURE;
+
+    if (!(test.net = virNetworkLookupByName(test.conn, "default"))) {
+        virConnectClose(test.conn);
+        return EXIT_FAILURE;
+    }
+
+    virTestQuiesceLibvirtErrors(false);
+
+    if (virTestRun("Assign metadata ", testAssignMetadata, &test) < 0)
+        ret = EXIT_FAILURE;
+    if (virTestRun("Rewrite Metadata ", testRewriteMetadata, &test) < 0)
+        ret = EXIT_FAILURE;
+    if (virTestRun("Erase metadata ", testEraseMetadata, &test) < 0)
+        ret = EXIT_FAILURE;
+
+    TEST_TITLE("1", "qwert");
+    TEST_TITLE("2", NULL);
+    TEST_TITLE("3", "blah");
+    TEST_TITLE_FAIL("4", "qwe\nrt");
+    TEST_TITLE_EXPECT("5", "", NULL);
+    TEST_TITLE_FAIL("6", "qwert\n");
+    TEST_TITLE_FAIL("7", "\n");
+
+    TEST_DESCR("1", "qwert\nqwert");
+    TEST_DESCR("2", NULL);
+    TEST_DESCR("3", "qwert");
+    TEST_DESCR("4", "\n");
+    TEST_DESCR_EXPECT("5", "", NULL);
+
+    virNetworkFree(test.net);
+    virConnectClose(test.conn);
+
+    return ret;
+}
+
+VIR_TEST_MAIN(mymain)
diff --git a/tools/virsh-network.c b/tools/virsh-network.c
index 42b7dba761..74712e29be 100644
--- a/tools/virsh-network.c
+++ b/tools/virsh-network.c
@@ -1206,7 +1206,8 @@ typedef struct virshNetEventData virshNetEventData;
 VIR_ENUM_DECL(virshNetworkEventId);
 VIR_ENUM_IMPL(virshNetworkEventId,
               VIR_NETWORK_EVENT_ID_LAST,
-              "lifecycle");
+              "lifecycle",
+              "metadata-change");
 
 static void
 vshEventLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
@@ -1239,9 +1240,84 @@ vshEventLifecyclePrint(virConnectPtr conn G_GNUC_UNUSED,
         vshEventDone(data->ctl);
 }
 
+static void G_GNUC_PRINTF(2, 3)
+virshEventPrintf(virshNetEventData *data,
+                 const char *fmt,
+                 ...)
+{
+    va_list ap;
+
+    if (!data->loop && data->count)
+        return;
+
+    if (data->timestamp) {
+        char timestamp[VIR_TIME_STRING_BUFLEN] = "";
+
+        ignore_value(virTimeStringNowRaw(timestamp));
+        vshPrint(data->ctl, "%s: ", timestamp);
+    }
+
+    va_start(ap, fmt);
+    vshPrintVa(data->ctl, fmt, ap);
+    va_end(ap);
+
+    (data->count)++;
+    if (!data->loop)
+        vshEventDone(data->ctl);
+}
+
+/**
+ * virshEventPrint:
+ *
+ * @data: opaque data passed to all event callbacks
+ * @buf: string buffer describing the event
+ *
+ * Print the event description found in @buf and update virshNetEventData.
+ *
+ * This function resets @buf and frees all memory consumed by its content.
+ */
+static void
+virshEventPrint(virshNetEventData *data,
+                virBuffer *buf)
+{
+    g_autofree char *msg = NULL;
+
+    if (!(msg = virBufferContentAndReset(buf)))
+        return;
+
+    virshEventPrintf(data, "%s", msg);
+}
+
+#define UNKNOWNSTR(str) (str ? str : N_("unsupported value"))
+
+VIR_ENUM_DECL(virshNetworkEventMetadataChangeType);
+VIR_ENUM_IMPL(virshNetworkEventMetadataChangeType,
+              VIR_NETWORK_METADATA_LAST,
+              N_("description"),
+              N_("title"),
+              N_("element"));
+
+static void
+vshEventMetadataChangePrint(virConnectPtr conn G_GNUC_UNUSED,
+                              virNetworkPtr net,
+                              int type,
+                              const char *nsuri,
+                              void *opaque)
+{
+    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+
+    virBufferAsprintf(&buf, _("event 'metadata-change' for network '%1$s': type %2$s, uri %3$s\n"),
+                      virNetworkGetName(net),
+                      UNKNOWNSTR(virshNetworkEventMetadataChangeTypeTypeToString(type)),
+                      NULLSTR(nsuri));
+    virshEventPrint(opaque, &buf);
+}
+
 virshNetworkEventCallback virshNetworkEventCallbacks[] = {
     { "lifecycle",
       VIR_NETWORK_EVENT_CALLBACK(vshEventLifecyclePrint), },
+    { "metadata-change",
+      VIR_NETWORK_EVENT_CALLBACK(vshEventMetadataChangePrint), },
 };
 G_STATIC_ASSERT(VIR_NETWORK_EVENT_ID_LAST == G_N_ELEMENTS(virshNetworkEventCallbacks));
 
-- 
2.41.0
Re: [libvirt PATCH] Metadata support for Network Objects
Posted by Michal Prívozník 10 months, 3 weeks ago
On 6/11/23 21:44, K Shiva wrote:
> Adds support for the following to Network Object:
> - <metadata>, <title> and <description> added to Network obj Schema.
> - Public Get and Set APIs, being virNetworkSetMetadata() &
>   virNetworkGetMetadata().
> - An async callback that notifies of changes to Network metadata.
> 
> Resolves (GSoC 2023): https://wiki.libvirt.org/Google_Summer_of_Code_Ideas.html
> Signed-off-by: K Shiva <shiva_kr@riseup.net>
> ---
>  include/libvirt/libvirt-domain.h    |   2 +-
>  include/libvirt/libvirt-network.h   |  51 ++++
>  include/libvirt/virterror.h         |   2 +
>  po/POTFILES                         |   1 +
>  src/conf/network_conf.c             |   3 +
>  src/conf/network_conf.h             |   2 +
>  src/conf/network_event.c            | 115 +++++++++
>  src/conf/network_event.h            |  11 +
>  src/conf/virnetworkobj.c            | 347 ++++++++++++++++++++++++++--
>  src/conf/virnetworkobj.h            |  56 +++++
>  src/driver-network.h                |  16 ++
>  src/libvirt-network.c               | 167 +++++++++++++
>  src/libvirt_public.syms             |   6 +
>  src/remote/remote_daemon_dispatch.c |  39 ++++
>  src/remote/remote_driver.c          |  32 +++
>  src/remote/remote_protocol.x        |  15 +-
>  src/remote_protocol-structs         |   6 +
>  src/test/test_driver.c              |  74 ++++++
>  src/util/virerror.c                 |   3 +
>  tests/meson.build                   |   1 +
>  tests/networkmetadatatest.c         | 297 ++++++++++++++++++++++++
>  tools/virsh-network.c               |  78 ++++++-
>  22 files changed, 1299 insertions(+), 25 deletions(-)
>  create mode 100644 tests/networkmetadatatest.c

There's too much happening in this patch. I suggest you split it into
smaller ones. For instance: in the first introduce just XML
parsing/formatting and saving into the XML file stored on the disk. In
the second introduce public APIs, in the fourth virsh, then in another
one event, etc.

The rule of the thumb here is to put yourself into shoes of reviewer and
ask yourself "Would I be comfortable reviewing this patch".

Michal
Re: [libvirt PATCH] Metadata support for Network Objects
Posted by K Shiva 10 months, 3 weeks ago
On 23/06/12 01:14AM, K Shiva wrote:
> Adds support for the following to Network Object:
> - <metadata>, <title> and <description> added to Network obj Schema.
> - Public Get and Set APIs, being virNetworkSetMetadata() &
>   virNetworkGetMetadata().
> - An async callback that notifies of changes to Network metadata.
> 
> Resolves (GSoC 2023): https://wiki.libvirt.org/Google_Summer_of_Code_Ideas.html
> Signed-off-by: K Shiva <shiva_kr@riseup.net>
> ---
>  include/libvirt/libvirt-domain.h    |   2 +-
>  include/libvirt/libvirt-network.h   |  51 ++++
 
Function names among others were corrected in the comments.
Link to v2:
https://listman.redhat.com/archives/libvir-list/2023-June/240444.html

Shiva
[libvirt PATCH v2 2/2] Corrected names in comments
Posted by K Shiva 11 months, 1 week ago
Signed-off-by: K Shiva <shiva_kr@riseup.net>
---
This patch applies to: https://listman.redhat.com/archives/libvir-list/2023-June/240299.html
 src/conf/virnetworkobj.c |  8 ++++----
 src/libvirt-network.c    | 10 +++++-----
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/conf/virnetworkobj.c b/src/conf/virnetworkobj.c
index f413b54425..82f90937bc 100644
--- a/src/conf/virnetworkobj.c
+++ b/src/conf/virnetworkobj.c
@@ -1808,7 +1808,7 @@ virNetworkObjLoadAllPorts(virNetworkObj *net,
  * @net: network object
  * @flags: flags to update the modification impact on
  *
- * Resolves virNetworkModificationImpact flags in @flags so that they correctly
+ * Resolves virNetworkUpdateFlags in @flags so that they correctly
  * apply to the actual state of @net. @flags may be modified after call to this
  * function.
  *
@@ -1850,7 +1850,7 @@ virNetworkObjUpdateModificationImpact(virNetworkObj *net,
  * virNetworkObjGetDefs:
  *
  * @net: network object
- * @flags: for virNetworkModificationImpact
+ * @flags: for virNetworkUpdateFlags
  * @liveDef: Set the pointer to the live definition of @net.
  * @persDef: Set the pointer to the config definition of @net.
  *
@@ -1898,7 +1898,7 @@ virNetworkObjGetDefs(virNetworkObj *net,
  * virNetworkObjGetOneDefState:
  *
  * @net: Network object
- * @flags: for virNetworkModificationImpact
+ * @flags: for virNetworkUpdateFlags
  * @live: set to true if live config was returned (may be omitted)
  *
  * Helper function to resolve @flags and return the correct network pointer
@@ -1941,7 +1941,7 @@ virNetworkObjGetOneDefState(virNetworkObj *net,
  * virNetworkObjGetOneDef:
  *
  * @net: Network object
- * @flags: for virNetworkModificationImpact
+ * @flags: for virNetworkUpdateFlags
  *
  * Helper function to resolve @flags and return the correct network pointer
  * object. This function returns one of @net->def or @net->persistentDef
diff --git a/src/libvirt-network.c b/src/libvirt-network.c
index 5f4e088421..c0c66bb2fa 100644
--- a/src/libvirt-network.c
+++ b/src/libvirt-network.c
@@ -1924,7 +1924,7 @@ virNetworkPortRef(virNetworkPortPtr port)
  * @metadata: new metadata text
  * @key: XML namespace key, or NULL
  * @uri: XML namespace URI, or NULL
- * @flags: bitwise-OR of virDomainModificationImpact
+ * @flags: bitwise-OR of virNetworkUpdateFlags
  *
  * Sets the appropriate network element given by @type to the
  * value of @metadata.  A @type of VIR_NETWORK_METADATA_DESCRIPTION
@@ -1937,12 +1937,12 @@ virNetworkPortRef(virNetworkPortPtr port)
  * XML belonging to namespace defined by @uri with local name @key.
  *
  * Passing NULL for @metadata says to remove that element from the
- * domain XML (passing the empty string leaves the element present).
+ * network XML (passing the empty string leaves the element present).
  *
  * The resulting metadata will be present in virNetworkGetXMLDesc(),
  * as well as quick access through virNetworkGetMetadata().
  *
- * @flags controls whether the live domain, persistent configuration,
+ * @flags controls whether the live network state, persistent configuration,
  * or both will be modified.
  *
  * Returns 0 on success, -1 in case of failure.
@@ -2015,7 +2015,7 @@ virNetworkSetMetadata(virNetworkPtr network,
  * @network: a network object
  * @type: type of metadata, from virNetworkMetadataType
  * @uri: XML namespace identifier
- * @flags: bitwise-OR of virDomainModificationImpact
+ * @flags: bitwise-OR of virNetworkUpdateFlags
  *
  * Retrieves the appropriate network element given by @type.
  * If VIR_NETWORK_METADATA_ELEMENT is requested parameter @uri
@@ -2027,7 +2027,7 @@ virNetworkSetMetadata(virNetworkPtr network,
  * a shortcut for seeing information from virNetworkSetMetadata()
  * without having to go through virNetworkGetXMLDesc().
  *
- * @flags controls whether the live domain or persistent
+ * @flags controls whether the live network state or persistent
  * configuration will be queried.
  *
  * Returns the metadata string on success (caller must free),
-- 
2.41.0
Re: [libvirt PATCH v2 2/2] Corrected names in comments
Posted by Michal Prívozník 10 months, 3 weeks ago
On 6/14/23 07:21, K Shiva wrote:
> Signed-off-by: K Shiva <shiva_kr@riseup.net>
> ---
> This patch applies to: https://listman.redhat.com/archives/libvir-list/2023-June/240299.html
>  src/conf/virnetworkobj.c |  8 ++++----
>  src/libvirt-network.c    | 10 +++++-----
>  2 files changed, 9 insertions(+), 9 deletions(-)

If you notice any problem with your earlier patch, it's common practice
to send the next version fully (i.e. no diffs to your patches) and
ideally reply to the previous version stating what's wrong and that
you've sent next version. For instance:

A reply to the original patch (say v2):

  Oh, this leaks memory in case when user passes flags X and Y. Fixed
version can be found here:

  https://listman.redhat.com/archives/libvir-list/.....


And in the v3 place a note:

  ${commitMessage}

  Signed-off-by: John Doe <jdoe@example.com>
  ---

  v3 of:

  ${linkToV2}

  diff to v2:
  - Fixed a memleak when X and Y are passed

  ${diffStatHere}


You found the right place - it's exactly where you put your comment.

Michal