qemu drive support block backup, it use qmp command "drive-backup"
to start the block, and use qmp command "block-dirty-bitmap-add"/
"block-dirty-bitmap-clear"/"block-dirty-bitmap-remove" to manage
the bitmap. Bitmap is used to incremental backup.
Signed-off-by: longyou <longyou(a)mogujie.com>
---
src/conf/domain_conf.h | 12 +++
src/qemu/qemu_blockjob.c | 2 +
src/qemu/qemu_capabilities.c | 4 +
src/qemu/qemu_capabilities.h | 5 ++
src/qemu/qemu_driver.c | 197 +++++++++++++++++++++++++++++++++++++++++++
src/qemu/qemu_monitor.c | 59 +++++++++++++
src/qemu/qemu_monitor.h | 23 +++++
src/qemu/qemu_monitor_json.c | 124 +++++++++++++++++++++++++++
src/qemu/qemu_monitor_json.h | 23 +++++
9 files changed, 449 insertions(+)
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index c182747..44cbefa 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -556,6 +556,15 @@ typedef enum {
} virDomainDiskMirrorState;
+typedef enum {
+ VIR_DOMAIN_DISK_BACKUP_STATE_NONE = 0, /* No job, or job not finished */
+ VIR_DOMAIN_DISK_BACKUP_STATE_FINISH, /* Job already finish */
+ VIR_DOMAIN_DISK_BACKUP_STATE_ABORT, /* Cause error, job aborted */
+
+ VIR_DOMAIN_DISK_BACKUP_STATE_LAST
+} virDomainDiskBackupState;
+
+
/* Stores the virtual disk configuration */
struct _virDomainDiskDef {
virStorageSourcePtr src; /* non-NULL. XXX Allow NULL for empty cdrom? */
@@ -606,6 +615,9 @@ struct _virDomainDiskDef {
int discard; /* enum virDomainDiskDiscard */
unsigned int iothread; /* unused = 0, > 0 specific thread # */
char *domain_name; /* backend domain name */
+
+ bool full_backup; /* done full backup or not */
+ int backup_state; /* enum virDomainDiskBackupState */
};
diff --git a/src/qemu/qemu_blockjob.c b/src/qemu/qemu_blockjob.c
index 83a5a3f..de8c0e4 100644
--- a/src/qemu/qemu_blockjob.c
+++ b/src/qemu/qemu_blockjob.c
@@ -162,6 +162,8 @@ qemuBlockJobEventProcess(virQEMUDriverPtr driver,
disk->mirrorJob = VIR_DOMAIN_BLOCK_JOB_TYPE_UNKNOWN;
ignore_value(qemuDomainDetermineDiskChain(driver, vm, disk,
true, true));
+
+ disk->backup_state = VIR_DOMAIN_DISK_BACKUP_STATE_FINISH;
diskPriv->blockjob = false;
break;
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
index d32e71f..52344f6 100644
--- a/src/qemu/qemu_capabilities.c
+++ b/src/qemu/qemu_capabilities.c
@@ -329,6 +329,9 @@ VIR_ENUM_IMPL(virQEMUCaps, QEMU_CAPS_LAST,
"nec-usb-xhci-ports",
"virtio-scsi-pci.iothread",
"name-guest",
+
+ "drive-backup", /* 225 */
+ "drive-backup-incremental",
);
@@ -1452,6 +1455,7 @@ struct virQEMUCapsStringFlags virQEMUCapsCommands[] = {
{ "block-commit", QEMU_CAPS_BLOCK_COMMIT },
{ "query-vnc", QEMU_CAPS_VNC },
{ "drive-mirror", QEMU_CAPS_DRIVE_MIRROR },
+ { "drive-backup", QEMU_CAPS_DRIVE_BACKUP },
{ "blockdev-snapshot-sync", QEMU_CAPS_DISK_SNAPSHOT },
{ "add-fd", QEMU_CAPS_ADD_FD },
{ "nbd-server-start", QEMU_CAPS_NBD_SERVER },
diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
index 368996a..cd1d2ae 100644
--- a/src/qemu/qemu_capabilities.h
+++ b/src/qemu/qemu_capabilities.h
@@ -361,6 +361,11 @@ typedef enum {
QEMU_CAPS_VIRTIO_SCSI_IOTHREAD, /* virtio-scsi-{pci,ccw}.iothread */
QEMU_CAPS_NAME_GUEST, /* -name guest= */
+ /* 225 */
+ QEMU_CAPS_DRIVE_BACKUP, /* drive-backup monitor command */
+ QEMU_CAPS_DRIVE_BACKUP_INCREMENTAL, /* drive-backup works
+ with "incremental" */
+
QEMU_CAPS_LAST /* this must always be the last item */
} virQEMUCapsFlags;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 10d3e3d..aaa249a 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -16941,6 +16941,202 @@ qemuDomainBlockCommit(virDomainPtr dom,
return ret;
}
+#define QEMU_DRIVE_BACKUP_BITMAP "drive-backup-bitmap"
+
+static int
+qemuDomainDriveBackupReady(virQEMUDriverPtr driver,
+ virDomainObjPtr vm,
+ virDomainDiskDefPtr disk)
+{
+ int status;
+
+ status = qemuBlockJobUpdate(driver, vm, disk);
+ if (status == VIR_DOMAIN_BLOCK_JOB_FAILED) {
+ virReportError(VIR_ERR_OPERATION_FAILED,
+ _("backup of disk %s failed"),
+ disk->dst);
+ return -1;
+ }
+
+ if (disk->backup_state == VIR_DOMAIN_DISK_BACKUP_STATE_FINISH) {
+ VIR_DEBUG("disk backup are finished");
+ return 1;
+ } else {
+ VIR_DEBUG("Waiting for disk backup to get ready");
+ return 0;
+ }
+}
+
+static int
+qemuDomainBlockBackup(virDomainPtr dom,
+ const char *path,
+ const char *dest,
+ unsigned long long bandwidth,
+ const char *format,
+ unsigned int flags)
+{
+ int ret = -1;
+ virQEMUDriverPtr driver = dom->conn->privateData;
+ qemuDomainObjPrivatePtr priv;
+ virDomainObjPtr vm = NULL;
+ char *device = NULL;
+ char *mode = NULL;
+ char *bitmap = NULL;
+ virDomainDiskDefPtr disk = NULL;
+ unsigned long long speed = bandwidth;
+ int sync_begin = 0;
+
+ virCheckFlags(VIR_DOMAIN_BLOCK_BACKUP_FULL |
+ VIR_DOMAIN_BLOCK_BACKUP_TOP |
+ VIR_DOMAIN_BLOCK_BACKUP_INCREMENTAL, -1);
+
+ if (!(vm = qemuDomObjFromDomain(dom)))
+ goto cleanup;
+ priv = vm->privateData;
+
+ if (virDomainBlockBackupEnsureACL(dom->conn, vm->def) < 0)
+ goto cleanup;
+
+ if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
+ goto cleanup;
+
+ if (!virDomainObjIsActive(vm)) {
+ virReportError(VIR_ERR_OPERATION_INVALID,
+ "%s", _("domain is not running"));
+ goto endjob;
+ }
+
+ if (!(virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_DRIVE_BACKUP) &&
+ virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_BLOCKJOB_ASYNC))) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("online backup not supported with this QEMU
binary"));
+ goto endjob;
+ }
+
+ /* Convert bandwidth MiB to bytes, if necessary */
+ if (speed > LLONG_MAX >> 20) {
+ virReportError(VIR_ERR_OVERFLOW,
+ _("bandwidth must be less than %llu"),
+ LLONG_MAX >> 20);
+ goto endjob;
+ }
+ speed <<= 20;
+
+ if (!(disk = qemuDomainDiskByName(vm->def, path)))
+ goto endjob;
+
+ if (!(device = qemuAliasFromDisk(disk)))
+ goto endjob;
+
+ if (qemuDomainDiskBlockJobIsActive(disk))
+ goto endjob;
+
+ if (!flags ||
+ (flags & VIR_DOMAIN_BLOCK_BACKUP_FULL) ||
+ (flags & VIR_DOMAIN_BLOCK_BACKUP_TOP)) {
+ qemuDomainObjEnterMonitor(driver, vm);
+ if (disk->full_backup) {
+ /* clear bitmap */
+ ret = qemuMonitorBlockDirtyBitmapClear(priv->mon, device,
+ QEMU_DRIVE_BACKUP_BITMAP);
+ } else {
+ /* add bitmap */
+ disk->full_backup = true;
+ ret = qemuMonitorBlockDirtyBitmapAdd(priv->mon, device,
+ QEMU_DRIVE_BACKUP_BITMAP, 0);
+ }
+
+ if (qemuDomainObjExitMonitor(driver, vm) < 0) {
+ ret = -1;
+ goto rollback;
+ }
+
+ if (ret < 0) {
+ virReportError(VIR_ERR_OPERATION_INVALID,
+ _("disk %s add or clear dirty bitmap failed"),
+ disk->dst);
+ goto rollback;
+ }
+
+ if (flags & VIR_DOMAIN_BLOCK_BACKUP_TOP) {
+ ignore_value(VIR_STRDUP(mode, "top"));
+ } else {
+ ignore_value(VIR_STRDUP(mode, "full"));
+ }
+ } else if (flags & VIR_DOMAIN_BLOCK_BACKUP_INCREMENTAL) {
+ if (!disk->full_backup) {
+ virReportError(VIR_ERR_OPERATION_INVALID,
+ _("disk %s has no bitmap, must do full backup firstly"),
+ disk->dst);
+ goto endjob;
+ }
+ ignore_value(VIR_STRDUP(mode, "incremental"));
+ ignore_value(VIR_STRDUP(bitmap, QEMU_DRIVE_BACKUP_BITMAP));
+ } else {
+ goto endjob;
+ }
+
+ qemuBlockJobSyncBegin(disk);
+ sync_begin = 1;
+
+ qemuDomainObjEnterMonitor(driver, vm);
+ ret = qemuMonitorDriveBackup(priv->mon, device, dest,
+ mode, format, bitmap, speed);
+ if (qemuDomainObjExitMonitor(driver, vm) < 0) {
+ ret = -1;
+ goto rollback;
+ }
+
+ if (ret == 0)
+ QEMU_DOMAIN_DISK_PRIVATE(disk)->blockjob = true;
+ else {
+ virReportError(VIR_ERR_OPERATION_INVALID,
+ _("disk %s do backup failed"), disk->dst);
+ goto rollback;
+ }
+
+ /* reset backup state */
+ disk->backup_state = VIR_DOMAIN_DISK_BACKUP_STATE_NONE;
+ while ((ret = qemuDomainDriveBackupReady(driver, vm, disk)) != 1) {
+ if (ret)
+ goto rollback;
+
+ if (priv->job.abortJob) {
+ priv->job.current->type = VIR_DOMAIN_JOB_CANCELLED;
+ virReportError(VIR_ERR_OPERATION_ABORTED, _("%s: %s"),
+ qemuDomainAsyncJobTypeToString(priv->job.asyncJob),
+ _("canceled by client"));
+ goto rollback;
+ }
+
+ if (virDomainObjWait(vm) < 0)
+ goto rollback;
+ }
+
+endjob:
+ if (sync_begin)
+ qemuBlockJobSyncEnd(driver, vm, disk);
+
+ qemuDomainObjEndJob(driver, vm);
+
+cleanup:
+ VIR_FREE(device);
+ VIR_FREE(mode);
+ VIR_FREE(bitmap);
+ virDomainObjEndAPI(&vm);
+ return ret;
+
+rollback:
+ qemuDomainObjEnterMonitor(driver, vm);
+ qemuMonitorBlockDirtyBitmapRemove(priv->mon, device,
+ QEMU_DRIVE_BACKUP_BITMAP);
+ if (qemuDomainObjExitMonitor(driver, vm) < 0)
+ ret = -1;
+
+ disk->full_backup = false;
+ goto endjob;
+}
+
static int
qemuDomainOpenGraphics(virDomainPtr dom,
unsigned int idx,
@@ -19912,6 +20108,7 @@ static virHypervisorDriver qemuHypervisorDriver = {
.domainBlockRebase = qemuDomainBlockRebase, /* 0.9.10 */
.domainBlockCopy = qemuDomainBlockCopy, /* 1.2.9 */
.domainBlockCommit = qemuDomainBlockCommit, /* 1.0.0 */
+ .domainBlockBackup = qemuDomainBlockBackup, /* 1.3.6 */
.connectIsAlive = qemuConnectIsAlive, /* 0.9.8 */
.nodeSuspendForDuration = qemuNodeSuspendForDuration, /* 0.9.8 */
.domainSetBlockIoTune = qemuDomainSetBlockIoTune, /* 0.9.8 */
diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c
index 597307f..ef52fa1 100644
--- a/src/qemu/qemu_monitor.c
+++ b/src/qemu/qemu_monitor.c
@@ -2935,6 +2935,65 @@ qemuMonitorSupportsActiveCommit(qemuMonitorPtr mon)
}
+/* Start a block-backup block job. bandwidth is in bytes/sec. */
+int
+qemuMonitorDriveBackup(qemuMonitorPtr mon, const char *device,
+ const char *dest, const char *mode,
+ const char *format, const char *bitmap,
+ unsigned long long bandwidth)
+{
+ VIR_DEBUG("device=%s, dest=%s, mode=%s, "
+ "format=%s, bitmap=%s, bandwidth=%llu",
+ device, dest, mode, NULLSTR(format),
+ NULLSTR(bitmap), bandwidth);
+
+ QEMU_CHECK_MONITOR_JSON(mon);
+
+ return qemuMonitorJSONDriveBackup(mon, device, dest, mode,
+ format, bitmap, bandwidth);
+}
+
+
+/* add dirty bitmap for backup on the block device. */
+int
+qemuMonitorBlockDirtyBitmapAdd(qemuMonitorPtr mon, const char *device,
+ const char *bitmap, unsigned int granularity)
+{
+ VIR_DEBUG("device=%s, bitmap=%s, granularity=%#x",
+ device, bitmap, granularity);
+
+ QEMU_CHECK_MONITOR_JSON(mon);
+
+ return qemuMonitorJSONBlockDirtyBitmapAdd(mon, device, bitmap, granularity);
+}
+
+
+/* remove dirty bitmap for backup on the block device. */
+int
+qemuMonitorBlockDirtyBitmapRemove(qemuMonitorPtr mon,
+ const char *device, const char *bitmap)
+{
+ VIR_DEBUG("device=%s, bitmap=%s", device, bitmap);
+
+ QEMU_CHECK_MONITOR_JSON(mon);
+
+ return qemuMonitorJSONBlockDirtyBitmapRemove(mon, device, bitmap);
+}
+
+
+/* clear dirty bitmap for backup on the block device. */
+int
+qemuMonitorBlockDirtyBitmapClear(qemuMonitorPtr mon,
+ const char *device, const char *bitmap)
+{
+ VIR_DEBUG("device=%s, bitmap=%s", device, bitmap);
+
+ QEMU_CHECK_MONITOR_JSON(mon);
+
+ return qemuMonitorJSONBlockDirtyBitmapClear(mon, device, bitmap);
+}
+
+
/* Determine the name that qemu is using for tracking the backing
* element TARGET within the chain starting at TOP. */
char *
diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h
index dd3587f..0dee9f6 100644
--- a/src/qemu/qemu_monitor.h
+++ b/src/qemu/qemu_monitor.h
@@ -736,6 +736,29 @@ int qemuMonitorBlockCommit(qemuMonitorPtr mon,
unsigned long long bandwidth)
ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4);
bool qemuMonitorSupportsActiveCommit(qemuMonitorPtr mon);
+
+int
+qemuMonitorDriveBackup(qemuMonitorPtr mon, const char *device,
+ const char *dest, const char *mode,
+ const char *format, const char *bitmap,
+ unsigned long long bandwidth)
+ ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) ATTRIBUTE_NONNULL(4);
+
+int
+qemuMonitorBlockDirtyBitmapAdd(qemuMonitorPtr mon, const char *device,
+ const char *bitmap, unsigned int granularity)
+ ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
+int
+qemuMonitorBlockDirtyBitmapRemove(qemuMonitorPtr mon,
+ const char *device, const char *bitmap)
+ ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
+int
+qemuMonitorBlockDirtyBitmapClear(qemuMonitorPtr mon,
+ const char *device, const char *bitmap)
+ ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
char *qemuMonitorDiskNameLookup(qemuMonitorPtr mon,
const char *device,
virStorageSourcePtr top,
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 585b882..9b06229 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -3902,6 +3902,130 @@ qemuMonitorJSONBlockCommit(qemuMonitorPtr mon, const char
*device,
return ret;
}
+/* TODO:
+ */
+int
+qemuMonitorJSONDriveBackup(qemuMonitorPtr mon, const char *device,
+ const char *dest, const char *mode,
+ const char *format, const char *bitmap,
+ unsigned long long speed)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuMonitorJSONMakeCommand("drive-backup",
+ "s:device", device,
+ "s:target", dest,
+ "s:sync", mode,
+ "s:mode", "existing",
+ "Y:speed", speed,
+ "S:format", format,
+ "S:bitmap", bitmap,
+ NULL);
+ if (!cmd)
+ return -1;
+
+ if ((ret = qemuMonitorJSONCommand(mon, cmd, &reply)) < 0)
+ goto cleanup;
+
+ ret = qemuMonitorJSONCheckError(cmd, reply);
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+/* TODO:
+ */
+int
+qemuMonitorJSONBlockDirtyBitmapAdd(qemuMonitorPtr mon,
+ const char *device,
+ const char *bitmap,
+ unsigned int granularity)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-add",
+ "s:node", device,
+ "s:name", bitmap,
+ "p:granularity", granularity,
+ NULL);
+ if (!cmd)
+ return -1;
+
+ if ((ret = qemuMonitorJSONCommand(mon, cmd, &reply)) < 0)
+ goto cleanup;
+
+ ret = qemuMonitorJSONCheckError(cmd, reply);
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+/* TODO:
+ */
+int
+qemuMonitorJSONBlockDirtyBitmapRemove(qemuMonitorPtr mon,
+ const char *device,
+ const char *bitmap)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-remove",
+ "s:node", device,
+ "s:name", bitmap,
+ NULL);
+ if (!cmd)
+ return -1;
+
+ if ((ret = qemuMonitorJSONCommand(mon, cmd, &reply)) < 0)
+ goto cleanup;
+
+ ret = qemuMonitorJSONCheckError(cmd, reply);
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
+/* TODO:
+ */
+int
+qemuMonitorJSONBlockDirtyBitmapClear(qemuMonitorPtr mon,
+ const char *device,
+ const char *bitmap)
+{
+ int ret = -1;
+ virJSONValuePtr cmd;
+ virJSONValuePtr reply = NULL;
+
+ cmd = qemuMonitorJSONMakeCommand("block-dirty-bitmap-clear",
+ "s:node", device,
+ "s:name", bitmap,
+ NULL);
+ if (!cmd)
+ return -1;
+
+ if ((ret = qemuMonitorJSONCommand(mon, cmd, &reply)) < 0)
+ goto cleanup;
+
+ ret = qemuMonitorJSONCheckError(cmd, reply);
+
+ cleanup:
+ virJSONValueFree(cmd);
+ virJSONValueFree(reply);
+ return ret;
+}
+
int
qemuMonitorJSONDrivePivot(qemuMonitorPtr mon,
const char *device)
diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h
index 76758db..5519e45 100644
--- a/src/qemu/qemu_monitor_json.h
+++ b/src/qemu/qemu_monitor_json.h
@@ -268,6 +268,29 @@ int qemuMonitorJSONBlockCommit(qemuMonitorPtr mon,
unsigned long long bandwidth)
ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+int qemuMonitorJSONDriveBackup(qemuMonitorPtr mon, const char *device,
+ const char *dest, const char *mode,
+ const char *format, const char *bitmap,
+ unsigned long long speed)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3)
+ ATTRIBUTE_NONNULL(4);
+
+int qemuMonitorJSONBlockDirtyBitmapAdd(qemuMonitorPtr mon,
+ const char *device,
+ const char *bitmap,
+ unsigned int granularity)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
+int qemuMonitorJSONBlockDirtyBitmapRemove(qemuMonitorPtr mon,
+ const char *device,
+ const char *bitmap)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
+int qemuMonitorJSONBlockDirtyBitmapClear(qemuMonitorPtr mon,
+ const char *device,
+ const char *bitmap)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
char *qemuMonitorJSONDiskNameLookup(qemuMonitorPtr mon,
const char *device,
virStorageSourcePtr top,
--
2.6.4