This patch adds a new command "desc" to show and modify notes and description for the domains using the new API. This patch also adds a new flag for the "list" command to show notes in the domain list, to allow easy identification of VMs by storing a short description. Example: virsh # list --note Id Name State Note ----------------------------------------------- 0 Domain-0 running Mailserver 1 2 fedora paused --- tools/virsh.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++----- tools/virsh.pod | 30 +++++++- 2 files changed, 255 insertions(+), 21 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index f223593..a1f9236 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -312,6 +312,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 note, unsigned int flags) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_RETURN_CHECK; #define VSH_BYID (1 << 1) #define VSH_BYUUID (1 << 2) @@ -885,6 +888,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")}, + {"note", VSH_OT_BOOL, 0, N_("show short domain description")}, {NULL, 0, 0, NULL} }; @@ -899,7 +903,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, "note"); + char *note; int state; + bool ret = false; inactive |= all; @@ -917,8 +924,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); @@ -928,37 +934,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"), _("Note")); + 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 (!(note = vshGetDomainDescription(ctl, dom, true, 0))) + goto cleanup; + + vshPrint(ctl, "%-5d %-30s %-10s %s\n", + virDomainGetID(dom), + virDomainGetName(dom), + _(vshDomainStateToString(vshDomainState(ctl, dom, NULL))), + note); + VIR_FREE(note); + } 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]); @@ -974,17 +995,163 @@ 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 (!(note = vshGetDomainDescription(ctl, dom, true, 0))) + goto cleanup; + + vshPrint(ctl, "%-5s %-30s %-10s %s\n", + "-", + names[i], + state == -2 ? _("saved") : _(vshDomainStateToString(state)), + note); + VIR_FREE(note); + } 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 note + */ +static const vshCmdInfo info_desc[] = { + {"help", N_("show or set domain's description or note")}, + {"desc", N_("Allows to show or modify description or note of a domain.")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_desc[] = { + {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, + {"persistent", VSH_OT_BOOL, 0, N_("modify persistent state of domain")}, + {"live", VSH_OT_BOOL, 0, N_("modify note only for current instance")}, + {"note", VSH_OT_BOOL, 0, N_("modify the note 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 inactive = vshCommandOptBool(cmd, "persistent"); + bool live = vshCommandOptBool(cmd, "live"); + + bool note = vshCommandOptBool(cmd, "note"); + bool edit = vshCommandOptBool(cmd, "edit"); + + int state; + char *desc = NULL; + char *desc_edited = NULL; + char *tmp = NULL; + const vshCmdOpt *opt = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + bool pad = false; + bool ret = false; + unsigned int flags = VIR_DOMAIN_DESCRIPTION_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_DESCRIPTION_LIVE; + if (inactive) + flags |= VIR_DOMAIN_DESCRIPTION_CONFIG; + if (!(inactive || live)) { + flags |= VIR_DOMAIN_DESCRIPTION_CONFIG; + if (state == VIR_DOMAIN_RUNNING || state == VIR_DOMAIN_PAUSED) + flags |= VIR_DOMAIN_DESCRIPTION_LIVE; + } + + if (virBufferError(&buf)) { + vshPrint(ctl, "%s", _("Failed to collect new description/note")); + goto cleanup; + } + desc = virBufferContentAndReset(&buf); + + if (edit || desc) { + if (!desc) { + desc = vshGetDomainDescription(ctl, dom, note, + inactive?VIR_DOMAIN_XML_INACTIVE:0); + if (!desc) + goto cleanup; + } + + if (edit) { + /* Create and open the temporary file. */ + tmp = editWriteToTempFile (ctl, desc); + if (!tmp) goto cleanup; + + /* Start the editor. */ + if (editFile (ctl, tmp) == -1) goto cleanup; + + /* Read back the edited file. */ + desc_edited = editReadBackFile (ctl, tmp); + if (!desc_edited) 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; + } + + if (virDomainSetDescription(dom, desc, flags) < 0) { + vshError(ctl, "%s", + _("Failed to set new domain description")); + goto cleanup; + } + } else { + desc = vshGetDomainDescription(ctl, dom, note, + inactive?VIR_DOMAIN_XML_INACTIVE:0); + if (desc) + 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; } /* @@ -15951,6 +16118,7 @@ static const vshCmdDef domManagementCmds[] = { {"migrate-getspeed", cmdMigrateGetMaxSpeed, opts_migrate_getspeed, info_migrate_getspeed, 0}, {"numatune", cmdNumatune, opts_numatune, info_numatune, 0}, + {"desc", cmdDesc, opts_desc, info_desc, 0}, {"reboot", cmdReboot, opts_reboot, info_reboot, 0}, {"reset", cmdReset, opts_reset, info_reset, 0}, {"restore", cmdRestore, opts_restore, info_restore, 0}, @@ -17671,6 +17839,44 @@ vshDomainStateReasonToString(int state, int reason) return N_("unknown"); } +/* extract note from domain xml */ +static char * +vshGetDomainDescription(vshControl *ctl, virDomainPtr dom, bool note, + unsigned int flags) +{ + char *desc = NULL; + char *domxml = NULL; + xmlDocPtr doc = NULL; + xmlXPathContextPtr ctxt = NULL; + + /* get domains xml description and extract the note */ + 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 (note) + desc = virXPathString("string(./description[1]/@note)", 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 c88395b..afea430 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<--note>] 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<--note> is specified, then the domain note is printed. The output then +the output looks as follows. + +B<virsh> list --note + Id Name State Note +----------------------------------------------- + 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,25 @@ 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<--persistent>] [I<--note>] [I<--edit>] + [I<--new_desc> New description or note message] + +Show or modify description and note for a domain. These values are user +fields that allow to store arbitrary textual data to allow easy identifiaction +of domains. Note is a short (maximum 40 characters) field. + +Flags I<--live> or I<--persistent> 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. + +Flag I<--edit> specifies that an editor with the contents of current description +or note should be opened and the contents save back afterwards. + +Flag I<--note> 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list