Still needs to actually kick off the right QMP commands, but at
least allows validation of backup XML, including the fact that
a backup job can survive a libvirtd restart. Atomically creating
a checkpoint alongside the backup still needs implementing.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
src/qemu/qemu_domain.h | 4 +
src/qemu/qemu_domain.c | 32 ++++++-
src/qemu/qemu_driver.c | 185 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 217 insertions(+), 4 deletions(-)
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index 9b29628107..7e3641c6d0 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -376,6 +376,10 @@ struct _qemuDomainObjPrivate {
/* true if global -mem-prealloc appears on cmd line */
bool memPrealloc;
+
+ /* Any currently running backup job.
+ * FIXME: allow jobs in parallel. For now, at most one job, always id 1. */
+ virDomainBackupDefPtr backup;
};
# define QEMU_DOMAIN_PRIVATE(vm) \
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 8a2b951dde..6648240dc4 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -57,6 +57,7 @@
#include "locking/domain_lock.h"
#include "virdomainsnapshotobjlist.h"
#include "virdomaincheckpointobjlist.h"
+#include "backup_conf.h"
#ifdef MAJOR_IN_MKDEV
# include <sys/mkdev.h>
@@ -2313,13 +2314,25 @@ static int
qemuDomainObjPrivateXMLFormatBlockjobs(virBufferPtr buf,
virDomainObjPtr vm)
{
+ qemuDomainObjPrivatePtr priv = vm->privateData;
virBuffer attrBuf = VIR_BUFFER_INITIALIZER;
bool bj = qemuDomainHasBlockjob(vm, false);
+ int ret = -1;
virBufferAsprintf(&attrBuf, " active='%s'",
virTristateBoolTypeToString(virTristateBoolFromBool(bj)));
- return virXMLFormatElement(buf, "blockjobs", &attrBuf, NULL);
+ if (virXMLFormatElement(buf, "blockjobs", &attrBuf, NULL) < 0)
+ goto cleanup;
+
+ /* TODO: merge other blockjobs and backups into uniform space? */
+ if (priv->backup && virDomainBackupDefFormat(buf, priv->backup, true)
< 0)
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
+ virBufferFreeAndReset(&attrBuf);
+ return ret;
}
@@ -2666,18 +2679,29 @@ qemuDomainObjPrivateXMLParseAutomaticPlacement(xmlXPathContextPtr
ctxt,
static int
-qemuDomainObjPrivateXMLParseBlockjobs(qemuDomainObjPrivatePtr priv,
+qemuDomainObjPrivateXMLParseBlockjobs(virQEMUDriverPtr driver,
+ qemuDomainObjPrivatePtr priv,
xmlXPathContextPtr ctxt)
{
+ xmlNodePtr node;
char *active;
int tmp;
+ int ret = -1;
if ((active = virXPathString("string(./blockjobs/@active)", ctxt))
&&
(tmp = virTristateBoolTypeFromString(active)) > 0)
priv->reconnectBlockjobs = tmp;
+ if ((node = virXPathNode("./domainbackup", ctxt)) &&
+ !(priv->backup = virDomainBackupDefParseNode(ctxt->doc, node,
+ driver->xmlopt,
+ VIR_DOMAIN_BACKUP_PARSE_INTERNAL)))
+ goto cleanup;
+
+ ret = 0;
+ cleanup:
VIR_FREE(active);
- return 0;
+ return ret;
}
@@ -3053,7 +3077,7 @@ qemuDomainObjPrivateXMLParse(xmlXPathContextPtr ctxt,
qemuDomainObjPrivateXMLParsePR(ctxt, &priv->prDaemonRunning);
- if (qemuDomainObjPrivateXMLParseBlockjobs(priv, ctxt) < 0)
+ if (qemuDomainObjPrivateXMLParseBlockjobs(driver, priv, ctxt) < 0)
goto error;
qemuDomainStorageIdReset(priv);
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 7eda8cf2c4..78c9f963ca 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -104,6 +104,7 @@
#include "virqemu.h"
#include "virdomainsnapshotobjlist.h"
#include "virdomaincheckpointobjlist.h"
+#include "backup_conf.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
@@ -17721,6 +17722,187 @@ qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint,
return ret;
}
+static int qemuDomainBackupBegin(virDomainPtr domain, const char *diskXml,
+ const char *checkpointXml, unsigned int flags)
+{
+ virQEMUDriverPtr driver = domain->conn->privateData;
+ virDomainObjPtr vm = NULL;
+ virDomainBackupDefPtr def = NULL;
+ virQEMUDriverConfigPtr cfg = NULL;
+ virCapsPtr caps = NULL;
+ qemuDomainObjPrivatePtr priv;
+ int ret = -1;
+ struct timeval tv;
+ char *suffix = NULL;
+
+ virCheckFlags(VIR_DOMAIN_BACKUP_BEGIN_NO_METADATA, -1);
+ /* TODO: VIR_DOMAIN_BACKUP_BEGIN_QUIESCE */
+
+ // FIXME: Support non-null checkpointXML for incremental - what
+ // code can be shared with CheckpointCreateXML, then add to transaction
+ // to create new checkpoint at same time as starting blockdev-backup
+ if (checkpointXml) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("cannot create incremental backups yet"));
+ return -1;
+ }
+ // if (chk) VIR_STRDUP(suffix, chk->name);
+ gettimeofday(&tv, NULL);
+ if (virAsprintf(&suffix, "%lld", (long long)tv.tv_sec) < 0)
+ goto cleanup;
+
+ if (!(vm = qemuDomObjFromDomain(domain)))
+ goto cleanup;
+
+ cfg = virQEMUDriverGetConfig(driver);
+
+ if (virDomainBackupBeginEnsureACL(domain->conn, vm->def, flags) < 0)
+ goto cleanup;
+
+ if (!(caps = virQEMUDriverGetCapabilities(driver, false)))
+ goto cleanup;
+
+ if (qemuProcessAutoDestroyActive(driver, vm)) {
+ virReportError(VIR_ERR_OPERATION_INVALID,
+ "%s", _("domain is marked for auto
destroy"));
+ goto cleanup;
+ }
+
+ if (!virDomainObjIsActive(vm)) {
+ virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
+ _("cannot perform disk backup for inactive domain"));
+ goto cleanup;
+ }
+ if (!(def = virDomainBackupDefParseString(diskXml, driver->xmlopt, 0)))
+ goto cleanup;
+
+ /* We are going to modify the domain below. */
+ if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
+ goto cleanup;
+
+ priv = vm->privateData;
+ if (priv->backup) {
+ virReportError(VIR_ERR_OPERATION_INVALID,
+ "%s", _("another backup job is already
running"));
+ goto endjob;
+ }
+
+ if (virDomainBackupAlignDisks(def, vm->def, suffix) < 0)
+ goto endjob;
+
+ /* actually start the checkpoint. 2x2 array of push/pull, full/incr,
+ plus additional tweak if checkpoint requested */
+ /* TODO: issue QMP commands:
+ - pull: nbd-server-start with <server> from user (or autogenerate server)
+ - push/pull: blockdev-add per <disk>
+ - incr: bitmap-add of tmp, bitmap-merge per <disk>
+ - transaction, containing:
+ - push+full: blockdev-backup sync:full
+ - push+incr: blockdev-backup sync:incremental bitmap:tmp
+ - pull+full: blockdev-backup sync:none
+ - pull+incr: blockdev-backup sync:none, bitmap-disable of tmp
+ - if checkpoint: bitmap-disable of old, bitmap-add of new
+ - pull: nbd-server-add per <disk>, including bitmap for incr
+ */
+
+ VIR_STEAL_PTR(priv->backup, def);
+ ret = priv->backup->id = 1; /* Hard-coded job id for now */
+ if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm,
+ driver->caps) < 0)
+ VIR_WARN("Unable to save status on vm %s after backup job",
+ vm->def->name);
+
+ endjob:
+ qemuDomainObjEndJob(driver, vm);
+
+ cleanup:
+ VIR_FREE(suffix);
+ virDomainObjEndAPI(&vm);
+ virDomainBackupDefFree(def);
+ virObjectUnref(caps);
+ virObjectUnref(cfg);
+ return ret;
+}
+
+static char *qemuDomainBackupGetXMLDesc(virDomainPtr domain, int id,
+ unsigned int flags)
+{
+ virDomainObjPtr vm = NULL;
+ char *xml = NULL;
+ qemuDomainObjPrivatePtr priv;
+ virBuffer buf = VIR_BUFFER_INITIALIZER;
+
+ virCheckFlags(0, NULL);
+
+ if (!(vm = qemuDomObjFromDomain(domain)))
+ return NULL;
+
+ if (virDomainBackupGetXMLDescEnsureACL(domain->conn, vm->def) < 0)
+ goto cleanup;
+
+ /* TODO: Allow more than one hard-coded job id */
+ priv = vm->privateData;
+ if (id != 1 || !priv->backup) {
+ virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT,
+ _("no domain backup job with id '%d'"), id);
+ goto cleanup;
+ }
+
+ if (virDomainBackupDefFormat(&buf, priv->backup, false) < 0)
+ goto cleanup;
+ xml = virBufferContentAndReset(&buf);
+
+ cleanup:
+ virDomainObjEndAPI(&vm);
+ return xml;
+}
+
+static int qemuDomainBackupEnd(virDomainPtr domain, int id, unsigned int flags)
+{
+ virQEMUDriverPtr driver = domain->conn->privateData;
+ virQEMUDriverConfigPtr cfg = NULL;
+ virDomainObjPtr vm = NULL;
+ int ret = -1;
+ virDomainBackupDefPtr backup = NULL;
+ qemuDomainObjPrivatePtr priv;
+ bool want_abort = flags & VIR_DOMAIN_BACKUP_END_ABORT;
+
+ virCheckFlags(VIR_DOMAIN_BACKUP_END_ABORT, -1);
+
+ if (!(vm = qemuDomObjFromDomain(domain)))
+ return -1;
+
+ cfg = virQEMUDriverGetConfig(driver);
+ if (virDomainBackupEndEnsureACL(domain->conn, vm->def) < 0)
+ goto cleanup;
+
+ /* TODO: Allow more than one hard-coded job id */
+ priv = vm->privateData;
+ if (id != 1 || !priv->backup) {
+ virReportError(VIR_ERR_NO_DOMAIN_CHECKPOINT,
+ _("no domain backup job with id '%d'"), id);
+ goto cleanup;
+ }
+
+ if (priv->backup->type != VIR_DOMAIN_BACKUP_TYPE_PUSH)
+ want_abort = false;
+
+ /* TODO: QMP commands to actually cancel the pending job, and on
+ * pull, also tear down the NBD server */
+ VIR_STEAL_PTR(backup, priv->backup);
+ if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm,
+ driver->caps) < 0)
+ VIR_WARN("Unable to save status on vm %s after backup job",
+ vm->def->name);
+
+ ret = want_abort ? 0 : 1;
+
+ cleanup:
+ virDomainBackupDefFree(backup);
+ virDomainObjEndAPI(&vm);
+ return ret;
+}
+
static int qemuDomainQemuMonitorCommand(virDomainPtr domain, const char *cmd,
char **result, unsigned int flags)
{
@@ -23486,6 +23668,9 @@ static virHypervisorDriver qemuHypervisorDriver = {
.domainCheckpointIsCurrent = qemuDomainCheckpointIsCurrent, /* 5.2.0 */
.domainCheckpointHasMetadata = qemuDomainCheckpointHasMetadata, /* 5.2.0 */
.domainCheckpointDelete = qemuDomainCheckpointDelete, /* 5.2.0 */
+ .domainBackupBegin = qemuDomainBackupBegin, /* 5.2.0 */
+ .domainBackupGetXMLDesc = qemuDomainBackupGetXMLDesc, /* 5.2.0 */
+ .domainBackupEnd = qemuDomainBackupEnd, /* 5.2.0 */
};
--
2.20.1