Re: [PATCH v7 15/23] backup: Implement virsh support for checkpoints

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Mar 27, 2019 at 05:10:46AM -0500, Eric Blake wrote:
Introduce a bunch of new virsh commands for managing checkpoints
in isolation. More commands are needed for performing incremental
backups, but these commands were easy to implement by modeling
heavily after virsh-snapshot.c (no need for checkpoint-revert,
and checkpoint-list was a lot easier since we don't have to cater
to older libvirt API).

Signed-off-by: Eric Blake <eblake@xxxxxxxxxx>
---
tools/virsh-checkpoint.h     |   29 +
tools/virsh-completer.h      |    4 +
tools/virsh-util.h           |    3 +
tools/virsh.h                |    1 +
po/POTFILES                  |    1 +
tools/Makefile.am            |    3 +-
tools/virsh-checkpoint.c     | 1370 ++++++++++++++++++++++++++++++++++
tools/virsh-completer.c      |   53 +-
tools/virsh-domain-monitor.c |   25 +-
tools/virsh-domain.c         |   15 +
tools/virsh-util.c           |   11 +
tools/virsh.c                |    2 +
tools/virsh.pod              |  238 +++++-
13 files changed, 1745 insertions(+), 10 deletions(-)
create mode 100644 tools/virsh-checkpoint.h
create mode 100644 tools/virsh-checkpoint.c

+static bool
+cmdCheckpointInfo(vshControl *ctl,
+                  const vshCmd *cmd)
+{
+    virDomainPtr dom;
+    virDomainCheckpointPtr checkpoint = NULL;
+    const char *name;
+    char *parent = NULL;
+    bool ret = false;
+    int count;
+    unsigned int flags;
+    int current;
+    int metadata;
+
+    dom = virshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        return false;
+
+    if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom,
+                              &checkpoint, &name) < 0)
+        goto cleanup;
+
+    vshPrint(ctl, "%-15s %s\n", _("Name:"), name);
+    vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom));

We have vshTableNew and vshTableRowAppend that can compute the
indentation at run-time regardless of the locale.

+
+    /* Determine if checkpoint is current.  */
+    current = virDomainCheckpointIsCurrent(checkpoint, 0);
+    if (current < 0) {
+        vshError(ctl, "%s",
+                 _("unexpected problem querying checkpoint state"));
+        goto cleanup;
+    }
+    vshPrint(ctl, "%-15s %s\n", _("Current:"),
+             current > 0 ? _("yes") : _("no"));
+
+    if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0) {
+        vshError(ctl, "%s",
+                 _("unexpected problem querying checkpoint state"));
+        goto cleanup;
+    }
+    vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : "-");
+
+    /* Children, Descendants.  */
+    flags = 0;
+    count = virDomainCheckpointListAllChildren(checkpoint, NULL, flags);
+    if (count < 0) {
+        if (last_error->code == VIR_ERR_NO_SUPPORT) {
+            vshResetLibvirtError();
+            ret = true;
+        }
+        goto cleanup;
+    }
+    vshPrint(ctl, "%-15s %d\n", _("Children:"), count);
+    flags = VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS;
+    count = virDomainCheckpointListAllChildren(checkpoint, NULL, flags);
+    if (count < 0)
+        goto cleanup;
+    vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count);
+
+    /* Metadata.  */
+    metadata = virDomainCheckpointHasMetadata(checkpoint, 0);
+    if (metadata >= 0)
+        vshPrint(ctl, "%-15s %s\n", _("Metadata:"),
+                 metadata ? _("yes") : _("no"));
+
+    ret = true;
+
+ cleanup:
+    VIR_FREE(parent);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+    return ret;
+}

[...]

