This patch adds a new command "desc" to show and modify titles and
description for the domains using the new API.
This patch also adds a new flag for the "list" command to show titles in
the domain list, to allow easy identification of VMs by storing a short
description.
Example:
virsh # list --title
Id Name State Title
-----------------------------------------------
0 Domain-0 running Mailserver 1
2 fedora paused
---
Diff to v1:
- tweaked wording in man page (forgot to change old names)
- added removal of newline at the end of domain's title for convinience
(and workaround of editors that add the newline automaticaly)
tools/virsh.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++----
tools/virsh.pod | 34 +++++++-
2 files changed, 296 insertions(+), 21 deletions(-)
diff --git a/tools/virsh.c b/tools/virsh.c
index 3a59746..7dc2deb 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -313,6 +313,9 @@ static int vshCommandOptULongLong(const vshCmd *cmd, const char
*name,
static bool vshCommandOptBool(const vshCmd *cmd, const char *name);
static const vshCmdOpt *vshCommandOptArgv(const vshCmd *cmd,
const vshCmdOpt *opt);
+static char *vshGetDomainDescription(vshControl *ctl, virDomainPtr dom,
+ bool title, unsigned int flags)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK;
#define VSH_BYID (1 << 1)
#define VSH_BYUUID (1 << 2)
@@ -886,6 +889,7 @@ static const vshCmdOptDef opts_list[] = {
{"all", VSH_OT_BOOL, 0, N_("list inactive & active
domains")},
{"managed-save", VSH_OT_BOOL, 0,
N_("mark domains with managed save state")},
+ {"title", VSH_OT_BOOL, 0, N_("show short domain description")},
{NULL, 0, 0, NULL}
};
@@ -900,7 +904,10 @@ cmdList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
char **names = NULL;
int maxname = 0;
bool managed = vshCommandOptBool(cmd, "managed-save");
+ bool desc = vshCommandOptBool(cmd, "title");
+ char *title;
int state;
+ bool ret = false;
inactive |= all;
@@ -918,8 +925,7 @@ cmdList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
if ((maxid = virConnectListDomains(ctl->conn, &ids[0], maxid)) < 0)
{
vshError(ctl, "%s", _("Failed to list active
domains"));
- VIR_FREE(ids);
- return false;
+ goto cleanup;
}
qsort(&ids[0], maxid, sizeof(int), idsorter);
@@ -929,37 +935,52 @@ cmdList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
maxname = virConnectNumOfDefinedDomains(ctl->conn);
if (maxname < 0) {
vshError(ctl, "%s", _("Failed to list inactive
domains"));
- VIR_FREE(ids);
- return false;
+ goto cleanup;
}
if (maxname) {
names = vshMalloc(ctl, sizeof(char *) * maxname);
if ((maxname = virConnectListDefinedDomains(ctl->conn, names, maxname))
< 0) {
vshError(ctl, "%s", _("Failed to list inactive
domains"));
- VIR_FREE(ids);
- VIR_FREE(names);
- return false;
+ goto cleanup;
}
qsort(&names[0], maxname, sizeof(char*), namesorter);
}
}
- vshPrintExtra(ctl, " %-5s %-30s %s\n", _("Id"),
_("Name"), _("State"));
- vshPrintExtra(ctl,
"----------------------------------------------------\n");
+
+ if (desc) {
+ vshPrintExtra(ctl, "%-5s %-30s %-10s %s\n", _("Id"),
_("Name"), _("State"), _("Title"));
+ vshPrintExtra(ctl,
"-----------------------------------------------------------\n");
+ } else {
+ vshPrintExtra(ctl, " %-5s %-30s %s\n", _("Id"),
_("Name"), _("State"));
+ vshPrintExtra(ctl,
"----------------------------------------------------\n");
+ }
for (i = 0; i < maxid; i++) {
- virDomainPtr dom = virDomainLookupByID(ctl->conn, ids[i]);
+ virDomainPtr dom = virDomainLookupByID(ctl->conn, ids[i]);
/* this kind of work with domains is not atomic operation */
if (!dom)
continue;
- vshPrint(ctl, " %-5d %-30s %s\n",
- virDomainGetID(dom),
- virDomainGetName(dom),
- _(vshDomainStateToString(vshDomainState(ctl, dom, NULL))));
- virDomainFree(dom);
+ if (desc) {
+ if (!(title = vshGetDomainDescription(ctl, dom, true, 0)))
+ goto cleanup;
+
+ vshPrint(ctl, "%-5d %-30s %-10s %s\n",
+ virDomainGetID(dom),
+ virDomainGetName(dom),
+ _(vshDomainStateToString(vshDomainState(ctl, dom, NULL))),
+ title);
+ VIR_FREE(title);
+ } else {
+ vshPrint(ctl, " %-5d %-30s %s\n",
+ virDomainGetID(dom),
+ virDomainGetName(dom),
+ _(vshDomainStateToString(vshDomainState(ctl, dom, NULL))));
+ }
+ virDomainFree(dom);
}
for (i = 0; i < maxname; i++) {
virDomainPtr dom = virDomainLookupByName(ctl->conn, names[i]);
@@ -975,17 +996,179 @@ cmdList(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
virDomainHasManagedSaveImage(dom, 0) > 0)
state = -2;
- vshPrint(ctl, " %-5s %-30s %s\n",
- "-",
- names[i],
- state == -2 ? _("saved") : _(vshDomainStateToString(state)));
+ if (desc) {
+ if (!(title = vshGetDomainDescription(ctl, dom, true, 0)))
+ goto cleanup;
+
+ vshPrint(ctl, "%-5s %-30s %-10s %s\n",
+ "-",
+ names[i],
+ state == -2 ? _("saved") :
_(vshDomainStateToString(state)),
+ title);
+ VIR_FREE(title);
+ } else {
+ vshPrint(ctl, " %-5s %-30s %s\n",
+ "-",
+ names[i],
+ state == -2 ? _("saved") :
_(vshDomainStateToString(state)));
virDomainFree(dom);
VIR_FREE(names[i]);
+ }
}
+
+ ret = true;
+cleanup:
VIR_FREE(ids);
VIR_FREE(names);
- return true;
+ return ret;
+}
+
+/*
+ * "desc" command for managing domain description and title
+ */
+static const vshCmdInfo info_desc[] = {
+ {"help", N_("show or set domain's description or title")},
+ {"desc", N_("Allows to show or modify description or title of a
domain.")},
+ {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_desc[] = {
+ {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or
uuid")},
+ {"live", VSH_OT_BOOL, 0, N_("modify/get running state")},
+ {"config", VSH_OT_BOOL, 0, N_("modify/get persistent
configuration")},
+ {"current", VSH_OT_BOOL, 0, N_("modify/get current state
configuration")},
+ {"title", VSH_OT_BOOL, 0, N_("modify the title instead of
description")},
+ {"edit", VSH_OT_BOOL, 0, N_("open an editor to modify the
description")},
+ {"new-desc", VSH_OT_ARGV, 0, N_("message")},
+ {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdDesc(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
+{
+ virDomainPtr dom;
+ bool config = vshCommandOptBool(cmd, "config");
+ bool live = vshCommandOptBool(cmd, "live");
+ /* current is ignored */
+
+ bool title = vshCommandOptBool(cmd, "title");
+ bool edit = vshCommandOptBool(cmd, "edit");
+
+ int state;
+ int type;
+ char *desc = NULL;
+ char *desc_edited = NULL;
+ char *tmp = NULL;
+ char *tmpstr;
+ const vshCmdOpt *opt = NULL;
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+ bool pad = false;
+ bool ret = false;
+ unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT;
+
+ if (!vshConnectionUsability(ctl, ctl->conn))
+ return false;
+
+ if (!(dom = vshCommandOptDomain(ctl, cmd, NULL)))
+ return false;
+
+ if ((state = vshDomainState(ctl, dom, NULL)) < 0) {
+ ret = false;
+ goto cleanup;
+ }
+
+ while ((opt = vshCommandOptArgv(cmd, opt))) {
+ if (pad)
+ virBufferAddChar(&buf, ' ');
+ pad = true;
+ virBufferAdd(&buf, opt->data, -1);
+ }
+
+ if (live)
+ flags |= VIR_DOMAIN_AFFECT_LIVE;
+ if (config)
+ flags |= VIR_DOMAIN_AFFECT_CONFIG;
+ if (title)
+ type = VIR_DOMAIN_METADATA_TITLE;
+ else
+ type = VIR_DOMAIN_METADATA_DESCRIPTION;
+
+ if (virBufferError(&buf)) {
+ vshPrint(ctl, "%s", _("Failed to collect new
description/title"));
+ goto cleanup;
+ }
+ desc = virBufferContentAndReset(&buf);
+
+ if (edit || desc) {
+ if (!desc) {
+ desc = vshGetDomainDescription(ctl, dom, title,
+ config?VIR_DOMAIN_XML_INACTIVE:0);
+ if (!desc)
+ goto cleanup;
+ }
+
+ if (edit) {
+ /* Create and open the temporary file. */
+ if (!(tmp = editWriteToTempFile(ctl, desc)))
+ goto cleanup;
+
+ /* Start the editor. */
+ if (editFile(ctl, tmp) == -1)
+ goto cleanup;
+
+ /* Read back the edited file. */
+ if (!(desc_edited = editReadBackFile(ctl, tmp)))
+ goto cleanup;
+
+ /* Compare original XML with edited. Has it changed at all? */
+ if (STREQ(desc, desc_edited)) {
+ vshPrint(ctl, _("Domain description not changed.\n"));
+ ret = true;
+ goto cleanup;
+ }
+
+ VIR_FREE(desc);
+ desc = desc_edited;
+ desc_edited = NULL;
+ }
+
+ /* strip a possible newline at the end of file */
+ /* some editors enforce a newline, this makes editing the title
+ * more convinient */
+ if (title &&
+ (tmpstr = strrchr(desc, '\n')) &&
+ *(tmpstr+1) == '\0')
+ *tmpstr = '\0';
+
+ if (virDomainSetMetadata(dom, type, desc, NULL, NULL, flags) < 0) {
+ vshError(ctl, "%s",
+ _("Failed to set new domain description"));
+ goto cleanup;
+ }
+ vshPrint(ctl, "%s", _("Domain description updated
successfuly"));
+ } else {
+ desc = vshGetDomainDescription(ctl, dom, title,
+ config?VIR_DOMAIN_XML_INACTIVE:0);
+ if (!desc)
+ goto cleanup;
+
+ if (strlen(desc) > 0)
+ vshPrint(ctl, "%s", desc);
+ else
+ vshPrint(ctl, _("No description for domain: %s"),
+ virDomainGetName(dom));
+ }
+
+ ret = true;
+cleanup:
+ VIR_FREE(desc_edited);
+ VIR_FREE(desc);
+ if (tmp) {
+ unlink(tmp);
+ VIR_FREE(tmp);
+ }
+ return ret;
}
/*
@@ -16168,6 +16351,7 @@ static const vshCmdDef domManagementCmds[] = {
{"cpu-compare", cmdCPUCompare, opts_cpu_compare, info_cpu_compare, 0},
{"create", cmdCreate, opts_create, info_create, 0},
{"define", cmdDefine, opts_define, info_define, 0},
+ {"desc", cmdDesc, opts_desc, info_desc, 0},
{"destroy", cmdDestroy, opts_destroy, info_destroy, 0},
{"detach-device", cmdDetachDevice, opts_detach_device,
info_detach_device, 0},
@@ -17933,6 +18117,65 @@ vshDomainStateReasonToString(int state, int reason)
return N_("unknown");
}
+/* extract description or title from domain xml */
+static char *
+vshGetDomainDescription(vshControl *ctl, virDomainPtr dom, bool title,
+ unsigned int flags)
+{
+ char *desc = NULL;
+ char *domxml = NULL;
+ virErrorPtr err = NULL;
+ xmlDocPtr doc = NULL;
+ xmlXPathContextPtr ctxt = NULL;
+ int type;
+
+ if (title)
+ type = VIR_DOMAIN_METADATA_TITLE;
+ else
+ type = VIR_DOMAIN_METADATA_DESCRIPTION;
+
+ if ((desc = virDomainGetMetadata(dom, type, NULL, flags))) {
+ return desc;
+ } else {
+ err = virGetLastError();
+
+ if (err && err->code == VIR_ERR_NO_DOMAIN_METADATA) {
+ desc = vshStrdup(ctl, "");
+ virResetLastError();
+ return desc;
+ }
+
+ if (err && err->code != VIR_ERR_NO_SUPPORT)
+ return desc;
+ }
+
+ /* fall back to xml */
+ /* get domains xml description and extract the title/description */
+ if (!(domxml = virDomainGetXMLDesc(dom, flags))) {
+ vshError(ctl, "%s", _("Failed to retrieve domain XML"));
+ goto cleanup;
+ }
+ doc = virXMLParseStringCtxt(domxml, _("(domain_definition)"), &ctxt);
+ if (!doc) {
+ vshError(ctl, "%s", _("Couldn't parse domain XML"));
+ goto cleanup;
+ }
+ if (title)
+ desc = virXPathString("string(./title[1])", ctxt);
+ else
+ desc = virXPathString("string(./description[1])", ctxt);
+
+ if (!desc)
+ desc = vshStrdup(ctl, "");
+
+cleanup:
+ VIR_FREE(domxml);
+ xmlXPathFreeContext(ctxt);
+ xmlFreeDoc(doc);
+
+ return desc;
+}
+
/* Return a non-NULL string representation of a typed parameter; exit
* if we are out of memory. */
static char *
diff --git a/tools/virsh.pod b/tools/virsh.pod
index 4bc25bf..0dfde56 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -279,7 +279,7 @@ The XML also show the NUMA topology information if available.
Inject NMI to the guest.
-=item B<list> [I<--inactive> | I<--all>] [I<--managed-save>]
+=item B<list> [I<--inactive> | I<--all>] [I<--managed-save>]
[I<--title>]
Prints information about existing domains. If no options are
specified it prints out information about running domains.
@@ -350,6 +350,15 @@ If I<--managed-save> is specified, then domains that have
managed save
state (only possible if they are in the B<shut off> state) will
instead show as B<saved> in the listing.
+If I<--title> is specified, then the domain note is printed. The output then
+the output looks as follows.
+
+B<virsh> list --note
+ Id Name State Title
+-----------------------------------------------
+ 0 Domain-0 running Mailserver 1
+ 2 fedora paused
+
=item B<freecell> [B<cellno> | I<--all>]
Prints the available amount of memory on the machine or within a
@@ -426,6 +435,29 @@ Define a domain from an XML <file>. The domain definition is
registered
but not started. If domain is already running, the changes will take
effect on the next boot.
+=item B<desc> [I<--live> | I<--config>] [I<--title>]
[I<--edit>]
+ [I<--new-desc> New description or title message]
+
+Show or modify description and title of a domain. These values are user
+fields that allow to store arbitrary textual data to allow easy identifiaction
+of domains. Note should be short, although it's not enforced.
+
+Flags I<--live> or I<--config> select wether this command works on live
+or persistent definitions of the domain. By default both are infuenced, while
+modifying and running definition is used while reading the note.
+
+If both I<--live> and I<--config> are specified, the I<--config> option
takes
+predcedence on getting the current description and both live configuration
+and config are updated while setting the description.
+
+Flag I<--edit> specifies that an editor with the contents of current description
+or note should be opened and the contents saved back afterwards.
+
+Flag I<--title> selects operation on the note field instead of description.
+
+If neither of I<--edit> and I<--new_desc> are specified the note or
description
+is displayed instead of being modified.
+
=item B<destroy> I<domain-id>
Immediately terminate the domain domain-id. This doesn't give the domain
--
1.7.3.4