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 <ebl...@redhat.com> --- src/qemu/qemu_domain.h | 4 + src/qemu/qemu_domain.c | 30 +++++-- src/qemu/qemu_driver.c | 185 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index f62fcdb985..374d1e96ef 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -370,6 +370,10 @@ struct _qemuDomainObjPrivate { /* qemuProcessStartCPUs stores the reason for starting vCPUs here for the * RESUME event handler to use it */ virDomainRunningReason runningReason; + + /* 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 15722956bc..03550e2046 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -2230,14 +2230,23 @@ static int qemuDomainObjPrivateXMLFormatBlockjobs(virBufferPtr buf, virDomainObjPtr vm) { + qemuDomainObjPrivatePtr priv = vm->privateData; virBuffer attrBuf = VIR_BUFFER_INITIALIZER; bool bj = qemuDomainHasBlockjob(vm, false); - int ret; + int ret = -1; virBufferAsprintf(&attrBuf, " active='%s'", virTristateBoolTypeToString(virTristateBoolFromBool(bj))); - ret = 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; } @@ -2590,18 +2599,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; } @@ -2978,7 +2998,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 236cbeb683..bc89fb6fa0 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -17448,6 +17448,188 @@ 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 use 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) < 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, x-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> + - pull+incr: nbd-server-add-bitmap per <disk> + */ + + 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) { @@ -22938,6 +23120,9 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainCheckpointIsCurrent = qemuDomainCheckpointIsCurrent, /* 4.9.0 */ .domainCheckpointHasMetadata = qemuDomainCheckpointHasMetadata, /* 4.9.0 */ .domainCheckpointDelete = qemuDomainCheckpointDelete, /* 4.9.0 */ + .domainBackupBegin = qemuDomainBackupBegin, /* 4.9.0 */ + .domainBackupGetXMLDesc = qemuDomainBackupGetXMLDesc, /* 4.9.0 */ + .domainBackupEnd = qemuDomainBackupEnd, /* 4.9.0 */ }; -- 2.17.2 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list