From a1521f84a532a4d3e9b23a648f73258f5e48cd6c Mon Sep 17 00:00:00 2001 From: Peter Krempa Date: Wed, 18 Sep 2019 13:11:12 +0200 Subject: [PATCH] qemu: Implement backup job APIs and qemu handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows to start and manage the backup job. Signed-off-by: Peter Krempa Reviewed-by: Ján Tomko --- po/POTFILES.in | 1 + src/qemu/Makefile.inc.am | 2 + src/qemu/qemu_backup.c | 940 +++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_backup.h | 41 ++ src/qemu/qemu_driver.c | 47 ++ 5 files changed, 1031 insertions(+) create mode 100644 src/qemu/qemu_backup.c create mode 100644 src/qemu/qemu_backup.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 48f3f431ec..5afecf21ba 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -140,6 +140,7 @@ @SRCDIR@/src/phyp/phyp_driver.c @SRCDIR@/src/qemu/qemu_agent.c @SRCDIR@/src/qemu/qemu_alias.c +@SRCDIR@/src/qemu/qemu_backup.c @SRCDIR@/src/qemu/qemu_block.c @SRCDIR@/src/qemu/qemu_blockjob.c @SRCDIR@/src/qemu/qemu_capabilities.c diff --git a/src/qemu/Makefile.inc.am b/src/qemu/Makefile.inc.am index bf30f8a3c5..839b1cacb8 100644 --- a/src/qemu/Makefile.inc.am +++ b/src/qemu/Makefile.inc.am @@ -69,6 +69,8 @@ QEMU_DRIVER_SOURCES = \ qemu/qemu_vhost_user_gpu.h \ qemu/qemu_checkpoint.c \ qemu/qemu_checkpoint.h \ + qemu/qemu_backup.c \ + qemu/qemu_backup.h \ $(NULL) diff --git a/src/qemu/qemu_backup.c b/src/qemu/qemu_backup.c new file mode 100644 index 0000000000..72cd9a459a --- /dev/null +++ b/src/qemu/qemu_backup.c @@ -0,0 +1,940 @@ +/* + * qemu_backup.c: Implementation and handling of the backup jobs + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#include + +#include "qemu_block.h" +#include "qemu_conf.h" +#include "qemu_capabilities.h" +#include "qemu_monitor.h" +#include "qemu_process.h" +#include "qemu_backup.h" +#include "qemu_monitor_json.h" +#include "qemu_checkpoint.h" +#include "qemu_command.h" + +#include "virerror.h" +#include "virlog.h" +#include "virbuffer.h" +#include "viralloc.h" +#include "virxml.h" +#include "virstoragefile.h" +#include "virstring.h" +#include "backup_conf.h" +#include "virdomaincheckpointobjlist.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +VIR_LOG_INIT("qemu.qemu_backup"); + + +static virDomainBackupDefPtr +qemuDomainGetBackup(virDomainObjPtr vm) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (!priv->backup) { + virReportError(VIR_ERR_NO_DOMAIN_BACKUP, "%s", + _("no domain backup job present")); + return NULL; + } + + return priv->backup; +} + + +static int +qemuBackupPrepare(virDomainBackupDefPtr def) +{ + + if (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL) { + if (!def->server) { + def->server = g_new(virStorageNetHostDef, 1); + + def->server->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + def->server->name = g_strdup("localhost"); + } + + switch ((virStorageNetHostTransport) def->server->transport) { + case VIR_STORAGE_NET_HOST_TRANS_TCP: + /* TODO: Update qemu.conf to provide a port range, + * probably starting at 10809, for obtaining automatic + * port via virPortAllocatorAcquire, as well as store + * somewhere if we need to call virPortAllocatorRelease + * during BackupEnd. Until then, user must provide port */ + if (!def->server->port) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _(" must specify TCP port for now")); + return -1; + } + break; + + case VIR_STORAGE_NET_HOST_TRANS_UNIX: + /* TODO: Do we need to mess with selinux? */ + break; + + case VIR_STORAGE_NET_HOST_TRANS_RDMA: + case VIR_STORAGE_NET_HOST_TRANS_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unexpected transport in ")); + return -1; + } + } + + return 0; +} + + +struct qemuBackupDiskData { + virDomainBackupDiskDefPtr backupdisk; + virDomainDiskDefPtr domdisk; + qemuBlockJobDataPtr blockjob; + virStorageSourcePtr store; + char *incrementalBitmap; + qemuBlockStorageSourceChainDataPtr crdata; + bool labelled; + bool initialized; + bool created; + bool added; + bool started; + bool done; +}; + + +static void +qemuBackupDiskDataCleanupOne(virDomainObjPtr vm, + struct qemuBackupDiskData *dd) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + if (dd->started) + return; + + if (dd->added) { + qemuDomainObjEnterMonitor(priv->driver, vm); + qemuBlockStorageSourceAttachRollback(priv->mon, dd->crdata->srcdata[0]); + ignore_value(qemuDomainObjExitMonitor(priv->driver, vm)); + } + + if (dd->created) { + if (virStorageFileUnlink(dd->store) < 0) + VIR_WARN("Unable to remove just-created %s", NULLSTR(dd->store->path)); + } + + if (dd->initialized) + virStorageFileDeinit(dd->store); + + if (dd->labelled) + qemuDomainStorageSourceAccessRevoke(priv->driver, vm, dd->store); + + if (dd->blockjob) + qemuBlockJobStartupFinalize(vm, dd->blockjob); + + qemuBlockStorageSourceChainDataFree(dd->crdata); +} + + +static void +qemuBackupDiskDataCleanup(virDomainObjPtr vm, + struct qemuBackupDiskData *dd, + size_t ndd) +{ + virErrorPtr orig_err; + size_t i; + + if (!dd) + return; + + virErrorPreserveLast(&orig_err); + + for (i = 0; i < ndd; i++) + qemuBackupDiskDataCleanupOne(vm, dd + i); + + g_free(dd); + virErrorRestore(&orig_err); +} + + + +static int +qemuBackupDiskPrepareOneBitmaps(struct qemuBackupDiskData *dd, + virJSONValuePtr actions, + virDomainMomentObjPtr *incremental) +{ + g_autoptr(virJSONValue) mergebitmaps = NULL; + g_autoptr(virJSONValue) mergebitmapsstore = NULL; + + if (!(mergebitmaps = virJSONValueNewArray())) + return -1; + + /* TODO: this code works only if the bitmaps are present on a single node. + * The algorithm needs to be changed so that it looks into the backing chain + * so that we can combine all relevant bitmaps for a given backing chain */ + while (*incremental) { + if (qemuMonitorTransactionBitmapMergeSourceAddBitmap(mergebitmaps, + dd->domdisk->src->nodeformat, + (*incremental)->def->name) < 0) + return -1; + + incremental++; + } + + if (!(mergebitmapsstore = virJSONValueCopy(mergebitmaps))) + return -1; + + if (qemuMonitorTransactionBitmapAdd(actions, + dd->domdisk->src->nodeformat, + dd->incrementalBitmap, + false, + true) < 0) + return -1; + + if (qemuMonitorTransactionBitmapMerge(actions, + dd->domdisk->src->nodeformat, + dd->incrementalBitmap, + &mergebitmaps) < 0) + return -1; + + if (qemuMonitorTransactionBitmapAdd(actions, + dd->store->nodeformat, + dd->incrementalBitmap, + false, + true) < 0) + return -1; + + if (qemuMonitorTransactionBitmapMerge(actions, + dd->store->nodeformat, + dd->incrementalBitmap, + &mergebitmapsstore) < 0) + return -1; + + return 0; +} + + +static int +qemuBackupDiskPrepareDataOne(virDomainObjPtr vm, + virDomainBackupDiskDefPtr backupdisk, + struct qemuBackupDiskData *dd, + virJSONValuePtr actions, + virDomainMomentObjPtr *incremental, + virQEMUDriverConfigPtr cfg, + bool removeStore) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + g_autoptr(virStorageSource) terminator = NULL; + + /* set data structure */ + dd->backupdisk = backupdisk; + dd->store = dd->backupdisk->store; + + if (!(dd->domdisk = virDomainDiskByTarget(vm->def, dd->backupdisk->name))) { + virReportError(VIR_ERR_INVALID_ARG, + _("no disk named '%s'"), dd->backupdisk->name); + return -1; + } + + if (!dd->store->format) + dd->store->format = VIR_STORAGE_FILE_QCOW2; + + if (qemuDomainStorageFileInit(priv->driver, vm, dd->store, dd->domdisk->src) < 0) + return -1; + + if (qemuDomainPrepareStorageSourceBlockdev(NULL, dd->store, priv, cfg) < 0) + return -1; + + if (incremental) { + dd->incrementalBitmap = g_strdup_printf("backup-%s", dd->domdisk->dst); + + if (qemuBackupDiskPrepareOneBitmaps(dd, actions, incremental) < 0) + return -1; + } + + /* install terminator to prevent qemu form opening backing images */ + if (!(terminator = virStorageSourceNew())) + return -1; + + if (!(dd->blockjob = qemuBlockJobDiskNewBackup(vm, dd->domdisk, dd->store, + removeStore, + dd->incrementalBitmap))) + return -1; + + if (!(dd->crdata = qemuBuildStorageSourceChainAttachPrepareBlockdevTop(dd->store, + terminator, + priv->qemuCaps))) + return -1; + + return 0; +} + + +static int +qemuBackupDiskPrepareDataOnePush(virJSONValuePtr actions, + struct qemuBackupDiskData *dd) +{ + qemuMonitorTransactionBackupSyncMode syncmode = QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_FULL; + + if (dd->incrementalBitmap) + syncmode = QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_INCREMENTAL; + + if (qemuMonitorTransactionBackup(actions, + dd->domdisk->src->nodeformat, + dd->blockjob->name, + dd->store->nodeformat, + dd->incrementalBitmap, + syncmode) < 0) + return -1; + + return 0; +} + + +static int +qemuBackupDiskPrepareDataOnePull(virJSONValuePtr actions, + struct qemuBackupDiskData *dd) +{ + if (qemuMonitorTransactionBackup(actions, + dd->domdisk->src->nodeformat, + dd->blockjob->name, + dd->store->nodeformat, + NULL, + QEMU_MONITOR_TRANSACTION_BACKUP_SYNC_MODE_NONE) < 0) + return -1; + + return 0; +} + + +static ssize_t +qemuBackupDiskPrepareData(virDomainObjPtr vm, + virDomainBackupDefPtr def, + virDomainMomentObjPtr *incremental, + virJSONValuePtr actions, + virQEMUDriverConfigPtr cfg, + struct qemuBackupDiskData **rdd, + bool reuse_external) +{ + struct qemuBackupDiskData *disks = NULL; + ssize_t ndisks = 0; + size_t i; + bool removeStore = !reuse_external && (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL); + + disks = g_new0(struct qemuBackupDiskData, def->ndisks); + + for (i = 0; i < def->ndisks; i++) { + virDomainBackupDiskDef *backupdisk = &def->disks[i]; + struct qemuBackupDiskData *dd = disks + ndisks; + + if (!backupdisk->store) + continue; + + ndisks++; + + if (qemuBackupDiskPrepareDataOne(vm, backupdisk, dd, actions, + incremental, cfg, removeStore) < 0) + goto error; + + if (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL) { + if (qemuBackupDiskPrepareDataOnePull(actions, dd) < 0) + goto error; + } else { + if (qemuBackupDiskPrepareDataOnePush(actions, dd) < 0) + goto error; + } + } + + *rdd = g_steal_pointer(&disks); + + return ndisks; + + error: + qemuBackupDiskDataCleanup(vm, disks, ndisks); + return -1; +} + + +static int +qemuBackupDiskPrepareOneStorage(virDomainObjPtr vm, + virHashTablePtr blockNamedNodeData, + struct qemuBackupDiskData *dd, + bool reuse_external) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + int rc; + + if (!reuse_external && + dd->store->type == VIR_STORAGE_TYPE_FILE && + virStorageFileSupportsCreate(dd->store)) { + + if (virFileExists(dd->store->path)) { + virReportError(VIR_ERR_INVALID_ARG, + _("store '%s' for backup of '%s' exists"), + dd->store->path, dd->domdisk->dst); + return -1; + } + + if (qemuDomainStorageFileInit(priv->driver, vm, dd->store, NULL) < 0) + return -1; + + dd->initialized = true; + + if (virStorageFileCreate(dd->store) < 0) { + virReportSystemError(errno, + _("failed to create image file '%s'"), + NULLSTR(dd->store->path)); + return -1; + } + + dd->created = true; + } + + if (qemuDomainStorageSourceAccessAllow(priv->driver, vm, dd->store, false, + true) < 0) + return -1; + + dd->labelled = true; + + if (!reuse_external) { + if (qemuBlockStorageSourceCreateDetectSize(blockNamedNodeData, + dd->store, dd->domdisk->src) < 0) + return -1; + + if (qemuBlockStorageSourceCreate(vm, dd->store, NULL, NULL, + dd->crdata->srcdata[0], + QEMU_ASYNC_JOB_BACKUP) < 0) + return -1; + } else { + if (qemuDomainObjEnterMonitorAsync(priv->driver, vm, QEMU_ASYNC_JOB_BACKUP) < 0) + return -1; + + rc = qemuBlockStorageSourceAttachApply(priv->mon, dd->crdata->srcdata[0]); + + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0 || rc < 0) + return -1; + } + + dd->added = true; + + return 0; +} + + +static int +qemuBackupDiskPrepareStorage(virDomainObjPtr vm, + struct qemuBackupDiskData *disks, + size_t ndisks, + virHashTablePtr blockNamedNodeData, + bool reuse_external) +{ + size_t i; + + for (i = 0; i < ndisks; i++) { + if (qemuBackupDiskPrepareOneStorage(vm, blockNamedNodeData, disks + i, + reuse_external) < 0) + return -1; + } + + return 0; +} + + +static void +qemuBackupDiskStarted(virDomainObjPtr vm, + struct qemuBackupDiskData *dd, + size_t ndd) +{ + size_t i; + + for (i = 0; i < ndd; i++) { + dd[i].started = true; + dd[i].backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING; + qemuBlockJobStarted(dd->blockjob, vm); + } +} + + +/** + * qemuBackupBeginPullExportDisks: + * @vm: domain object + * @disks: backup disk data list + * @ndisks: number of valid disks in @disks + * + * Exports all disks from @dd when doing a pull backup in the NBD server. This + * function must be called while in the monitor context. + */ +static int +qemuBackupBeginPullExportDisks(virDomainObjPtr vm, + struct qemuBackupDiskData *disks, + size_t ndisks) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + size_t i; + + for (i = 0; i < ndisks; i++) { + struct qemuBackupDiskData *dd = disks + i; + + if (qemuMonitorNBDServerAdd(priv->mon, + dd->store->nodeformat, + dd->domdisk->dst, + false, + dd->incrementalBitmap) < 0) + return -1; + } + + return 0; +} + + +/** + * qemuBackupBeginCollectIncrementalCheckpoints: + * @vm: domain object + * @incrFrom: name of checkpoint representing starting point of incremental backup + * + * Returns a NULL terminated list of pointers to checkpoints in chronological + * order starting from the 'current' checkpoint until reaching @incrFrom. + */ +static virDomainMomentObjPtr * +qemuBackupBeginCollectIncrementalCheckpoints(virDomainObjPtr vm, + const char *incrFrom) +{ + virDomainMomentObjPtr n = virDomainCheckpointGetCurrent(vm->checkpoints); + g_autofree virDomainMomentObjPtr *incr = NULL; + size_t nincr = 0; + + while (n) { + if (VIR_APPEND_ELEMENT_COPY(incr, nincr, n) < 0) + return NULL; + + if (STREQ(n->def->name, incrFrom)) { + virDomainMomentObjPtr terminator = NULL; + if (VIR_APPEND_ELEMENT_COPY(incr, nincr, terminator) < 0) + return NULL; + + return g_steal_pointer(&incr); + } + + if (!n->def->parent_name) + break; + + n = virDomainCheckpointFindByName(vm->checkpoints, n->def->parent_name); + } + + virReportError(VIR_ERR_OPERATION_INVALID, + _("could not locate checkpoint '%s' for incremental backup"), + incrFrom); + return NULL; +} + + +static void +qemuBackupJobTerminate(virDomainObjPtr vm, + qemuDomainJobStatus jobstatus) + +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + + qemuDomainJobInfoUpdateTime(priv->job.current); + + g_free(priv->job.completed); + priv->job.completed = g_new0(qemuDomainJobInfo, 1); + *priv->job.completed = *priv->job.current; + + priv->job.completed->stats.backup.total = priv->backup->push_total; + priv->job.completed->stats.backup.transferred = priv->backup->push_transferred; + priv->job.completed->stats.backup.tmp_used = priv->backup->pull_tmp_used; + priv->job.completed->stats.backup.tmp_total = priv->backup->pull_tmp_total; + + priv->job.completed->status = jobstatus; + + qemuDomainEventEmitJobCompleted(priv->driver, vm); + + virDomainBackupDefFree(priv->backup); + priv->backup = NULL; + qemuDomainObjEndAsyncJob(priv->driver, vm); +} + + +/** + * qemuBackupJobCancelBlockjobs: + * @vm: domain object + * @backup: backup definition + * @terminatebackup: flag whether to terminate and unregister the backup + * + * Sends all active blockjobs which are part of @backup of @vm a signal to + * cancel. If @terminatebackup is true qemuBackupJobTerminate is also called + * if there are no outstanding active blockjobs. + */ +void +qemuBackupJobCancelBlockjobs(virDomainObjPtr vm, + virDomainBackupDefPtr backup, + bool terminatebackup) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + size_t i; + int rc = 0; + bool has_active = false; + + if (!backup) + return; + + for (i = 0; i < backup->ndisks; i++) { + virDomainBackupDiskDefPtr backupdisk = backup->disks + i; + virDomainDiskDefPtr disk; + g_autoptr(qemuBlockJobData) job = NULL; + + if (!backupdisk->store) + continue; + + /* Look up corresponding disk as backupdisk->idx is no longer reliable */ + if (!(disk = virDomainDiskByTarget(vm->def, backupdisk->name))) + continue; + + if (!(job = qemuBlockJobDiskGetJob(disk))) + continue; + + if (backupdisk->state != VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING && + backupdisk->state != VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLING) + continue; + + has_active = true; + + if (backupdisk->state != VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING) + continue; + + qemuDomainObjEnterMonitor(priv->driver, vm); + + rc = qemuMonitorJobCancel(priv->mon, job->name, false); + + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0) + return; + + if (rc == 0) { + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLING; + job->state = QEMU_BLOCKJOB_STATE_ABORTING; + } + } + + if (terminatebackup && !has_active) + qemuBackupJobTerminate(vm, QEMU_DOMAIN_JOB_STATUS_CANCELED); +} + + +int +qemuBackupBegin(virDomainObjPtr vm, + const char *backupXML, + const char *checkpointXML, + unsigned int flags) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(priv->driver); + g_autoptr(virDomainBackupDef) def = NULL; + g_autofree char *suffix = NULL; + struct timeval tv; + bool pull = false; + virDomainMomentObjPtr chk = NULL; + g_autoptr(virDomainCheckpointDef) chkdef = NULL; + g_autofree virDomainMomentObjPtr *incremental = NULL; + g_autoptr(virJSONValue) actions = NULL; + struct qemuBackupDiskData *dd = NULL; + ssize_t ndd = 0; + g_autoptr(virHashTable) blockNamedNodeData = NULL; + bool job_started = false; + bool nbd_running = false; + bool reuse = (flags & VIR_DOMAIN_BACKUP_BEGIN_REUSE_EXTERNAL); + int rc = 0; + int ret = -1; + + virCheckFlags(VIR_DOMAIN_BACKUP_BEGIN_REUSE_EXTERNAL, -1); + + if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_INCREMENTAL_BACKUP)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("incremental backup is not supported yet")); + return -1; + } + + if (!(def = virDomainBackupDefParseString(backupXML, priv->driver->xmlopt, 0))) + return -1; + + if (checkpointXML) { + if (!(chkdef = virDomainCheckpointDefParseString(checkpointXML, + priv->driver->xmlopt, + priv->qemuCaps, 0))) + return -1; + + suffix = g_strdup(chkdef->parent.name); + } else { + gettimeofday(&tv, NULL); + suffix = g_strdup_printf("%lld", (long long)tv.tv_sec); + } + + if (def->type == VIR_DOMAIN_BACKUP_TYPE_PULL) + pull = true; + + /* we'll treat this kind of backup job as an asyncjob as it uses some of the + * infrastructure for async jobs. We'll allow standard modify-type jobs + * as the interlocking of conflicting operations is handled on the block + * job level */ + if (qemuDomainObjBeginAsyncJob(priv->driver, vm, QEMU_ASYNC_JOB_BACKUP, + VIR_DOMAIN_JOB_OPERATION_BACKUP, flags) < 0) + return -1; + + qemuDomainObjSetAsyncJobMask(vm, (QEMU_JOB_DEFAULT_MASK | + JOB_MASK(QEMU_JOB_SUSPEND) | + JOB_MASK(QEMU_JOB_MODIFY))); + priv->job.current->statsType = QEMU_DOMAIN_JOB_STATS_TYPE_BACKUP; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", + _("cannot perform disk backup for inactive domain")); + goto endjob; + } + + if (priv->backup) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("another backup job is already running")); + goto endjob; + } + + if (qemuBackupPrepare(def) < 0) + goto endjob; + + if (virDomainBackupAlignDisks(def, vm->def, suffix) < 0) + goto endjob; + + if (def->incremental && + !(incremental = qemuBackupBeginCollectIncrementalCheckpoints(vm, def->incremental))) + goto endjob; + + if (!(actions = virJSONValueNewArray())) + goto endjob; + + if (chkdef) { + if (qemuCheckpointCreateCommon(priv->driver, vm, &chkdef, + &actions, &chk) < 0) + goto endjob; + } + + if ((ndd = qemuBackupDiskPrepareData(vm, def, incremental, actions, cfg, &dd, + reuse)) <= 0) { + if (ndd == 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("no disks selected for backup")); + } + + goto endjob; + } + + if (qemuDomainObjEnterMonitorAsync(priv->driver, vm, QEMU_ASYNC_JOB_BACKUP) < 0) + goto endjob; + blockNamedNodeData = qemuMonitorBlockGetNamedNodeData(priv->mon); + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0 || !blockNamedNodeData) + goto endjob; + + if (qemuBackupDiskPrepareStorage(vm, dd, ndd, blockNamedNodeData, reuse) < 0) + goto endjob; + + priv->backup = g_steal_pointer(&def); + + if (qemuDomainObjEnterMonitorAsync(priv->driver, vm, QEMU_ASYNC_JOB_BACKUP) < 0) + goto endjob; + + /* TODO: TLS is a must-have for the modern age */ + if (pull) { + if ((rc = qemuMonitorNBDServerStart(priv->mon, priv->backup->server, NULL)) == 0) + nbd_running = true; + } + + if (rc == 0) + rc = qemuMonitorTransaction(priv->mon, &actions); + + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0 || rc < 0) + goto endjob; + + job_started = true; + qemuBackupDiskStarted(vm, dd, ndd); + + if (chk && + qemuCheckpointCreateFinalize(priv->driver, vm, cfg, chk, true) < 0) + goto endjob; + + if (pull) { + if (qemuDomainObjEnterMonitorAsync(priv->driver, vm, QEMU_ASYNC_JOB_BACKUP) < 0) + goto endjob; + /* note that if the export fails we've already created the checkpoint + * and we will not delete it */ + rc = qemuBackupBeginPullExportDisks(vm, dd, ndd); + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0) + goto endjob; + + if (rc < 0) { + qemuBackupJobCancelBlockjobs(vm, priv->backup, false); + goto endjob; + } + } + + ret = 0; + + endjob: + qemuBackupDiskDataCleanup(vm, dd, ndd); + if (!job_started && nbd_running && + qemuDomainObjEnterMonitorAsync(priv->driver, vm, QEMU_ASYNC_JOB_BACKUP) < 0) { + ignore_value(qemuMonitorNBDServerStop(priv->mon)); + ignore_value(qemuDomainObjExitMonitor(priv->driver, vm)); + } + + if (ret < 0 && !job_started) + def = g_steal_pointer(&priv->backup); + + if (ret == 0) + qemuDomainObjReleaseAsyncJob(vm); + else + qemuDomainObjEndAsyncJob(priv->driver, vm); + + return ret; +} + + +char * +qemuBackupGetXMLDesc(virDomainObjPtr vm, + unsigned int flags) +{ + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + virDomainBackupDefPtr backup; + + virCheckFlags(0, NULL); + + if (!(backup = qemuDomainGetBackup(vm))) + return NULL; + + if (virDomainBackupDefFormat(&buf, backup, false) < 0) + return NULL; + + return virBufferContentAndReset(&buf); +} + + +void +qemuBackupNotifyBlockjobEnd(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + qemuBlockjobState state, + unsigned long long cur, + unsigned long long end) +{ + qemuDomainObjPrivatePtr priv = vm->privateData; + bool has_running = false; + bool has_cancelling = false; + bool has_cancelled = false; + bool has_failed = false; + qemuDomainJobStatus jobstatus = QEMU_DOMAIN_JOB_STATUS_COMPLETED; + virDomainBackupDefPtr backup = priv->backup; + size_t i; + + VIR_DEBUG("vm: '%s', disk:'%s', state:'%d'", + vm->def->name, disk->dst, state); + + if (!backup) + return; + + if (backup->type == VIR_DOMAIN_BACKUP_TYPE_PULL) { + qemuDomainObjEnterMonitor(priv->driver, vm); + ignore_value(qemuMonitorNBDServerStop(priv->mon)); + if (qemuDomainObjExitMonitor(priv->driver, vm) < 0) + return; + + /* update the final statistics with the current job's data */ + backup->pull_tmp_used += cur; + backup->pull_tmp_total += end; + } else { + backup->push_transferred += cur; + backup->push_total += end; + } + + for (i = 0; i < backup->ndisks; i++) { + virDomainBackupDiskDefPtr backupdisk = backup->disks + i; + + if (!backupdisk->store) + continue; + + if (STREQ(disk->dst, backupdisk->name)) { + switch (state) { + case QEMU_BLOCKJOB_STATE_COMPLETED: + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_COMPLETE; + break; + + case QEMU_BLOCKJOB_STATE_CONCLUDED: + case QEMU_BLOCKJOB_STATE_FAILED: + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_FAILED; + break; + + case QEMU_BLOCKJOB_STATE_CANCELLED: + backupdisk->state = VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLED; + break; + + case QEMU_BLOCKJOB_STATE_READY: + case QEMU_BLOCKJOB_STATE_NEW: + case QEMU_BLOCKJOB_STATE_RUNNING: + case QEMU_BLOCKJOB_STATE_ABORTING: + case QEMU_BLOCKJOB_STATE_PIVOTING: + case QEMU_BLOCKJOB_STATE_LAST: + default: + break; + } + } + + switch (backupdisk->state) { + case VIR_DOMAIN_BACKUP_DISK_STATE_COMPLETE: + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_RUNNING: + has_running = true; + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLING: + has_cancelling = true; + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_FAILED: + has_failed = true; + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_CANCELLED: + has_cancelled = true; + break; + + case VIR_DOMAIN_BACKUP_DISK_STATE_NONE: + case VIR_DOMAIN_BACKUP_DISK_STATE_LAST: + break; + } + } + + if (has_running && (has_failed || has_cancelled)) { + /* cancel the rest of the jobs */ + qemuBackupJobCancelBlockjobs(vm, backup, false); + } else if (!has_running && !has_cancelling) { + /* all sub-jobs have stopped */ + + if (has_failed) + jobstatus = QEMU_DOMAIN_JOB_STATUS_FAILED; + else if (has_cancelled && backup->type == VIR_DOMAIN_BACKUP_TYPE_PUSH) + jobstatus = QEMU_DOMAIN_JOB_STATUS_CANCELED; + + qemuBackupJobTerminate(vm, jobstatus); + } + + /* otherwise we must wait for the jobs to end */ +} diff --git a/src/qemu/qemu_backup.h b/src/qemu/qemu_backup.h new file mode 100644 index 0000000000..96297fc9e4 --- /dev/null +++ b/src/qemu/qemu_backup.h @@ -0,0 +1,41 @@ +/* + * qemu_backup.h: Implementation and handling of the backup jobs + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + */ + +#pragma once + +int +qemuBackupBegin(virDomainObjPtr vm, + const char *backupXML, + const char *checkpointXML, + unsigned int flags); + +char * +qemuBackupGetXMLDesc(virDomainObjPtr vm, + unsigned int flags); + +void +qemuBackupJobCancelBlockjobs(virDomainObjPtr vm, + virDomainBackupDefPtr backup, + bool terminatebackup); + +void +qemuBackupNotifyBlockjobEnd(virDomainObjPtr vm, + virDomainDiskDefPtr disk, + qemuBlockjobState state, + unsigned long long cur, + unsigned long long end); diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 15eb581fb3..f2967c787a 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -52,6 +52,7 @@ #include "qemu_blockjob.h" #include "qemu_security.h" #include "qemu_checkpoint.h" +#include "qemu_backup.h" #include "virerror.h" #include "virlog.h" @@ -17197,6 +17198,50 @@ qemuDomainCheckpointDelete(virDomainCheckpointPtr checkpoint, } +static int +qemuDomainBackupBegin(virDomainPtr domain, + const char *backupXML, + const char *checkpointXML, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + int ret = -1; + + if (!(vm = qemuDomainObjFromDomain(domain))) + goto cleanup; + + if (virDomainBackupBeginEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + ret = qemuBackupBegin(vm, backupXML, checkpointXML, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + +static char * +qemuDomainBackupGetXMLDesc(virDomainPtr domain, + unsigned int flags) +{ + virDomainObjPtr vm = NULL; + char *ret = NULL; + + if (!(vm = qemuDomainObjFromDomain(domain))) + return NULL; + + if (virDomainBackupGetXMLDescEnsureACL(domain->conn, vm->def) < 0) + goto cleanup; + + ret = qemuBackupGetXMLDesc(vm, flags); + + cleanup: + virDomainObjEndAPI(&vm); + return ret; +} + + static int qemuDomainQemuMonitorCommand(virDomainPtr domain, const char *cmd, char **result, unsigned int flags) { @@ -22932,6 +22977,8 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainCheckpointDelete = qemuDomainCheckpointDelete, /* 5.6.0 */ .domainGetGuestInfo = qemuDomainGetGuestInfo, /* 5.7.0 */ .domainAgentSetResponseTimeout = qemuDomainAgentSetResponseTimeout, /* 5.10.0 */ + .domainBackupBegin = qemuDomainBackupBegin, /* 6.0.0 */ + .domainBackupGetXMLDesc = qemuDomainBackupGetXMLDesc, /* 6.0.0 */ }; -- GitLab