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@xxxxxxxxxxx> --- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list