[libvirt PATCH v2 4/7] virsh exposure of Network Metadata APIs

K Shiva Kiran shiva_kr at riseup.net
Wed Aug 16 18:47:12 UTC 2023


Adds two new commands and a new option:
- 'net-desc' to show/modify network title and description.
- 'net-metadata' to show/modify network metadata.
- Option '--title' for 'net-list' to print corresponding
  network titles in an additional column.
- Documentation for all the above.
- XML Fallback function `virshNetworkGetXMLFromNet` for title and
  description for compatibility with hosts running older versions
  of libvirtd.

Signed-off-by: K Shiva Kiran <shiva_kr at riseup.net>
---
 docs/manpages/virsh.rst |  77 ++++++++
 tools/virsh-network.c   | 411 ++++++++++++++++++++++++++++++++++++++--
 tools/virsh-util.c      |  25 +++
 tools/virsh-util.h      |   9 +
 4 files changed, 511 insertions(+), 11 deletions(-)

diff --git a/docs/manpages/virsh.rst b/docs/manpages/virsh.rst
index f4e5a0bd62..673812036d 100644
--- a/docs/manpages/virsh.rst
+++ b/docs/manpages/virsh.rst
@@ -5566,6 +5566,7 @@ to get a description of the XML network format used by libvirt.
 Optionally, the format of the input XML file can be validated against an
 internal RNG schema with *--validate*.
 
+
 net-define
 ----------
 
@@ -5581,6 +5582,38 @@ Optionally, the format of the input XML file can be validated against an
 internal RNG schema with *--validate*.
 
 
+net-desc
+--------
+
+**Syntax:**
+
+::
+
+   net-desc network [[--live] [--config] |
+      [--current]] [--title] [--edit] [--new-desc
+      New description or title message]
+
+Show or modify description and title of a network. These values are user
+fields that allow storing arbitrary textual data to allow easy
+identification of networks. Title should be short, although it's not enforced.
+(See also ``net-metadata`` that works with XML based network metadata.)
+
+Flags *--live* or *--config* select whether this command works on live
+or persistent definitions of the network. If both *--live* and *--config*
+are specified, the *--config* option takes precedence on getting the current
+description and both live configuration and config are updated while setting
+the description. *--current* is exclusive and implied if none of these was
+specified.
+
+Flag *--edit* specifies that an editor with the contents of current
+description or title should be opened and the contents saved back afterwards.
+
+Flag *--title* selects operation on the title field instead of description.
+
+If neither of *--edit* and *--new-desc* are specified the note or description
+is displayed instead of being modified.
+
+
 net-destroy
 -----------
 
@@ -5689,6 +5722,7 @@ net-list
       { [--table] | --name | --uuid }
       [--persistent] [<--transient>]
       [--autostart] [<--no-autostart>]
+      [--title]
 
 Returns the list of active networks, if *--all* is specified this will also
 include defined but inactive networks, if *--inactive* is specified only the
@@ -5703,12 +5737,55 @@ instead of names. Flag *--table* specifies that the legacy table-formatted
 output should be used. This is the default. All of these are mutually
 exclusive.
 
+If *--title* is specified, then the short network description (title) is
+printed in an extra column. This flag is usable only with the default
+*--table* output.
+
 NOTE: When talking to older servers, this command is forced to use a series of
 API calls with an inherent race, where a pool might not be listed or might appear
 more than once if it changed state between calls while the list was being
 collected.  Newer servers do not have this problem.
 
 
