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..f9fea0a126 100644 --- a/tools/virsh-network.c +++ b/tools/virsh-network.c @@ -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, 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,