[libvirt PATCH] Add Public Get and Set APIs for Network Metadata
by K Shiva Kiran
This patch introduces public Get and Set APIs for modifying <title>,
<description> and <metadata> elements of the Network object.
Added:
- enum to select one of the above elements to operate on.
- error code and messages for missing metadata.
- public API implementation.
- driver support.
- wire protocol format.
- enums and structs for RPC.
Signed-off-by: K Shiva Kiran <shiva_kr(a)riseup.net>
---
include/libvirt/libvirt-network.h | 29 ++++++
include/libvirt/virterror.h | 1 +
src/driver-network.h | 16 +++
src/libvirt-network.c | 167 ++++++++++++++++++++++++++++++
src/libvirt_public.syms | 6 ++
src/remote/remote_driver.c | 2 +
src/remote/remote_protocol.x | 36 ++++++-
src/remote_protocol-structs | 19 ++++
src/util/virerror.c | 3 +
9 files changed, 278 insertions(+), 1 deletion(-)
diff --git a/include/libvirt/libvirt-network.h b/include/libvirt/libvirt-network.h
index 90cde0cf24..4102f0ecb4 100644
--- a/include/libvirt/libvirt-network.h
+++ b/include/libvirt/libvirt-network.h
@@ -547,4 +547,33 @@ 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);
+
#endif /* LIBVIRT_NETWORK_H */
diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h
index df13e4f11e..ba5d3bb95c 100644
--- a/include/libvirt/virterror.h
+++ b/include/libvirt/virterror.h
@@ -348,6 +348,7 @@ 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/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..451cac4dfe 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_driver.c b/src/remote/remote_driver.c
index 65ec239fb7..658a6980d5 100644
--- a/src/remote/remote_driver.c
+++ b/src/remote/remote_driver.c
@@ -8160,6 +8160,8 @@ static virNetworkDriver network_driver = {
.networkPortSetParameters = remoteNetworkPortSetParameters, /* 5.5.0 */
.networkPortGetParameters = remoteNetworkPortGetParameters, /* 5.5.0 */
.networkPortDelete = remoteNetworkPortDelete, /* 5.5.0 */
+ .networkSetMetadata = remoteNetworkSetMetadata, /* 9.5.0 */
+ .networkGetMetadata = remoteNetworkGetMetadata, /* 9.5.0 */
};
static virInterfaceDriver interface_driver = {
diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x
index 5d86a51116..7ff059e393 100644
--- a/src/remote/remote_protocol.x
+++ b/src/remote/remote_protocol.x
@@ -3323,6 +3323,26 @@ struct remote_network_event_lifecycle_msg {
int detail;
};
+struct remote_network_set_metadata_args {
+ remote_nonnull_network network;
+ int type;
+ remote_string metadata;
+ remote_string key;
+ remote_string uri;
+ unsigned int flags;
+};
+
+struct remote_network_get_metadata_args {
+ remote_nonnull_network network;
+ int type;
+ remote_string uri;
+ unsigned int flags;
+};
+
+struct remote_network_get_metadata_ret {
+ remote_nonnull_string metadata;
+};
+
struct remote_connect_storage_pool_event_register_any_args {
int eventID;
remote_storage_pool pool;
@@ -6974,5 +6994,19 @@ enum remote_procedure {
* @generate: none
* @acl: domain:write
*/
- REMOTE_PROC_DOMAIN_FD_ASSOCIATE = 443
+ REMOTE_PROC_DOMAIN_FD_ASSOCIATE = 443,
+
+ /**
+ * @generate: both
+ * @acl: network:write
+ * @acl: network:save:!VIR_NETWORK_UPDATE_AFFECT_CONFIG|VIR_NETWORK_UPDATE_AFFECT_LIVE
+ * @acl: network:save:VIR_NETWORK_UPDATE_AFFECT_CONFIG
+ */
+ REMOTE_PROC_NETWORK_SET_METADATA = 444,
+
+ /**
+ * @generate: both
+ * @acl: network:read
+ */
+ REMOTE_PROC_NETWORK_GET_METADATA = 445
};
diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs
index 3c6c230a16..14898a0bc7 100644
--- a/src/remote_protocol-structs
+++ b/src/remote_protocol-structs
@@ -3130,6 +3130,23 @@ struct remote_network_port_delete_args {
remote_nonnull_network_port port;
u_int flags;
};
+struct remote_network_set_metadata_args {
+ remote_nonnull_network network;
+ int type;
+ remote_string metadata;
+ remote_string key;
+ remote_string uri;
+ u_int flags;
+};
+struct remote_network_get_metadata_args {
+ remote_nonnull_network network;
+ int type;
+ remote_string uri;
+ u_int flags;
+};
+struct remote_network_get_metadata_ret {
+ remote_nonnull_string metadata;
+};
struct remote_domain_checkpoint_create_xml_args {
remote_nonnull_domain dom;
remote_nonnull_string xml_desc;
@@ -3717,4 +3734,6 @@ enum remote_procedure {
REMOTE_PROC_DOMAIN_RESTORE_PARAMS = 441,
REMOTE_PROC_DOMAIN_ABORT_JOB_FLAGS = 442,
REMOTE_PROC_DOMAIN_FD_ASSOCIATE = 443,
+ REMOTE_PROC_NETWORK_SET_METADATA = 444,
+ REMOTE_PROC_NETWORK_GET_METADATA = 445
};
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);
--
2.41.0
1 year, 6 months
[PATCH v5] vfio/pci: Propagate ACPI notifications to user-space via eventfd
by Grzegorz Jaszczyk
To allow pass-through devices receiving ACPI notifications, permit to
register ACPI notify handler (via VFIO_DEVICE_SET_IRQS) for a given
device. The handler role is to receive and propagate such ACPI
notifications to the user-space through the user provided eventfd. This
allows VMM to receive and propagate them further to the VM, where the
actual driver for pass-through device resides and can react to device
specific notifications accordingly.
The eventfd usage ensures VMM and device isolation: it allows to use a
dedicated channel associated with the device for such events, such that
the VMM has direct access.
Since the eventfd counter is used as ACPI notification value
placeholder, the eventfd signaling needs to be serialized in order to
not end up with notification values being coalesced. Therefore ACPI
notification values are buffered and signalized one by one, when the
previous notification value has been consumed.
Signed-off-by: Grzegorz Jaszczyk <jaz(a)semihalf.com>
---
Changelog v4..v5
Address Alex Williamson's feedback:
- s/vfio_acpi_notify.{c,o}/acpi_notify.{c,o}
- Do not put acpi_notify to its own module but fold it into main
vfio.ko. Additionally select it from VFIO_PCI_CORE instead of VFIO_PCI.
- Cleanup acpi notify under igate mutex (in vfio_pci_core_close_device).
- Add extra check for ACPI companion in vfio_pci_get_irq_count and
extend vfio_pci_ioctl_get_irq_info.
- Drop acpi.h include - linux/vfio_acpi_notify.h includes it already.
- Send device check notification value for DATA_NONE and non-zero count
and for DATA_BOOL and non-zero count (as for loopback testing).
- Drop some redundant !acpi_notify->acpi_notify_trigger checks.
- Move some common code to new helper functions:
1) acpi_notification_dequeue
2) vfio_acpi_notify_cleanup and rename previous
vfio_acpi_notify_cleanup into vfio_remove_acpi_notify which uses it
- Add rate limited logging for dropped notifications.
- Move vdev->acpi_notification pointer cleanup to the
vfio_acpi_notify_cleanup function this also fixes two bigger issues
caught by Alex.
- Allow the eventfd to be swapped.
- s/GFP_KERNEL/GFP_KERNEL_ACCOUNT.
- s/VFIO_PCI_ACPI_NTFY_IRQ_INDEX/VFIO_PCI_ACPI_IRQ_INDEX.
- Add header protection for multiple includes.
- v4: https://patchwork.kernel.org/project/kvm/patch/20230522165811.123417-1-ja...
Changelog v3..v4
Address Alex Williamson feedback:
- Instead of introducing new ioctl used for eventfd registration, take
advantage of VFIO_DEVICE_SET_IRQS which already supports virtual IRQs
for things like error notification and device release requests.
- Introduced mechanism preventing creation of large queues.
Other:
- Move the implementation into the newly introduced VFIO_ACPI_NOTIFY
helper module. It is actually not bound to VFIO_PCI but VFIO_PCI
enables it whenever ACPI support is enabled. This change is introduced
since ACPI notifications are not limited to PCI devices, making it PCI
independent will allow to re-use it also for other VFIO_* like
supports: e.g. VFIO_PLATFORM in the future if needed. Moving it out of
drivers/vfio/pci/ was also suggested offline.
- s/notify_val_next/node
- v3: https://patchwork.kernel.org/project/kvm/patch/20230502132700.654528-1-ja...
Changelog v2..v3:
- Fix compilation warnings when building with "W=1"
Changelog v1..v2:
- The v2 implementation is actually completely different then v1:
instead of using acpi netlink events for propagating ACPI
notifications to the user space take advantage of eventfd, which can
provide better VMM and device isolation: it allows to use a dedicated
channel associated with the device for such events, such that the VMM
has direct access.
- Using eventfd counter as notification value placeholder was suggested
in v1 and requires additional serialization logic introduced in v2.
- Since the vfio-pci supports non-ACPI platforms address !CONFIG_ACPI
case.
- v1 discussion: https://patchwork.kernel.org/project/kvm/patch/20230307220553.631069-1-ja...
---
drivers/vfio/Kconfig | 5 +
drivers/vfio/Makefile | 1 +
drivers/vfio/acpi_notify.c | 249 ++++++++++++++++++++++++++++++
drivers/vfio/pci/Kconfig | 1 +
drivers/vfio/pci/vfio_pci_core.c | 13 ++
drivers/vfio/pci/vfio_pci_intrs.c | 85 ++++++++++
include/linux/vfio_acpi_notify.h | 45 ++++++
include/linux/vfio_pci_core.h | 1 +
include/uapi/linux/vfio.h | 1 +
9 files changed, 401 insertions(+)
create mode 100644 drivers/vfio/acpi_notify.c
create mode 100644 include/linux/vfio_acpi_notify.h
diff --git a/drivers/vfio/Kconfig b/drivers/vfio/Kconfig
index 89e06c981e43..cd9df43a4eb4 100644
--- a/drivers/vfio/Kconfig
+++ b/drivers/vfio/Kconfig
@@ -12,6 +12,11 @@ menuconfig VFIO
If you don't know what to do here, say N.
if VFIO
+config VFIO_ACPI_NOTIFY
+ bool
+ depends on ACPI
+ default n
+
config VFIO_CONTAINER
bool "Support for the VFIO container /dev/vfio/vfio"
select VFIO_IOMMU_TYPE1 if MMU && (X86 || S390 || ARM || ARM64)
diff --git a/drivers/vfio/Makefile b/drivers/vfio/Makefile
index 70e7dcb302ef..003c2b041785 100644
--- a/drivers/vfio/Makefile
+++ b/drivers/vfio/Makefile
@@ -7,6 +7,7 @@ vfio-y += vfio_main.o \
vfio-$(CONFIG_IOMMUFD) += iommufd.o
vfio-$(CONFIG_VFIO_CONTAINER) += container.o
vfio-$(CONFIG_VFIO_VIRQFD) += virqfd.o
+vfio-$(CONFIG_VFIO_ACPI_NOTIFY) += acpi_notify.o
obj-$(CONFIG_VFIO_IOMMU_TYPE1) += vfio_iommu_type1.o
obj-$(CONFIG_VFIO_IOMMU_SPAPR_TCE) += vfio_iommu_spapr_tce.o
diff --git a/drivers/vfio/acpi_notify.c b/drivers/vfio/acpi_notify.c
new file mode 100644
index 000000000000..8d3f063502fe
--- /dev/null
+++ b/drivers/vfio/acpi_notify.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VFIO ACPI notification propagation
+ *
+ * Author: Grzegorz Jaszczyk <jaz(a)semihalf.com>
+ */
+#include <linux/vfio_acpi_notify.h>
+
+#define NOTIFICATION_QUEUE_SIZE 20
+
+struct notification_queue {
+ int notification_val;
+ struct list_head node;
+};
+
+static int vfio_eventfd_wakeup(wait_queue_entry_t *wait, unsigned int mode,
+ int sync, void *key)
+{
+ struct vfio_acpi_notification *acpi_notify =
+ container_of(wait, struct vfio_acpi_notification, wait);
+ __poll_t flags = key_to_poll(key);
+
+ /*
+ * eventfd_read signalize EPOLLOUT at the end of its function - this
+ * means previous eventfd with its notification value was consumed so
+ * the next notification can be signalized now if pending - schedule
+ * proper work.
+ */
+ if (flags & EPOLLOUT) {
+ mutex_unlock(&acpi_notify->notification_lock);
+ schedule_work(&acpi_notify->acpi_notification_work);
+ }
+
+ return 0;
+}
+
+static void vfio_ptable_queue_proc(struct file *file,
+ wait_queue_head_t *wqh, poll_table *pt)
+{
+ struct vfio_acpi_notification *acpi_notify =
+ container_of(pt, struct vfio_acpi_notification, pt);
+
+ add_wait_queue(wqh, &acpi_notify->wait);
+}
+
+static struct notification_queue *
+acpi_notification_dequeue(struct vfio_acpi_notification *acpi_notify)
+{
+ struct notification_queue *oldest_entry;
+
+ oldest_entry = list_first_entry(&acpi_notify->notification_list,
+ struct notification_queue,
+ node);
+ list_del(&oldest_entry->node);
+ acpi_notify->notification_queue_count--;
+
+ return oldest_entry;
+}
+
+static void acpi_notification_work_fn(struct work_struct *work)
+{
+ struct vfio_acpi_notification *acpi_notify;
+ struct notification_queue *entry;
+
+ acpi_notify = container_of(work, struct vfio_acpi_notification,
+ acpi_notification_work);
+
+ mutex_lock(&acpi_notify->notification_list_lock);
+ if (list_empty(&acpi_notify->notification_list))
+ goto out;
+
+ /*
+ * If the previous eventfd was not yet consumed by user-space lets hold
+ * on and exit. The notification function will be rescheduled when
+ * signaling eventfd will be possible (when the EPOLLOUT will be
+ * signalized and unlocks notify_events).
+ */
+ if (!mutex_trylock(&acpi_notify->notification_lock))
+ goto out;
+
+ entry = acpi_notification_dequeue(acpi_notify);
+
+ mutex_unlock(&acpi_notify->notification_list_lock);
+
+ eventfd_signal(acpi_notify->acpi_notify_trigger, entry->notification_val);
+
+ kfree(entry);
+
+ return;
+out:
+ mutex_unlock(&acpi_notify->notification_list_lock);
+}
+
+static void
+vfio_acpi_notify_cleanup(struct vfio_acpi_notification **acpi_notify_ptr,
+ struct acpi_device *adev)
+{
+ struct vfio_acpi_notification *acpi_notify = *acpi_notify_ptr;
+ struct notification_queue *entry, *entry_tmp;
+ u64 cnt;
+
+ eventfd_ctx_remove_wait_queue(acpi_notify->acpi_notify_trigger,
+ &acpi_notify->wait, &cnt);
+
+ flush_work(&acpi_notify->acpi_notification_work);
+
+ mutex_lock(&acpi_notify->notification_list_lock);
+ list_for_each_entry_safe(entry, entry_tmp,
+ &acpi_notify->notification_list,
+ node) {
+ list_del(&entry->node);
+ kfree(entry);
+ }
+ mutex_unlock(&acpi_notify->notification_list_lock);
+
+ eventfd_ctx_put(acpi_notify->acpi_notify_trigger);
+
+ kfree(acpi_notify);
+
+ *acpi_notify_ptr = NULL;
+}
+
+void vfio_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct vfio_acpi_notification *acpi_notify = (struct vfio_acpi_notification *)data;
+ struct notification_queue *entry;
+
+ entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return;
+
+ entry->notification_val = event;
+ INIT_LIST_HEAD(&entry->node);
+
+ mutex_lock(&acpi_notify->notification_list_lock);
+ if (acpi_notify->notification_queue_count > NOTIFICATION_QUEUE_SIZE) {
+ struct notification_queue *oldest_entry =
+ acpi_notification_dequeue(acpi_notify);
+
+ if (printk_ratelimit())
+ acpi_handle_warn(handle,
+ "dropping notification value %d\n",
+ oldest_entry->notification_val);
+
+ kfree(oldest_entry);
+ }
+ list_add_tail(&entry->node, &acpi_notify->notification_list);
+ acpi_notify->notification_queue_count++;
+ mutex_unlock(&acpi_notify->notification_list_lock);
+
+ schedule_work(&acpi_notify->acpi_notification_work);
+}
+EXPORT_SYMBOL_GPL(vfio_acpi_notify);
+
+void vfio_remove_acpi_notify(struct vfio_acpi_notification **acpi_notify_ptr,
+ struct acpi_device *adev)
+{
+ struct vfio_acpi_notification *acpi_notify = *acpi_notify_ptr;
+
+ if (!acpi_notify)
+ return;
+
+ acpi_remove_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
+ vfio_acpi_notify);
+
+ vfio_acpi_notify_cleanup(acpi_notify_ptr, adev);
+}
+EXPORT_SYMBOL_GPL(vfio_remove_acpi_notify);
+
+int vfio_register_acpi_notify_handler(struct vfio_acpi_notification **acpi_notify_ptr,
+ struct acpi_device *adev, int32_t fd)
+{
+ struct vfio_acpi_notification *acpi_notify = *acpi_notify_ptr;
+ struct file *acpi_notify_trigger_file;
+ struct eventfd_ctx *efdctx;
+ acpi_status status;
+
+ if (fd < -1)
+ return -EINVAL;
+ else if (fd == -1) {
+ vfio_remove_acpi_notify(acpi_notify_ptr, adev);
+ return 0;
+ }
+
+ efdctx = eventfd_ctx_fdget(fd);
+ if (IS_ERR(efdctx))
+ return PTR_ERR(efdctx);
+
+ /* Allow eventfd to be swapped */
+ if (acpi_notify) {
+ u64 cnt;
+
+ acpi_notify_trigger_file = eventfd_fget(fd);
+
+ mutex_lock(&acpi_notify->notification_lock);
+ eventfd_ctx_remove_wait_queue(acpi_notify->acpi_notify_trigger,
+ &acpi_notify->wait, &cnt);
+ eventfd_ctx_put(acpi_notify->acpi_notify_trigger);
+ acpi_notify->acpi_notify_trigger = efdctx;
+ vfs_poll(acpi_notify_trigger_file, &acpi_notify->pt);
+ mutex_unlock(&acpi_notify->notification_lock);
+
+ /*
+ * The ACPI notifications could arrive and be queued during
+ * eventfd swap, retrigger the worker after notification
+ * replication unlocking.
+ */
+ schedule_work(&acpi_notify->acpi_notification_work);
+
+ return 0;
+ }
+
+ acpi_notify = kzalloc(sizeof(*acpi_notify), GFP_KERNEL_ACCOUNT);
+ if (!acpi_notify)
+ return -ENOMEM;
+
+ *acpi_notify_ptr = acpi_notify;
+
+ INIT_WORK(&acpi_notify->acpi_notification_work, acpi_notification_work_fn);
+ INIT_LIST_HEAD(&acpi_notify->notification_list);
+
+ acpi_notify->acpi_notify_trigger = efdctx;
+
+ mutex_init(&acpi_notify->notification_lock);
+
+ /*
+ * Install custom wake-up handler to be notified whenever underlying
+ * eventfd is consumed by the user-space.
+ */
+ init_waitqueue_func_entry(&acpi_notify->wait, vfio_eventfd_wakeup);
+ init_poll_funcptr(&acpi_notify->pt, vfio_ptable_queue_proc);
+
+ acpi_notify_trigger_file = eventfd_fget(fd);
+ vfs_poll(acpi_notify_trigger_file, &acpi_notify->pt);
+
+ status = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
+ vfio_acpi_notify, (void *)acpi_notify);
+ if (ACPI_FAILURE(status)) {
+ dev_err(&adev->dev, "Failed to install notify handler: %s",
+ acpi_format_exception(status));
+
+ vfio_acpi_notify_cleanup(acpi_notify_ptr, adev);
+
+ return -ENODEV;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(vfio_register_acpi_notify_handler);
diff --git a/drivers/vfio/pci/Kconfig b/drivers/vfio/pci/Kconfig
index f9d0c908e738..f03ca773dfd9 100644
--- a/drivers/vfio/pci/Kconfig
+++ b/drivers/vfio/pci/Kconfig
@@ -4,6 +4,7 @@ config VFIO_PCI_CORE
tristate
select VFIO_VIRQFD
select IRQ_BYPASS_MANAGER
+ select VFIO_ACPI_NOTIFY if ACPI
config VFIO_PCI_MMAP
def_bool y if !S390
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index a5ab416cf476..1cc4a9c05403 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -27,6 +27,7 @@
#include <linux/vgaarb.h>
#include <linux/nospec.h>
#include <linux/sched/mm.h>
+#include <linux/vfio_acpi_notify.h>
#if IS_ENABLED(CONFIG_EEH)
#include <asm/eeh.h>
#endif
@@ -683,6 +684,7 @@ void vfio_pci_core_close_device(struct vfio_device *core_vdev)
{
struct vfio_pci_core_device *vdev =
container_of(core_vdev, struct vfio_pci_core_device, vdev);
+ struct acpi_device *adev = ACPI_COMPANION(&vdev->pdev->dev);
if (vdev->sriov_pf_core_dev) {
mutex_lock(&vdev->sriov_pf_core_dev->vf_token->lock);
@@ -704,6 +706,8 @@ void vfio_pci_core_close_device(struct vfio_device *core_vdev)
eventfd_ctx_put(vdev->req_trigger);
vdev->req_trigger = NULL;
}
+ if (adev)
+ vfio_remove_acpi_notify(&vdev->acpi_notification, adev);
mutex_unlock(&vdev->igate);
}
EXPORT_SYMBOL_GPL(vfio_pci_core_close_device);
@@ -725,6 +729,8 @@ EXPORT_SYMBOL_GPL(vfio_pci_core_finish_enable);
static int vfio_pci_get_irq_count(struct vfio_pci_core_device *vdev, int irq_type)
{
+ struct acpi_device *adev = ACPI_COMPANION(&vdev->pdev->dev);
+
if (irq_type == VFIO_PCI_INTX_IRQ_INDEX) {
u8 pin;
@@ -761,6 +767,8 @@ static int vfio_pci_get_irq_count(struct vfio_pci_core_device *vdev, int irq_typ
return 1;
} else if (irq_type == VFIO_PCI_REQ_IRQ_INDEX) {
return 1;
+ } else if (adev && irq_type == VFIO_PCI_ACPI_IRQ_INDEX) {
+ return 1;
}
return 0;
@@ -1084,6 +1092,7 @@ static int vfio_pci_ioctl_get_irq_info(struct vfio_pci_core_device *vdev,
struct vfio_irq_info __user *arg)
{
unsigned long minsz = offsetofend(struct vfio_irq_info, count);
+ struct acpi_device *adev = ACPI_COMPANION(&vdev->pdev->dev);
struct vfio_irq_info info;
if (copy_from_user(&info, arg, minsz))
@@ -1096,6 +1105,10 @@ static int vfio_pci_ioctl_get_irq_info(struct vfio_pci_core_device *vdev,
case VFIO_PCI_INTX_IRQ_INDEX ... VFIO_PCI_MSIX_IRQ_INDEX:
case VFIO_PCI_REQ_IRQ_INDEX:
break;
+ case VFIO_PCI_ACPI_IRQ_INDEX:
+ if (adev)
+ break;
+ return -EINVAL;
case VFIO_PCI_ERR_IRQ_INDEX:
if (pci_is_pcie(vdev->pdev))
break;
diff --git a/drivers/vfio/pci/vfio_pci_intrs.c b/drivers/vfio/pci/vfio_pci_intrs.c
index bffb0741518b..410f517f8b2c 100644
--- a/drivers/vfio/pci/vfio_pci_intrs.c
+++ b/drivers/vfio/pci/vfio_pci_intrs.c
@@ -19,6 +19,7 @@
#include <linux/vfio.h>
#include <linux/wait.h>
#include <linux/slab.h>
+#include <linux/vfio_acpi_notify.h>
#include "vfio_pci_priv.h"
@@ -667,6 +668,76 @@ static int vfio_pci_set_req_trigger(struct vfio_pci_core_device *vdev,
count, flags, data);
}
+static int
+vfio_pci_set_acpi_ntfy_trigger(struct vfio_pci_core_device *vdev,
+ unsigned int index, unsigned int start,
+ unsigned int count, uint32_t flags, void *data)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&vdev->pdev->dev);
+ acpi_handle handle;
+
+ if (index != VFIO_PCI_ACPI_IRQ_INDEX || start != 0 || count > 1)
+ return -EINVAL;
+
+ if (!adev)
+ return -ENODEV;
+
+ if (!vdev->acpi_notification)
+ return -EINVAL;
+
+#if IS_ENABLED(CONFIG_ACPI)
+ handle = adev->handle;
+#endif
+
+ /*
+ * Disable notifications: flags = (DATA_NONE|ACTION_TRIGGER), count = 0
+ * Enable loopback testing: (DATA_BOOL|ACTION_TRIGGER) or
+ * (DATA_NONE|ACTION_TRIGGER), count != 0
+ */
+ if (flags & VFIO_IRQ_SET_DATA_NONE) {
+ if (count)
+ vfio_acpi_notify(handle, ACPI_NOTIFY_DEVICE_CHECK,
+ vdev->acpi_notification);
+ else
+ vfio_remove_acpi_notify(&vdev->acpi_notification, adev);
+
+ return 0;
+ } else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
+ uint8_t trigger;
+
+ if (!count)
+ return -EINVAL;
+
+ trigger = *(uint8_t *)data;
+ if (trigger)
+ vfio_acpi_notify(handle, ACPI_NOTIFY_DEVICE_CHECK,
+ vdev->acpi_notification);
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int
+vfio_pci_set_acpi_ntfy_eventfd_trigger(struct vfio_pci_core_device *vdev,
+ unsigned int index, unsigned int start,
+ unsigned int count, uint32_t flags, void *data)
+{
+ struct acpi_device *adev = ACPI_COMPANION(&vdev->pdev->dev);
+ int32_t fd;
+
+ if (index != VFIO_PCI_ACPI_IRQ_INDEX || start != 0 || count != 1)
+ return -EINVAL;
+
+ if (!adev)
+ return -ENODEV;
+
+ fd = *(int32_t *)data;
+
+ return vfio_register_acpi_notify_handler(&vdev->acpi_notification, adev, fd);
+}
+
int vfio_pci_set_irqs_ioctl(struct vfio_pci_core_device *vdev, uint32_t flags,
unsigned index, unsigned start, unsigned count,
void *data)
@@ -716,6 +787,20 @@ int vfio_pci_set_irqs_ioctl(struct vfio_pci_core_device *vdev, uint32_t flags,
break;
}
break;
+ case VFIO_PCI_ACPI_IRQ_INDEX:
+ switch (flags & VFIO_IRQ_SET_ACTION_TYPE_MASK) {
+ case VFIO_IRQ_SET_ACTION_TRIGGER:
+ switch (flags & VFIO_IRQ_SET_DATA_TYPE_MASK) {
+ case VFIO_IRQ_SET_DATA_BOOL:
+ case VFIO_IRQ_SET_DATA_NONE:
+ func = vfio_pci_set_acpi_ntfy_trigger;
+ break;
+ case VFIO_IRQ_SET_DATA_EVENTFD:
+ func = vfio_pci_set_acpi_ntfy_eventfd_trigger;
+ break;
+ }
+ }
+ break;
}
if (!func)
diff --git a/include/linux/vfio_acpi_notify.h b/include/linux/vfio_acpi_notify.h
new file mode 100644
index 000000000000..4bf1d055a014
--- /dev/null
+++ b/include/linux/vfio_acpi_notify.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * VFIO ACPI notification replication
+ *
+ * Author: Grzegorz Jaszczyk <jaz(a)semihalf.com>
+ */
+#include <linux/acpi.h>
+#include <linux/eventfd.h>
+#include <linux/poll.h>
+
+#ifndef VFIO_ACPI_NOTIFY_H
+#define VFIO_ACPI_NOTIFY_H
+
+struct vfio_acpi_notification {
+ struct eventfd_ctx *acpi_notify_trigger;
+ struct work_struct acpi_notification_work;
+ struct list_head notification_list;
+ struct mutex notification_list_lock;
+ struct mutex notification_lock;
+ int notification_queue_count;
+ poll_table pt;
+ wait_queue_entry_t wait;
+};
+
+#if IS_ENABLED(CONFIG_ACPI)
+void vfio_acpi_notify(acpi_handle handle, u32 event, void *data);
+int vfio_register_acpi_notify_handler(struct vfio_acpi_notification **acpi_notify,
+ struct acpi_device *adev, int32_t fd);
+void vfio_remove_acpi_notify(struct vfio_acpi_notification **acpi_notify,
+ struct acpi_device *adev);
+#else
+static inline void vfio_acpi_notify(acpi_handle handle, u32 event, void *data) {}
+static inline int
+vfio_register_acpi_notify_handler(struct vfio_acpi_notification **acpi_notify,
+ struct acpi_device *adev, int32_t fd)
+{
+ return -ENODEV;
+}
+
+static inline void
+vfio_remove_acpi_notify(struct vfio_acpi_notification **acpi_notify,
+ struct acpi_device *adev) {}
+#endif /* CONFIG_ACPI */
+
+#endif /* VFIO_ACPI_NOTIFY_H */
diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h
index 367fd79226a3..a4491b3d8064 100644
--- a/include/linux/vfio_pci_core.h
+++ b/include/linux/vfio_pci_core.h
@@ -96,6 +96,7 @@ struct vfio_pci_core_device {
struct mutex vma_lock;
struct list_head vma_list;
struct rw_semaphore memory_lock;
+ struct vfio_acpi_notification *acpi_notification;
};
/* Will be exported for vfio pci drivers usage */
diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h
index 0552e8dcf0cb..6b20ef3d202c 100644
--- a/include/uapi/linux/vfio.h
+++ b/include/uapi/linux/vfio.h
@@ -625,6 +625,7 @@ enum {
VFIO_PCI_MSIX_IRQ_INDEX,
VFIO_PCI_ERR_IRQ_INDEX,
VFIO_PCI_REQ_IRQ_INDEX,
+ VFIO_PCI_ACPI_IRQ_INDEX,
VFIO_PCI_NUM_IRQS
};
--
2.41.0.162.gfafddb0af9-goog
1 year, 6 months
[PATCH] qemu: prevent SIGSEGV in qemuProcessHandleDumpCompleted
by Nikolai Barybin
If VIR_ASYNC_JOB_NONE flag is present, job.current is equal
to NULL, which leads to SIGSEGV. Thus, this check should be
moved up.
Signed-off-by: Nikolai Barybin <nikolai.barybin(a)virtuozzo.com>
---
src/qemu/qemu_process.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index d3b1bdf6a4..db06991450 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -1601,11 +1601,11 @@ qemuProcessHandleDumpCompleted(qemuMonitor *mon G_GNUC_UNUSED,
vm, vm->def->name, stats, NULLSTR(error));
jobPriv = vm->job->privateData;
- privJobCurrent = vm->job->current->privateData;
if (vm->job->asyncJob == VIR_ASYNC_JOB_NONE) {
VIR_DEBUG("got DUMP_COMPLETED event without a dump_completed job");
goto cleanup;
}
+ privJobCurrent = vm->job->current->privateData;
jobPriv->dumpCompleted = true;
privJobCurrent->stats.dump = *stats;
vm->job->error = g_strdup(error);
--
2.39.3
1 year, 6 months
[libvirt PATCH v3 0/8] Metadata support for Network Objects
by K Shiva
Adds the following to Network Object:
- <metadata>, <title> and <description> to the Network Schema,
along with appropriate XML parse methods.
- 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>
K Shiva (8):
Parser and Schema definitions
Metadata change APIs and related enums
Added Metadata change events
virsh: Added Methods to print metadata changes
Methods to relay metadata change callbacks
Test driver implementations
Error code and message for NO_NETWORK_METADATA
Test program for network metadata
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
--
This is a v3 of:
https://listman.redhat.com/archives/libvir-list/2023-June/240444.html
Diff to v2:
Formatted patch into a smaller set of series.
2.41.0
1 year, 6 months
[PATCH] qemu_passt: Actually use @logfd
by Michal Privoznik
In one of my previous commits I've introduced @logfd variable
that was supposed to hold FD of passt logfile. But I've forgot to
assign the qemuDomainOpenFile() retval to it.
Fixes: 8511b96a319836700b4829816cdae27c3630060d
Signed-off-by: Michal Privoznik <mprivozn(a)redhat.com>
---
Pushed as trivial.
src/qemu/qemu_passt.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/qemu/qemu_passt.c b/src/qemu/qemu_passt.c
index 25b22d8ad9..3679bf75fc 100644
--- a/src/qemu/qemu_passt.c
+++ b/src/qemu/qemu_passt.c
@@ -204,9 +204,9 @@ qemuPasstStart(virDomainObj *vm,
/* The logFile location is not restricted to a per-domain directory. It
* can be anywhere. Pre-create it as passt may not have enough perms to
* do so. */
- if (qemuDomainOpenFile(cfg, vm->def, net->backend.logFile,
- O_CREAT | O_TRUNC | O_APPEND | O_RDWR,
- &needUnlink) < 0) {
+ if ((logfd = qemuDomainOpenFile(cfg, vm->def, net->backend.logFile,
+ O_CREAT | O_TRUNC | O_APPEND | O_RDWR,
+ &needUnlink)) < 0) {
return -1;
}
--
2.39.3
1 year, 6 months
[PATCH 0/2] qemu: Handle passt logfile a bit better
by Michal Privoznik
*** BLURB HERE ***
Michal Prívozník (2):
qemu_passt: Precreate passt logfile
docs: Move passt log file in our example XML
docs/formatdomain.rst | 2 +-
src/qemu/qemu_passt.c | 40 +++++++++++++++++++++++++++++++++++-----
2 files changed, 36 insertions(+), 6 deletions(-)
--
2.39.3
1 year, 6 months
[PATCH] util: don't validate empty params
by Oleg Vasilev
If there are no parameters, there is nothing to validate.
If params == NULL, memcpy below results in memcpy(sorted, NULL, 0),
which is UB.
Found by UBSAN. Example of this codepath: virDomainBlockCopy()
(where nparams == 0 is valid) -> qemuDomainBlockCopy()
Signed-off-by: Oleg Vasilev <oleg.vasilev(a)virtuozzo.com>
---
src/util/virtypedparam.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/util/virtypedparam.c b/src/util/virtypedparam.c
index 3bb8b125e9..ef3b8052f6 100644
--- a/src/util/virtypedparam.c
+++ b/src/util/virtypedparam.c
@@ -68,6 +68,10 @@ virTypedParamsValidate(virTypedParameterPtr params, int nparams, ...)
g_autofree virTypedParameterPtr sorted = NULL;
g_autofree virTypedParameterPtr keys = NULL;
+ if (!nparams) {
+ return 0;
+ }
+
va_start(ap, nparams);
sorted = g_new0(virTypedParameter, nparams);
--
2.41.0
1 year, 6 months
[libvirt PATCH] Metadata support for Network Objects
by K Shiva
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(a)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
1 year, 6 months
[libvirt PATCH v2] Metadata support for Network Objects
by K Shiva
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
1 year, 6 months
[PATCH v2] qemuDomainWaitForDeviceRemoval: recheck the value of priv->unplug.alias when timeout
by Peter Krempa
From: zuoboqun <zuoboqun(a)baidu.com>
When detaching a device, the following race condition may happen:
Once qemuDomainSignalDeviceRemoval() marks the device for
removal, it returns true, which means it is the caller
that marked the device for removal is going to remove the
device from domain definition.
But qemuDomainWaitForDeviceRemoval() may still receive
timeout from virDomainObjWaitUntil() which is implemented
by pthread_cond_timedwait() due to an unavoidable race
between the expiration of the timeout and the predicate
state(priv->unplug.alias) change.
And then qemuDomainWaitForDeviceRemoval() will return 0,
thus the caller will not remove the device from domain
definition.
In this situation, the device is still present in the domain
definition but doesn't exist in qemu anymore. Worse, there is
no way to remove it from the domain definition.
Solution is to recheck the value of priv->unplug.alias to
determine who is going to remove the device from domain
definition.
Signed-off-by: zuo boqun <zuoboqun(a)baidu.com>
Reviewed-by: Peter Krempa <pkrempa(a)redhat.com>
Signed-off-by: Peter Krempa <pkrempa(a)redhat.com>
---
v2:
- rewrote waiting loop so that we always check the unplug status if the
thread was notified
- added comments explaining the logic
src/qemu/qemu_hotplug.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index ba9e44945b..2e3c99760d 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -5391,21 +5391,27 @@ qemuDomainWaitForDeviceRemoval(virDomainObj *vm)
{
qemuDomainObjPrivate *priv = vm->privateData;
unsigned long long until;
- int rc;
if (virTimeMillisNow(&until) < 0)
return 1;
until += qemuDomainGetUnplugTimeout(vm);
- while (priv->unplug.alias) {
- if ((rc = virDomainObjWaitUntil(vm, until)) == 1)
- return 0;
+ while (true) {
+ int rc;
- if (rc < 0) {
- VIR_WARN("Failed to wait on unplug condition for domain '%s' "
- "device '%s'", vm->def->name, priv->unplug.alias);
+ if ((rc = virDomainObjWaitUntil(vm, until)) < 0) {
+ VIR_WARN("Failed to wait on unplug condition for domain '%s' device '%s'",
+ vm->def->name, priv->unplug.alias);
return 1;
}
+
+ /* unplug event for this device was received, check the status */
+ if (!priv->unplug.alias)
+ break;
+
+ /* timeout */
+ if (rc == 1)
+ return 0;
}
if (priv->unplug.status == QEMU_DOMAIN_UNPLUGGING_DEVICE_STATUS_GUEST_REJECTED) {
--
2.40.1
1 year, 6 months