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

Ján Tomko jtomko at redhat.com
Wed Mar 27 14:29:53 UTC 2019


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 at redhat.com>
>---
> 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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20190327/db1b1206/attachment-0001.sig>


More information about the libvir-list mailing list