+
+static bool
+cmdCheckpointList(vshControl *ctl,
+                  const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    unsigned int flags = 0;
+    size_t i;
+    xmlDocPtr xml = NULL;
+    xmlXPathContextPtr ctxt = NULL;
+    char *doc = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    long long creation_longlong;
+    time_t creation_time_t;
+    char timestr[100];
+    struct tm time_info;
+    bool tree = vshCommandOptBool(cmd, "tree");
+    bool name = vshCommandOptBool(cmd, "name");
+    bool from = vshCommandOptBool(cmd, "from");
+    bool parent = vshCommandOptBool(cmd, "parent");
+    bool roots = vshCommandOptBool(cmd, "roots");
+    bool current = vshCommandOptBool(cmd, "current");
+    const char *from_chk = NULL;
+    char *parent_chk = NULL;
+    virDomainCheckpointPtr start = NULL;
+    virshCheckpointListPtr chklist = NULL;
+
+    VSH_EXCLUSIVE_OPTIONS_VAR(tree, name);
+    VSH_EXCLUSIVE_OPTIONS_VAR(parent, roots);
+    VSH_EXCLUSIVE_OPTIONS_VAR(parent, tree);
+    VSH_EXCLUSIVE_OPTIONS_VAR(roots, tree);
+    VSH_EXCLUSIVE_OPTIONS_VAR(roots, from);
+    VSH_EXCLUSIVE_OPTIONS_VAR(roots, current);
+
+#define FILTER(option, flag) \
+    do { \
+        if (vshCommandOptBool(cmd, option)) { \
+            if (tree) { \
+                vshError(ctl, \
+                         _("--%s and --tree are mutually exclusive"), \
+                         option); \
+                return false; \
+            } \
+            flags |= VIR_DOMAIN_CHECKPOINT_LIST_ ## flag; \
+        } \
+    } while (0)
+
+    FILTER("leaves", LEAVES);
+    FILTER("no-leaves", NO_LEAVES);
+#undef FILTER
+
+    if (vshCommandOptBool(cmd, "topological"))
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL;
+
+    if (roots)
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_ROOTS;
+
+    if (vshCommandOptBool(cmd, "metadata"))
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_METADATA;
+
+    if (vshCommandOptBool(cmd, "no-metadata"))
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_NO_METADATA;
+
+    if (vshCommandOptBool(cmd, "descendants")) {
+        if (!from && !current) {
+            vshError(ctl, "%s",
+                     _("--descendants requires either --from or --current"));
+            return false;
+        }
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS;
+    }
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return false;
+
+    if ((from || current) &&
+        virshLookupCheckpoint(ctl, cmd, "from", true, dom, &start, &from_chk) < 0)
+        goto cleanup;
+
+    if (!(chklist = virshCheckpointListCollect(ctl, dom, start, flags, tree)))
+        goto cleanup;
+
+    if (!tree && !name) {
+        if (parent)
+            vshPrintExtra(ctl, " %-20s %-25s %s",
+                          _("Name"), _("Creation Time"), _("Parent"));
+        else
+            vshPrintExtra(ctl, " %-20s %-25s",
+                          _("Name"), _("Creation Time"));

vshTableNew

+        vshPrintExtra(ctl, "\n"
+                           "------------------------------"
+                           "--------------\n");
+    }
+
+    if (tree) {
+        for (i = 0; i < chklist->nchks; i++) {
+            if (!chklist->chks[i].parent &&
+                vshTreePrint(ctl, virshCheckpointListLookup, chklist,
+                             chklist->nchks, i) < 0)
+                goto cleanup;
+        }
+        ret = true;
+        goto cleanup;
+    }
+
+    for (i = 0; i < chklist->nchks; i++) {

Putting this whole loop in a separate function would make this function
more readable - both tree and non-tree would share the same 'ret = true'
assignment and, more importantly, all the XML parsing stuff is only
needed for non-tree printing.

+        const char *chk_name;
+
+        /* free up memory from previous iterations of the loop */

Sounds like a job for VIR_AUTOFREE

+        VIR_FREE(parent_chk);
+        xmlXPathFreeContext(ctxt);
+        xmlFreeDoc(xml);
+        VIR_FREE(doc);
+
+        checkpoint = chklist->chks[i].chk;
+        chk_name = virDomainCheckpointGetName(checkpoint);
+        assert(chk_name);
+
+        if (name) {
+            /* just print the checkpoint name */
+            vshPrint(ctl, "%s\n", chk_name);
+            continue;
+        }
+
+        if (!(doc = virDomainCheckpointGetXMLDesc(checkpoint, 0)))
+            continue;
+
+        if (!(xml = virXMLParseStringCtxt(doc, _("(domain_checkpoint)"), &ctxt)))
+            continue;
+
+        if (parent)
+            parent_chk = virXPathString("string(/domaincheckpoint/parent/name)",
+                                        ctxt);
+
+        if (virXPathLongLong("string(/domaincheckpoint/creationTime)", ctxt,
+                             &creation_longlong) < 0)
+            continue;
+        creation_time_t = creation_longlong;
+        if (creation_time_t != creation_longlong) {
+            vshError(ctl, "%s", _("time_t overflow"));
+            continue;
+        }
+        localtime_r(&creation_time_t, &time_info);
+        strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z",
+                 &time_info);
+
+        if (parent)
+            vshPrint(ctl, " %-20s %-25s %s\n",
+                     chk_name, timestr, parent_chk ?: "-");

vshTableRowApend

+        else
+            vshPrint(ctl, " %-20s %-25s\n", chk_name, timestr);
+    }
+
+    ret = true;
+
+ cleanup:
+    /* this frees up memory from the last iteration of the loop */
+    virshCheckpointListFree(chklist);
+    VIR_FREE(parent_chk);
+    virshDomainCheckpointFree(start);
+    xmlXPathFreeContext(ctxt);
+    xmlFreeDoc(xml);
+    VIR_FREE(doc);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/*
+ * "checkpoint-dumpxml" command
+ */
+static const vshCmdInfo info_checkpoint_dumpxml[] = {
+    {.name = "help",
+     .data = N_("Dump XML for a domain checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Checkpoint Dump XML")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_dumpxml[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_DATA,
+     .flags = VSH_OFLAG_REQ,
+     .help = N_("checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    {.name = "security-info",
+     .type = VSH_OT_BOOL,
+     .help = N_("include security sensitive information in XML dump")
+    },
+    {.name = "no-domain",
+     .type = VSH_OT_BOOL,
+     .help = N_("exclude <domain> from XML")
+    },
+    {.name = "size",
+     .type = VSH_OT_BOOL,
+     .help = N_("include backup size estimate in XML dump")
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointDumpXML(vshControl *ctl,
+                     const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    char *xml = NULL;
+    unsigned int flags = 0;
+
+    if (vshCommandOptBool(cmd, "security-info"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_SECURE;
+    if (vshCommandOptBool(cmd, "no-domain"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN;
+    if (vshCommandOptBool(cmd, "size"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_SIZE;
+
+    if (vshCommandOptStringReq(ctl, cmd, "checkpointname", &name) < 0)
+        return false;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return false;
+
+    if (!(checkpoint = virDomainCheckpointLookupByName(dom, name, 0)))
+        goto cleanup;
+
+    if (!(xml = virDomainCheckpointGetXMLDesc(checkpoint, flags)))
+        goto cleanup;
+
+    vshPrint(ctl, "%s", xml);
+    ret = true;
+
+ cleanup:
+    VIR_FREE(xml);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/*
+ * "checkpoint-parent" command
+ */
+static const vshCmdInfo info_checkpoint_parent[] = {
+    {.name = "help",
+     .data = N_("Get the name of the parent of a checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Extract the checkpoint's parent, if any")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_parent[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_STRING,
+     .help = N_("find parent of checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("find parent of current checkpoint")),
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointParent(vshControl *ctl,
+                    const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    char *parent = NULL;
+
+    dom = virshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom,
+                              &checkpoint, &name) < 0)
+        goto cleanup;
+
+    if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0)
+        goto cleanup;
+    if (!parent) {
+        vshError(ctl, _("checkpoint '%s' has no parent"), name);
+        goto cleanup;
+    }
+
+    vshPrint(ctl, "%s", parent);
+
+    ret = true;
+
+ cleanup:
+    VIR_FREE(parent);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/*
+ * "checkpoint-delete" command
+ */
+static const vshCmdInfo info_checkpoint_delete[] = {
+    {.name = "help",
+     .data = N_("Delete a domain checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Checkpoint Delete")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_delete[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_STRING,
+     .help = N_("checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("delete current checkpoint")),
+    {.name = "children",
+     .type = VSH_OT_BOOL,
+     .help = N_("delete checkpoint and all children")
+    },
+    {.name = "children-only",
+     .type = VSH_OT_BOOL,
+     .help = N_("delete children but not checkpoint")
+    },
+    {.name = "metadata",
+     .type = VSH_OT_BOOL,
+     .help = N_("delete only libvirt metadata, leaving checkpoint contents behind")
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointDelete(vshControl *ctl,
+                    const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    unsigned int flags = 0;
+
+    dom = virshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom,
+                              &checkpoint, &name) < 0)
+        goto cleanup;
+
+    if (vshCommandOptBool(cmd, "children"))
+        flags |= VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN;
+    if (vshCommandOptBool(cmd, "children-only"))
+        flags |= VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY;
+    if (vshCommandOptBool(cmd, "metadata"))
+        flags |= VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY;
+
+    if (virDomainCheckpointDelete(checkpoint, flags) == 0) {
+        if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY)
+            vshPrintExtra(ctl, _("Domain checkpoint %s children deleted\n"), name);
+        else
+            vshPrintExtra(ctl, _("Domain checkpoint %s deleted\n"), name);
+    } else {
+        vshError(ctl, _("Failed to delete checkpoint %s"), name);
+        goto cleanup;
+    }
+
+    ret = true;
+
+ cleanup:
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+const vshCmdDef checkpointCmds[] = {
+    {.name = "checkpoint-create",
+     .handler = cmdCheckpointCreate,
+     .opts = opts_checkpoint_create,
+     .info = info_checkpoint_create,
+     .flags = 0
+    },
+    {.name = "checkpoint-create-as",
+     .handler = cmdCheckpointCreateAs,
+     .opts = opts_checkpoint_create_as,
+     .info = info_checkpoint_create_as,
+     .flags = 0
+    },
+    {.name = "checkpoint-current",
+     .handler = cmdCheckpointCurrent,
+     .opts = opts_checkpoint_current,
+     .info = info_checkpoint_current,
+     .flags = 0
+    },
+    {.name = "checkpoint-delete",
+     .handler = cmdCheckpointDelete,
+     .opts = opts_checkpoint_delete,
+     .info = info_checkpoint_delete,
+     .flags = 0
+    },
+    {.name = "checkpoint-dumpxml",
+     .handler = cmdCheckpointDumpXML,
+     .opts = opts_checkpoint_dumpxml,
+     .info = info_checkpoint_dumpxml,
+     .flags = 0
+    },
+    {.name = "checkpoint-edit",
+     .handler = cmdCheckpointEdit,
+     .opts = opts_checkpoint_edit,
+     .info = info_checkpoint_edit,
+     .flags = 0
+    },
+    {.name = "checkpoint-info",
+     .handler = cmdCheckpointInfo,
+     .opts = opts_checkpoint_info,
+     .info = info_checkpoint_info,
+     .flags = 0
+    },
+    {.name = "checkpoint-list",
+     .handler = cmdCheckpointList,
+     .opts = opts_checkpoint_list,
+     .info = info_checkpoint_list,
+     .flags = 0
+    },
+    {.name = "checkpoint-parent",
+     .handler = cmdCheckpointParent,
+     .opts = opts_checkpoint_parent,
+     .info = info_checkpoint_parent,
+     .flags = 0
+    },
+    {.name = NULL}
+};
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c
index c4adbb70d0..ed5e1015c9 100644
--- a/tools/virsh-completer.c
+++ b/tools/virsh-completer.c
@@ -1,7 +1,7 @@
/*
 * virsh-completer.c: virsh completer callbacks
 *
- * Copyright (C) 2017 Red Hat, Inc.
+ * Copyright (C) 2017-2019 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
@@ -623,6 +623,57 @@ virshSecretUUIDCompleter(vshControl *ctl,
}


+char **
+virshCheckpointNameCompleter(vshControl *ctl,
+                             const vshCmd *cmd,
+                             unsigned int flags)
+{
+    virshControlPtr priv = ctl->privData;
+    virDomainPtr dom = NULL;
+    virDomainCheckpointPtr *checkpoints = NULL;
+    int ncheckpoints = 0;
+    size_t i = 0;
+    char **ret = NULL;
+
+    virCheckFlags(0, NULL);
+
+    if (!priv->conn || virConnectIsAlive(priv->conn) <= 0)
+        return NULL;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return NULL;
+
+    if ((ncheckpoints = virDomainListAllCheckpoints(dom, &checkpoints,
+                                                    flags)) < 0)

I recommend adding an 'int rc' variable and only assigning the result to
ncheckpoints if it's non-negative,

+        goto error;
+
+    if (VIR_ALLOC_N(ret, ncheckpoints + 1) < 0)
+        goto error;
+
+    for (i = 0; i < ncheckpoints; i++) {
+        const char *name = virDomainCheckpointGetName(checkpoints[i]);
+
+        if (VIR_STRDUP(ret[i], name) < 0)
+            goto error;
+
+        virshDomainCheckpointFree(checkpoints[i]);
+    }
+    VIR_FREE(checkpoints);
+    virshDomainFree(dom);
+
+    return ret;
+
+ error:
+    for (; i < ncheckpoints; i++)
+        virshDomainCheckpointFree(checkpoints[i]);

otherwise this loop will try to run for a long time.
Also, given that we free these on success too, having a shared 'cleanup'
path would be nicer.


+    VIR_FREE(checkpoints);
+    for (i = 0; i < ncheckpoints; i++)
+        VIR_FREE(ret[i]);
+    VIR_FREE(ret);
+    virshDomainFree(dom);
+    return NULL;
+}
+
char **
virshSnapshotNameCompleter(vshControl *ctl,
                           const vshCmd *cmd,

Jano

Attachment: signature.asc
Description: PGP signature

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list

[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]

  Powered by Linux