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@xxxxxxxxxx> --- 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