[libvirt] [PATCHv3 3/4] virsh: Add support for modifying domain description and titles

Peter Krempa pkrempa at redhat.com
Wed Feb 1 13:03:51 UTC 2012


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
---
 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 23962bf..cb5b88a 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;
 }

 /*
@@ -16245,6 +16428,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 +18195,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 e9598ac..71dea2c 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




More information about the libvir-list mailing list