+net-metadata
+------------
+
+**Syntax:**
+
+::
+
+   net-metadata network [[--live] [--config] | [--current]]
+      [--edit] [uri] [key] [set] [--remove]
+
+Show or modify custom XML metadata of a network. The metadata is a user
+defined XML that allows storing arbitrary XML data in the network definition.
+Multiple separate custom metadata pieces can be stored in the network XML.
+The pieces are identified by a private XML namespace provided via the
+*uri* argument. (See also ``net-desc`` that works with textual metadata of
+a network, such as title and description.)
+
+Flags *--live* or *--config* select whether this command works on live
+or persistent definitions of the network. If both *--live* and *--config*
+are specified, the *--config* option takes precedence on getting the current
+description and both live configuration and config are updated while setting
+the description. *--current* is exclusive and implied if none of these was
+specified.
+
+Flag *--remove* specifies that the metadata element specified by the *uri*
+argument should be removed rather than updated.
+
+Flag *--edit* specifies that an editor with the metadata identified by the
+*uri* argument should be opened and the contents saved back afterwards.
+Otherwise the new contents can be provided via the *set* argument.
+
+When setting metadata via *--edit* or *set* the *key* argument must be
+specified and is used to prefix the custom elements to bind them
+to the private namespace.
+
+If neither of *--edit* and *set* are specified the XML metadata corresponding
+to the *uri* namespace is displayed instead of being modified.
+
+
 net-name
 --------
 
diff --git a/tools/virsh-network.c b/tools/virsh-network.c
index 42b7dba761..5a5cd15e24 100644
--- a/tools/virsh-network.c
+++ b/tools/virsh-network.c
@@ -330,6 +330,353 @@ cmdNetworkDestroy(vshControl *ctl, const vshCmd *cmd)
     return ret;
 }
 
