Adds the following to Network Object:
- <metadata>, <title> and <description> to the Network Schema.
- Get and Set APIs to access or modify the above.
- An async callback that notifies of metadata changes.
Resolves (GSoC 2023):
https://wiki.libvirt.org/Google_Summer_of_Code_Ideas.html
Signed-off-by: K Shiva <shiva_kr(a)riseup.net>
---
This is a v2 of:
https://listman.redhat.com/archives/libvir-list/2023-June/240299.html
Changes from v1:
- Corrected names in comments
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..82f90937bc 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 virNetworkUpdateFlags 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 virNetworkUpdateFlags
+ * @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 virNetworkUpdateFlags
+ * @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 virNetworkUpdateFlags
+ *
+ * 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..c0c66bb2fa 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 virNetworkUpdateFlags
+ *
+ * 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
+ * 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 network state, 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 virNetworkUpdateFlags
+ *
+ * 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 network state 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