Introduce a few more new virsh commands for performing backup jobs.
At this time, I did not opt for a convenience command
'backup-begin-as' that cobbles together appropriate XML from the
user's command line arguments, but that may be a viable future
extension. Similarly, since backup is a potentially long-running
operation, it might be nice to add some sugar that automatically
handles waiting for the job to end, rather than making the user have
to poll or figure out virsh event to do the same. Eventually, we
will also need a way to create a checkpoint atomically with an
external snapshot.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
tools/virsh-domain.c | 245 +++++++++++++++++++++++++++++++++++++++++++
tools/virsh.pod | 49 +++++++++
2 files changed, 294 insertions(+)
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c
index abc9001508..83c89400eb 100644
--- a/tools/virsh-domain.c
+++ b/tools/virsh-domain.c
@@ -14041,6 +14041,233 @@ cmdDomFSInfo(vshControl *ctl, const vshCmd *cmd)
return ret;
}
+
+/*
+ * "backup-begin" command
+ */
+static const vshCmdInfo info_backup_begin[] = {
+ {.name = "help",
+ .data = N_("Start a disk backup of a live domain")
+ },
+ {.name = "desc",
+ .data = N_("Use XML to start a full or incremental disk backup of a live
"
+ "domain, optionally creating a checkpoint")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_backup_begin[] = {
+ VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+ {.name = "xmlfile",
+ .type = VSH_OT_STRING,
+ .help = N_("domain backup XML"),
+ },
+ {.name = "checkpointxml",
+ .type = VSH_OT_STRING,
+ .help = N_("domain checkpoint XML"),
+ },
+ {.name = "no-metadata",
+ .type = VSH_OT_BOOL,
+ .help = N_("create checkpoint but don't track metadata"),
+ },
+ {.name = "quiesce",
+ .type = VSH_OT_BOOL,
+ .help = N_("quiesce guest's file systems"),
+ },
+ /* TODO: --wait/--verbose/--timeout flags for push model backups? */
+ {.name = NULL}
+};
+
+static bool
+cmdBackupBegin(vshControl *ctl,
+ const vshCmd *cmd)
+{
+ virDomainPtr dom = NULL;
+ bool ret = false;
+ const char *backup_from = NULL;
+ char *backup_buffer = NULL;
+ const char *check_from = NULL;
+ char *check_buffer = NULL;
+ unsigned int flags = 0;
+ int id;
+
+ if (vshCommandOptBool(cmd, "no-metadata"))
+ flags |= VIR_DOMAIN_BACKUP_BEGIN_NO_METADATA;
+ if (vshCommandOptBool(cmd, "quiesce"))
+ flags |= VIR_DOMAIN_BACKUP_BEGIN_QUIESCE;
+
+ if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+ goto cleanup;
+
+ if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &backup_from) < 0)
+ goto cleanup;
+ if (!backup_from) {
+ backup_buffer = vshStrdup(ctl, "<domainbackup/>");
+ } else {
+ if (virFileReadAll(backup_from, VSH_MAX_XML_FILE, &backup_buffer) < 0) {
+ vshSaveLibvirtError();
+ goto cleanup;
+ }
+ }
+
+ if (vshCommandOptStringReq(ctl, cmd, "checkpointxml", &check_from) <
0)
+ goto cleanup;
+ if (check_from) {
+ if (virFileReadAll(check_from, VSH_MAX_XML_FILE, &check_buffer) < 0) {
+ vshSaveLibvirtError();
+ goto cleanup;
+ }
+ }
+
+ id = virDomainBackupBegin(dom, backup_buffer, check_buffer, flags);
+
+ if (id < 0)
+ goto cleanup;
+
+ vshPrint(ctl, _("Backup id %d started\n"), id);
+ if (backup_from)
+ vshPrintExtra(ctl, _("backup used description from '%s'\n"),
+ backup_from);
+ if (check_buffer)
+ vshPrintExtra(ctl, _("checkpoint created from '%s'\n"),
check_from);
+
+ ret = true;
+
+ cleanup:
+ VIR_FREE(backup_buffer);
+ VIR_FREE(check_buffer);
+ virshDomainFree(dom);
+
+ return ret;
+}
+
+/* TODO: backup-begin-as? */
+
+/*
+ * "backup-dumpxml" command
+ */
+static const vshCmdInfo info_backup_dumpxml[] = {
+ {.name = "help",
+ .data = N_("Dump XML for an ongoing domain block backup job")
+ },
+ {.name = "desc",
+ .data = N_("Backup Dump XML")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_backup_dumpxml[] = {
+ VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+ {.name = "id",
+ .type = VSH_OT_INT,
+ .help = N_("backup job id"),
+ /* TODO: Add API for listing active jobs, then adding a completer? */
+ },
+ /* TODO - worth adding this flag?
+ {.name = "checkpoint",
+ .type = VSH_OT_BOOL,
+ .help = N_("if the backup created a checkpoint, also dump that XML")
+ },
+ */
+ {.name = NULL}
+};
+
+static bool
+cmdBackupDumpXML(vshControl *ctl,
+ const vshCmd *cmd)
+{
+ virDomainPtr dom = NULL;
+ bool ret = false;
+ char *xml = NULL;
+ unsigned int flags = 0;
+ int id = 0;
+
+ if (vshCommandOptBool(cmd, "security-info"))
+ flags |= VIR_DOMAIN_XML_SECURE;
+
+ if (vshCommandOptInt(ctl, cmd, "id", &id) < 0)
+ return false;
+
+ if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+ return false;
+
+ if (!(xml = virDomainBackupGetXMLDesc(dom, id, flags)))
+ goto cleanup;
+
+ vshPrint(ctl, "%s", xml);
+ ret = true;
+
+ cleanup:
+ VIR_FREE(xml);
+ virshDomainFree(dom);
+
+ return ret;
+}
+
+
+/*
+ * "backup-end" command
+ */
+static const vshCmdInfo info_backup_end[] = {
+ {.name = "help",
+ .data = N_("Conclude a disk backup of a live domain")
+ },
+ {.name = "desc",
+ .data = N_("End a domain block backup job")
+ },
+ {.name = NULL}
+};
+
+static const vshCmdOptDef opts_backup_end[] = {
+ VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+ {.name = "id",
+ .type = VSH_OT_INT,
+ .help = N_("backup job id"),
+ /* TODO: Add API for listing active jobs, then adding a completer? */
+ },
+ {.name = "abort",
+ .type = VSH_OT_BOOL,
+ .help = N_("abandon a push model backup that has not yet completed")
+ },
+ {.name = NULL}
+};
+
+static bool
+cmdBackupEnd(vshControl *ctl, const vshCmd *cmd)
+{
+ virDomainPtr dom = NULL;
+ bool ret = false;
+ unsigned int flags = 0;
+ int id = 0;
+ int rc;
+
+ if (vshCommandOptBool(cmd, "abort"))
+ flags |= VIR_DOMAIN_BACKUP_END_ABORT;
+
+ if (vshCommandOptInt(ctl, cmd, "id", &id) < 0)
+ return false;
+
+ if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+ goto cleanup;
+
+ rc = virDomainBackupEnd(dom, id, flags);
+
+ if (rc < 0)
+ goto cleanup;
+ if (rc == 0)
+ vshPrint(ctl, _("Backup id %d aborted"), id);
+ else
+ vshPrint(ctl, _("Backup id %d completed"), id);
+
+ ret = true;
+
+ cleanup:
+ virshDomainFree(dom);
+
+ return ret;
+}
+
+
const vshCmdDef domManagementCmds[] = {
{.name = "attach-device",
.handler = cmdAttachDevice,
@@ -14066,6 +14293,24 @@ const vshCmdDef domManagementCmds[] = {
.info = info_autostart,
.flags = 0
},
+ {.name = "backup-begin",
+ .handler = cmdBackupBegin,
+ .opts = opts_backup_begin,
+ .info = info_backup_begin,
+ .flags = 0
+ },
+ {.name = "backup-dumpxml",
+ .handler = cmdBackupDumpXML,
+ .opts = opts_backup_dumpxml,
+ .info = info_backup_dumpxml,
+ .flags = 0
+ },
+ {.name = "backup-end",
+ .handler = cmdBackupEnd,
+ .opts = opts_backup_end,
+ .info = info_backup_end,
+ .flags = 0
+ },
{.name = "blkdeviotune",
.handler = cmdBlkdeviotune,
.opts = opts_blkdeviotune,
diff --git a/tools/virsh.pod b/tools/virsh.pod
index 8d69d349e9..7ca8869412 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -1352,6 +1352,55 @@ I<bandwidth> specifies copying bandwidth limit in MiB/s. For
further information
on the I<bandwidth> argument see the corresponding section for the
B<blockjob>
command.
+=item B<backup-begin> I<domain> [I<xmlfile>]
+[I<checkpointxml> [I<--no-metadata>]] [I<--quiesce>]
+
+Begin a new backup job, and output the resulting job id on success. If
+I<xmlfile> is omitted, this defaults to a full backup using a push
+model to filenames generated by libvirt; supplying XML allows
+fine-tuning such as requesting an incremental backup relative to an
+earlier checkpoint, controlling which disks participate or which
+filenames are involved, or requesting the use of a pull model backup.
+The B<backup-dumpxml> command shows any resulting values assigned by
+libvirt. For more information on backup XML, see:
+L<https://libvirt.org/formatbackup.html>.
+
+If I<checkpointxml> is specified, a second file with a top-level
+element of <domaincheckpoint> is used to create a simultaneous
+checkpoint, for doing a later incremental backup relative to the time
+the snapshot was created. If I<--no-metadata> is specified, then the
+checkpoint is created, but any metadata is immediately discarded. See
+B<checkpoint-create> for more details on checkpoints.
+
+If I<--quiesce> is specified, libvirt will try to use guest agent
+to freeze and unfreeze domain's mounted file systems. However,
+if domain has no guest agent, the backup job will fail.
+
+This command returns as soon as possible, and the backup job runs in
+the background; the progress of a push model backup can be checked
+with B<domjobinfo> or by waiting for an event with B<event> (the
+progress of a pull model backup is under the control of whatever third
+party connects to the NBD export). The job is ended with B<block-end>.
+
+=item B<backup-dumpxml> I<domain> [I<id>]
+
+Output XML describing the backup job I<id>. The default for I<id> is
+0, which works as long as there are no parallel jobs; it is also
+possible to use the positive id printed by B<backup-begin> on success.
+
+=item B<backup-end> I<domain> [I<id>] [I<--abort>]
+
+End the currentbackup job I<id>. The default for I<id> is 0, which
+works as long as there are no parallel jobs; it is also possible to
+use the positive id printed by B<backup-begin> on success.
+
+If the backup job is a push job, but the hypervisor is not yet
+complete, this command will fail unless I<--abort> is given; if
+aborted, the backup file is incomplete. If the backup job is a pull
+job, I<--abort> has no effect, because libvirt assumes the third-party
+client is done performing the backup.
+
+
=item B<blkdeviotune> I<domain> I<device>
[[I<--config>] [I<--live>] | [I<--current>]]
[[I<total-bytes-sec>] | [I<read-bytes-sec>] [I<write-bytes-sec>]]
--
2.20.1