virsh: Add support for modifying domain description and titles

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
This commit is contained in:
Peter Krempa 2012-02-01 14:03:51 +01:00 committed by Eric Blake
parent c471e55e10
commit fad5cd2108
2 changed files with 294 additions and 21 deletions

View File

@ -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,177 @@ 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)
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;
/* 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_edited, '\n')) &&
*(tmpstr+1) == '\0')
*tmpstr = '\0';
/* 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;
}
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 successfully"));
} 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;
}
/*
@ -16245,6 +16426,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},
@ -18011,6 +18193,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 domain's 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 *

View File

@ -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
identification of domains. Title should be short, although it's not enforced.
Flags I<--live> or I<--config> select whether this command works on live
or persistent definitions of the domain. By default both are influenced, 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
precedence 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 title should be opened and the contents saved back afterwards.
Flag I<--title> selects operation on the title 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