+/*
+ * "net-desc" command
+ */
+static const vshCmdInfo info_network_desc[] = {
+    {.name = "help",
+     .data = N_("show or set network's description or title")
+    },
+    {.name = "desc",
+     .data = N_("Allows setting or modifying the description or title of a network.")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_network_desc[] = {
+    VIRSH_COMMON_OPT_NETWORK_FULL(0),
+    VIRSH_COMMON_OPT_LIVE(N_("modify/get running state")),
+    VIRSH_COMMON_OPT_CONFIG(N_("modify/get persistent configuration")),
+    VIRSH_COMMON_OPT_CURRENT(N_("modify/get current state configuration")),
+    {.name = "title",
+     .type = VSH_OT_BOOL,
+     .help = N_("modify/get the title instead of description")
+    },
+    {.name = "edit",
+     .type = VSH_OT_BOOL,
+     .help = N_("open an editor to modify the description")
+    },
+    {.name = "new-desc",
+     .type = VSH_OT_ARGV,
+     .help = N_("message")
+    },
+    {.name = NULL}
+};
+
+/* extract description or title from network xml */
+static char *
+virshGetNetworkDescription(vshControl *ctl, virNetworkPtr net,
+                           bool title, unsigned int flags)
+{
+    char *desc = NULL;
+    g_autoptr(xmlDoc) doc = NULL;
+    g_autoptr(xmlXPathContext) ctxt = NULL;
+    int type;
+
+    if (title)
+        type = VIR_NETWORK_METADATA_TITLE;
+    else
+        type = VIR_NETWORK_METADATA_DESCRIPTION;
+
+    if ((desc = virNetworkGetMetadata(net, type, NULL, flags))) {
+        return desc;
+    } else {
+        int errCode = virGetLastErrorCode();
+
+        if (errCode == VIR_ERR_NO_NETWORK_METADATA) {
+            desc = g_strdup("");
+            vshResetLibvirtError();
+            return desc;
+        }
+
+        if (errCode != VIR_ERR_NO_SUPPORT)
+            return desc;
+    }
+
+    /* fall back to xml */
+    if (virshNetworkGetXMLFromNet(ctl, net, flags, &doc, &ctxt) < 0)
+        return NULL;
+
+    if (title)
+        desc = virXPathString("string(./title[1])", ctxt);
+    else
+        desc = virXPathString("string(./description[1])", ctxt);
+
+    if (!desc)
+        desc = g_strdup("");
+
+    return desc;
+}
+
+static bool
+cmdNetworkDesc(vshControl *ctl, const vshCmd *cmd)
+{
+    g_autoptr(virshNetwork) net = NULL;
+    bool config = vshCommandOptBool(cmd, "config");
+    bool live = vshCommandOptBool(cmd, "live");
+    bool current = vshCommandOptBool(cmd, "current");
+
+    bool title = vshCommandOptBool(cmd, "title");
+    bool edit = vshCommandOptBool(cmd, "edit");
+
+    int type;
+    g_autofree char *descArg = NULL;
+    const vshCmdOpt *opt = NULL;
+    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+    unsigned int flags = VIR_NETWORK_UPDATE_AFFECT_CURRENT;
+    unsigned int queryflags = 0;
+
+    VSH_EXCLUSIVE_OPTIONS_VAR(current, live);
+    VSH_EXCLUSIVE_OPTIONS_VAR(current, config);
+
+    if (config) {
+        flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG;
+        queryflags |= VIR_NETWORK_XML_INACTIVE;
+    }
+    if (live)
+        flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE;
+
+    if (!(net = virshCommandOptNetwork(ctl, cmd, NULL)))
+        return false;
+
+    if (title)
+        type = VIR_NETWORK_METADATA_TITLE;
+    else
+        type = VIR_NETWORK_METADATA_DESCRIPTION;
+
+    while ((opt = vshCommandOptArgv(ctl, cmd, opt)))
+        virBufferAsprintf(&buf, "%s ", opt->data);
+
+    virBufferTrim(&buf, " ");
+
+    descArg = virBufferContentAndReset(&buf);
+
+    if (edit || descArg) {
+        g_autofree char *descNet = NULL;
+        g_autofree char *descNew = NULL;
+
+        if (!(descNet = virshGetNetworkDescription(ctl, net, title, queryflags)))
+            return false;
+
+        if (!descArg)
+            descArg = g_strdup(descNet);
+
+        if (edit) {
+            g_autoptr(vshTempFile) tmp = NULL;
+            g_autofree char *desc_edited = NULL;
+            char *tmpstr;
+
+            /* Create and open the temporary file. */
+            if (!(tmp = vshEditWriteToTempFile(ctl, descArg)))
+                return false;
+
+            /* Start the editor. */
+            if (vshEditFile(ctl, tmp) == -1)
+                return false;
+
+            /* Read back the edited file. */
+            if (!(desc_edited = vshEditReadBackFile(ctl, tmp)))
+                return false;
+
+            /* strip a possible newline at the end of file; some
+             * editors enforce a newline, this makes editing the title
+             * more convenient */
+            if (title &&
+                (tmpstr = strrchr(desc_edited, '\n')) &&
+                *(tmpstr+1) == '\0')
+                *tmpstr = '\0';
+
+            /* Compare original XML with edited.  Has it changed at all? */
+            if (STREQ(descNet, desc_edited)) {
+                if (title)
+                    vshPrintExtra(ctl, "%s", _("Network title not changed\n"));
+                else
+                    vshPrintExtra(ctl, "%s", _("Network description not changed\n"));
+
+                return true;
+            }
+
+            descNew = g_steal_pointer(&desc_edited);
+        } else {
+            descNew = g_steal_pointer(&descArg);
+        }
+
+        if (virNetworkSetMetadata(net, type, descNew, NULL, NULL, flags) < 0) {
+            if (title)
+                vshError(ctl, "%s", _("Failed to set new network title"));
+            else
+                vshError(ctl, "%s", _("Failed to set new network description"));
+
+            return false;
+        }
+
+        if (title)
+            vshPrintExtra(ctl, "%s", _("Network title updated successfully"));
+        else
+            vshPrintExtra(ctl, "%s", _("Network description updated successfully"));
+
+    } else {
+        g_autofree char *desc = virshGetNetworkDescription(ctl, net, title, queryflags);
+        if (!desc)
+            return false;
+
+        if (strlen(desc) > 0) {
+            vshPrint(ctl, "%s", desc);
+        } else {
+            if (title)
+                vshPrintExtra(ctl, _("No title for network: %1$s"), virNetworkGetName(net));
+            else
+                vshPrintExtra(ctl, _("No description for network: %1$s"), virNetworkGetName(net));
+        }
+    }
+
+    return true;
+}
+
+/*
+ * "net-metadata" command
+ */
+static const vshCmdInfo info_network_metadata[] = {
+    {.name = "help",
+     .data = N_("show or set network's custom XML metadata")
+    },
+    {.name = "desc",
+     .data = N_("Shows or modifies the XML metadata of a network.")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_network_metadata[] = {
+    VIRSH_COMMON_OPT_NETWORK_FULL(0),
+    {.name = "uri",
+     .type = VSH_OT_DATA,
+     .flags = VSH_OFLAG_REQ,
+     .help = N_("URI of the namespace")
+    },
+    VIRSH_COMMON_OPT_LIVE(N_("modify/get running state")),
+    VIRSH_COMMON_OPT_CONFIG(N_("modify/get persistent configuration")),
+    VIRSH_COMMON_OPT_CURRENT(N_("modify/get current state configuration")),
+    {.name = "edit",
+     .type = VSH_OT_BOOL,
+     .help = N_("use an editor to change the metadata")
+    },
+    {.name = "key",
+     .type = VSH_OT_STRING,
+     .help = N_("key to be used as a namespace identifier"),
+    },
+    {.name = "set",
+     .type = VSH_OT_STRING,
+     .completer = virshCompleteEmpty,
+     .help = N_("new metadata to set"),
+    },
+    {.name = "remove",
+     .type = VSH_OT_BOOL,
+     .help = N_("remove the metadata corresponding to an uri")
+    },
+    {.name = NULL}
+};
+
+/* helper to add new metadata using the --edit option */
+static char *
+virshNetworkGetEditMetadata(vshControl *ctl G_GNUC_UNUSED,
+                            virNetworkPtr net,
+                            const char *uri,
+                            unsigned int flags)
+{
+    char *ret;
+
+    if (!(ret = virNetworkGetMetadata(net, VIR_NETWORK_METADATA_ELEMENT,
+                                      uri, flags))) {
+        vshResetLibvirtError();
+        ret = g_strdup("\n");
+    }
+
+    return ret;
+}
+
+static bool
+cmdNetworkMetadata(vshControl *ctl, const vshCmd *cmd)
+{
+    g_autoptr(virshNetwork) net = NULL;
+    g_autoptr(xmlXPathContext) ctxt = NULL;
+    bool config = vshCommandOptBool(cmd, "config");
+    bool live = vshCommandOptBool(cmd, "live");
+    bool current = vshCommandOptBool(cmd, "current");
+    bool edit = vshCommandOptBool(cmd, "edit");
+    bool rem = vshCommandOptBool(cmd, "remove");
+    const char *set = NULL;
+    const char *uri = NULL;
+    const char *key = NULL;
+    unsigned int flags = VIR_NETWORK_UPDATE_AFFECT_CURRENT;
+    bool ret = false;
+
+    VSH_EXCLUSIVE_OPTIONS_VAR(current, live);
+    VSH_EXCLUSIVE_OPTIONS_VAR(current, config);
+    VSH_EXCLUSIVE_OPTIONS("edit", "set");
+    VSH_EXCLUSIVE_OPTIONS("remove", "set");
+    VSH_EXCLUSIVE_OPTIONS("remove", "edit");
+
+    if (config)
+        flags |= VIR_NETWORK_UPDATE_AFFECT_CONFIG;
+    if (live)
+        flags |= VIR_NETWORK_UPDATE_AFFECT_LIVE;
+
+    if (!(net = virshCommandOptNetwork(ctl, cmd, NULL)))
+        return false;
+
+    if (vshCommandOptStringReq(ctl, cmd, "uri", &uri) < 0 ||
+        vshCommandOptStringReq(ctl, cmd, "key", &key) < 0 ||
+        vshCommandOptStringReq(ctl, cmd, "set", &set) < 0)
+        return false;
+
+    if ((set || edit) && !key) {
+        vshError(ctl, "%s",
+                 _("namespace key is required when modifying metadata"));
+        return false;
+    }
+
+    if (set || rem) {
+        if (virNetworkSetMetadata(net, VIR_NETWORK_METADATA_ELEMENT,
+                                  set, key, uri, flags))
+            return false;
+
+        if (rem)
+            vshPrintExtra(ctl, "%s\n", _("Metadata removed"));
+        else
+            vshPrintExtra(ctl, "%s\n", _("Metadata modified"));
+    } else if (edit) {
+#define EDIT_GET_XML \
+        virshNetworkGetEditMetadata(ctl, net, uri, flags)
+#define EDIT_NOT_CHANGED \
+        do { \
+            vshPrintExtra(ctl, "%s", _("Metadata not changed")); \
+            ret = true; \
+            goto edit_cleanup; \
+        } while (0)
+
+#define EDIT_DEFINE \
+        (virNetworkSetMetadata(net, VIR_NETWORK_METADATA_ELEMENT, doc_edited, \
+                               key, uri, flags) == 0)
+#include "virsh-edit.c"
+
+        vshPrintExtra(ctl, "%s\n", _("Metadata modified"));
+    } else {
+        g_autofree char *data = NULL;
+        g_autoptr(xmlDoc) doc = NULL;
+        /* get */
+        if (!(data = virNetworkGetMetadata(net, VIR_NETWORK_METADATA_ELEMENT,
+                                           uri, flags)))
+            return false;
+
+        vshPrint(ctl, "%s\n", data);
+    }
+
+    ret = true;
+
+ cleanup:
+    return ret;
+}
+
 /*
  * "net-dumpxml" command
  */
@@ -708,6 +1055,10 @@ static const vshCmdOptDef opts_network_list[] = {
      .type = VSH_OT_BOOL,
      .help = N_("list table (default)")
     },
+    {.name = "title",
+     .type = VSH_OT_BOOL,
+     .help = N_("show network title")
+    },
     {.name = NULL}
 };
 
@@ -721,6 +1072,7 @@ cmdNetworkList(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
     size_t i;
     bool ret = false;
     bool optName = vshCommandOptBool(cmd, "name");
+    bool optTitle = vshCommandOptBool(cmd, "title");
     bool optTable = vshCommandOptBool(cmd, "table");
     bool optUUID = vshCommandOptBool(cmd, "uuid");
     char uuid[VIR_UUID_STRING_BUFLEN];
@@ -754,8 +1106,12 @@ cmdNetworkList(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
         return false;
 
     if (optTable) {
-        table = vshTableNew(_("Name"), _("State"), _("Autostart"),
-                            _("Persistent"), NULL);
+        if (optTitle)
+            table = vshTableNew(_("Name"), _("State"), _("Autostart"),
+                                _("Persistent"), _("Title"), NULL);
+        else
+            table = vshTableNew(_("Name"), _("State"), _("Autostart"),
+                                _("Persistent"), NULL);
         if (!table)
             goto cleanup;
     }
@@ -771,16 +1127,37 @@ cmdNetworkList(vshControl *ctl, const vshCmd *cmd G_GNUC_UNUSED)
             else
                 autostartStr = is_autostart ? _("yes") : _("no");
 
-            if (vshTableRowAppend(table,
-                                  virNetworkGetName(network),
-                                  virNetworkIsActive(network) ?
-                                  _("active") : _("inactive"),
-                                  autostartStr,
-                                  virNetworkIsPersistent(network) ?
-                                  _("yes") : _("no"),
-                                  NULL) < 0)
-                goto cleanup;
+            if (optTitle) {
+                g_autofree char *title = NULL;
+
+                if (!(title = virshGetNetworkDescription(ctl, network, true, 0)))
+                    goto cleanup;
+                if (vshTableRowAppend(table,
+                            virNetworkGetName(network),
+                            virNetworkIsActive(network) ?
+                            _("active") : _("inactive"),
+                            autostartStr,
+                            virNetworkIsPersistent(network) ?
+                            _("yes") : _("no"),
+                            title,
+                            NULL) < 0)
+                    goto cleanup;
+
+            } else {
+                if (vshTableRowAppend(table,
+                            virNetworkGetName(network),
+                            virNetworkIsActive(network) ?
+                            _("active") : _("inactive"),
+                            autostartStr,
+                            virNetworkIsPersistent(network) ?
+                            _("yes") : _("no"),
+                            NULL) < 0)
+                    goto cleanup;
+
+            }
+
         } else if (optUUID) {
+
             if (virNetworkGetUUIDString(network, uuid) < 0) {
                 vshError(ctl, "%s", _("Failed to get network's UUID"));
                 goto cleanup;
@@ -1825,6 +2202,12 @@ const vshCmdDef networkCmds[] = {
      .info = info_network_define,
      .flags = 0
     },
+    {.name = "net-desc",
+     .handler = cmdNetworkDesc,
+     .opts = opts_network_desc,
+     .info = info_network_desc,
+     .flags = 0
+    },
     {.name = "net-destroy",
      .handler = cmdNetworkDestroy,
      .opts = opts_network_destroy,
@@ -1867,6 +2250,12 @@ const vshCmdDef networkCmds[] = {
      .info = info_network_list,
      .flags = 0
     },
+    {.name = "net-metadata",
+     .handler = cmdNetworkMetadata,
+     .opts = opts_network_metadata,
+     .info = info_network_metadata,
+     .flags = 0
+    },
     {.name = "net-name",
      .handler = cmdNetworkName,
      .opts = opts_network_name,
diff --git a/tools/virsh-util.c b/tools/virsh-util.c
index 61e403a636..fb6327613a 100644
--- a/tools/virsh-util.c
+++ b/tools/virsh-util.c
@@ -398,6 +398,31 @@ virshDomainGetXMLFromDom(vshControl *ctl,
 }
 
 
+int
+virshNetworkGetXMLFromNet(vshControl *ctl,
+                          virNetworkPtr net,
+                          unsigned int flags,
+                          xmlDocPtr *xml,
+                          xmlXPathContextPtr *ctxt)
+{
+    g_autofree char *desc = NULL;
+
+    if (!(desc = virNetworkGetXMLDesc(net, flags))) {
+        vshError(ctl, _("Failed to get network description xml"));
+        return -1;
+    }
+
+    *xml = virXMLParseStringCtxt(desc, _("(network_definition)"), ctxt);
+
+    if (!(*xml)) {
+        vshError(ctl, _("Failed to parse network description xml"));
+        return -1;
+    }
+
+    return 0;
+}
+
+
 int
 virshDomainGetXML(vshControl *ctl,
                   const vshCmd *cmd,
diff --git a/tools/virsh-util.h b/tools/virsh-util.h
index 0f81a2771b..2386847072 100644
--- a/tools/virsh-util.h
+++ b/tools/virsh-util.h
@@ -143,6 +143,15 @@ virshDomainGetXMLFromDom(vshControl *ctl,
     ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4)
     ATTRIBUTE_NONNULL(5) G_GNUC_WARN_UNUSED_RESULT;
 
+int
+virshNetworkGetXMLFromNet(vshControl *ctl,
+                          virNetworkPtr net,
+                          unsigned int flags,
+                          xmlDocPtr *xml,
+                          xmlXPathContextPtr *ctxt)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(4)
+    ATTRIBUTE_NONNULL(5) G_GNUC_WARN_UNUSED_RESULT;
+
 int
 virshDomainGetXML(vshControl *ctl,
                   const vshCmd *cmd,
-- 
2.41.0



More information about the libvir-list mailing list