virsh exposure of Network Metadata APIs

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@riseup.net>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
This commit is contained in:
K Shiva Kiran 2023-08-17 00:17:12 +05:30 committed by Michal Privoznik
parent 7ab9d1ec88
commit 93d1989a3d
4 changed files with 510 additions and 11 deletions

View File

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

View File

@ -330,6 +330,352 @@ 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 +1054,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 +1071,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 +1105,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 +1126,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 +2201,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 +2249,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,

View File

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

View File

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