[libvirt] [RFC PATCH 0/4] Add new function about block backup

Add new function about block backup, it supports the drive-backup function in qemu, it supports three modes: full, top, incremental. How to use it? As usual, we must do the full backup at first, if it's successfull, next time, we can do the incremental backup, because it has the bitmap to trace the dirty io. The incermental backup is faster. If backup fail at some time, we must do the full backup again, only in this way, we can keep the backup data correctly. Rudy Zhang (4): Introduce virDomainBlockBackup API qemu drive support block backup. virsh: support blockbackup in virsh command libvirt-test: libvirt test supports drive-backup docs/apibuild.py | 3 +- include/libvirt/libvirt-domain.h | 15 ++ src/conf/domain_conf.h | 12 ++ src/driver-hypervisor.h | 9 + src/libvirt-domain.c | 56 ++++++ src/libvirt_public.syms | 5 + 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 +++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +- src/remote_protocol-structs | 9 + tests/qemucaps2xmldata/all_1.6.0-1.caps | 1 + tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps | 1 + tools/virsh-domain.c | 149 ++++++++++++++++ 20 files changed, 713 insertions(+), 2 deletions(-) -- 2.6.4

For Backuping the disk image. It supports tree backup mode: full, top, incremental. Incremental backup mode must do full or top backup first, and it exists dirty bitmap to trace io. Signed-off-by: longyou <longyou@mogujie.com> --- docs/apibuild.py | 3 ++- include/libvirt/libvirt-domain.h | 15 +++++++++++ src/driver-hypervisor.h | 9 +++++++ src/libvirt-domain.c | 56 ++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 5 ++++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +++++++++++- src/remote_protocol-structs | 9 +++++++ 8 files changed, 113 insertions(+), 2 deletions(-) diff --git a/docs/apibuild.py b/docs/apibuild.py index f5216ea..42ce538 100755 --- a/docs/apibuild.py +++ b/docs/apibuild.py @@ -1851,7 +1851,8 @@ class CParser: "virDomainBlockJobSetSpeed" : (False, ("bandwidth")), "virDomainBlockPull" : (False, ("bandwidth")), "virDomainBlockRebase" : (False, ("bandwidth")), - "virDomainMigrateGetMaxSpeed" : (False, ("bandwidth")) } + "virDomainMigrateGetMaxSpeed" : (False, ("bandwidth")), + "virDomainBlockBackup" : (False, ("bandwidth")) } def checkLongLegacyFunction(self, name, return_type, signature): if "long" in return_type and "long long" not in return_type: diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h index cba4fa5..3a9ab6c 100644 --- a/include/libvirt/libvirt-domain.h +++ b/include/libvirt/libvirt-domain.h @@ -2139,6 +2139,21 @@ int virDomainBlockCommit(virDomainPtr dom, const char *disk, const char *base, const char *top, unsigned long bandwidth, unsigned int flags); +/** + * virDomainBlockBackupFlags: + * + * Flags available for virDomainBlockBackup(). + */ +typedef enum { + VIR_DOMAIN_BLOCK_BACKUP_FULL = 1 << 1, /* Backup with full mode */ + VIR_DOMAIN_BLOCK_BACKUP_TOP = 1 << 2, /* Backup with top mode */ + VIR_DOMAIN_BLOCK_BACKUP_INCREMENTAL = 1 << 3, /* Backup with incremental + mode by dirty bitmap */ +} virDomainBlockBackupFlags; + +int virDomainBlockBackup(virDomainPtr dom, const char *path, const char *dest, + unsigned long bandwidth, const char *format, + unsigned int flags); /* Block I/O throttling support */ diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index d11ff7f..171eb14 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1020,6 +1020,14 @@ typedef int unsigned int flags); typedef int +(*virDrvDomainBlockBackup)(virDomainPtr dom, + const char *path, + const char *dest, + unsigned long long bandwidth, + const char *format, + unsigned int flags); + +typedef int (*virDrvConnectSetKeepAlive)(virConnectPtr conn, int interval, unsigned int count); @@ -1436,6 +1444,7 @@ struct _virHypervisorDriver { virDrvDomainBlockRebase domainBlockRebase; virDrvDomainBlockCopy domainBlockCopy; virDrvDomainBlockCommit domainBlockCommit; + virDrvDomainBlockBackup domainBlockBackup; virDrvConnectSetKeepAlive connectSetKeepAlive; virDrvConnectIsAlive connectIsAlive; virDrvNodeSuspendForDuration nodeSuspendForDuration; diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c index 73ae369..18ef249 100644 --- a/src/libvirt-domain.c +++ b/src/libvirt-domain.c @@ -10473,6 +10473,62 @@ virDomainBlockCommit(virDomainPtr dom, const char *disk, /** + * virDomainBlockBackup: + * @dom: pointer to domain object + * @path: path to the block device, or device shorthand + * @dest: path to image for backup + * @bandwidth: (optional) specify bandwidth limit; flags determine the unit + * @format: (optional) the dest image format + * @flags: bitwise-OR of virDomainBlockBackupFlags + * + * Backup the path image to dest image, it supports three backup mode: + * "full" mode with flags VIR_DOMAIN_BLOCK_BACKUP_FULL, it will backup all + * data in the image chain. + * "top" mode with flags VIR_DOMAIN_BLOCK_BACKUP_TOP, it only backup the + * data of top image to dest image. + * "incremental" mode with flags VIR_DOMAIN_BLOCK_BACKUP_INCREMENTAL, it wosks + * with dirty bitmap when done the "full" or "top" mode backup. Incremental + * backup only backup the data in the dirty bitmap. + * + * Returns 0 if the operation has started, -1 on failure. + */ +int +virDomainBlockBackup(virDomainPtr dom, const char *path, + const char *dest, unsigned long bandwidth, + const char *format, unsigned int flags) +{ + virConnectPtr conn; + + VIR_DOMAIN_DEBUG(dom, "path=%s, dest=%s, bandwidth=%lu, format=%s, flags=%x", + path, dest, bandwidth, format, flags); + + virResetLastError(); + + virCheckDomainReturn(dom, -1); + conn = dom->conn; + + virCheckReadOnlyGoto(conn->flags, error); + virCheckNonNullArgGoto(path, error); + virCheckNonNullArgGoto(dest, error); + + if (conn->driver->domainBlockBackup) { + int ret; + ret = conn->driver->domainBlockBackup(dom, path, dest, + bandwidth, format, flags); + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + +error: + virDispatchError(dom->conn); + return -1; +} + + +/** * virDomainOpenGraphics: * @dom: pointer to domain object * @idx: index of graphics config to open diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 1e920d6..34a2fff 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -732,4 +732,9 @@ LIBVIRT_1.3.3 { virDomainSetPerfEvents; } LIBVIRT_1.2.19; +LIBVIRT_1.3.6 { + global: + virDomainBlockBackup; +} LIBVIRT_1.3.3; + # .... define new API here using predicted next version number .... diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index e3cf5fb..7513a7f 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7796,6 +7796,7 @@ static virHypervisorDriver hypervisor_driver = { .domainBlockRebase = remoteDomainBlockRebase, /* 0.9.10 */ .domainBlockCopy = remoteDomainBlockCopy, /* 1.2.9 */ .domainBlockCommit = remoteDomainBlockCommit, /* 0.10.2 */ + .domainBlockBackup = remoteDomainBlockBackup, /* 1.3.6 */ .connectSetKeepAlive = remoteConnectSetKeepAlive, /* 0.9.8 */ .connectIsAlive = remoteConnectIsAlive, /* 0.9.8 */ .nodeSuspendForDuration = remoteNodeSuspendForDuration, /* 0.9.8 */ diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index bab8ef2..0f3b29c 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -1364,6 +1364,15 @@ struct remote_domain_block_commit_args { unsigned int flags; }; +struct remote_domain_block_backup_args { + remote_nonnull_domain dom; + remote_nonnull_string path; + remote_nonnull_string dest; + unsigned hyper bandwidth; + remote_string format; + unsigned int flags; +}; + struct remote_domain_set_block_io_tune_args { remote_nonnull_domain dom; remote_nonnull_string disk; @@ -5793,5 +5802,11 @@ enum remote_procedure { * @generate: both * @acl: none */ - REMOTE_PROC_DOMAIN_EVENT_CALLBACK_DEVICE_REMOVAL_FAILED = 367 + REMOTE_PROC_DOMAIN_EVENT_CALLBACK_DEVICE_REMOVAL_FAILED = 367, + + /** + * @generate: both + * @acl: domain:block_write + */ + REMOTE_PROC_DOMAIN_BLOCK_BACKUP = 368 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index fe1b8a8..e3cc5c5 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -996,6 +996,14 @@ struct remote_domain_block_commit_args { uint64_t bandwidth; u_int flags; }; +struct remote_domain_block_backup_args { + remote_nonnull_domain dom; + remote_nonnull_string path; + remote_nonnull_string dest; + uint64_t bandwidth; + remote_string format; + u_int flags; +}; struct remote_domain_set_block_io_tune_args { remote_nonnull_domain dom; remote_nonnull_string disk; @@ -3103,4 +3111,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_GET_PERF_EVENTS = 365, REMOTE_PROC_DOMAIN_SET_PERF_EVENTS = 366, REMOTE_PROC_DOMAIN_EVENT_CALLBACK_DEVICE_REMOVAL_FAILED = 367, + REMOTE_PROC_DOMAIN_BLOCK_BACKUP = 358, }; -- 2.6.4

On 06/08/2016 12:21 AM, Rudy Zhang wrote:
For Backuping the disk image.
It supports tree backup mode: full, top, incremental. Incremental backup mode must do full or top backup first, and it exists dirty bitmap to trace io.
Signed-off-by: longyou <longyou@mogujie.com> ---
+++ b/src/remote/remote_driver.c @@ -7796,6 +7796,7 @@ static virHypervisorDriver hypervisor_driver = { .domainBlockRebase = remoteDomainBlockRebase, /* 0.9.10 */ .domainBlockCopy = remoteDomainBlockCopy, /* 1.2.9 */ .domainBlockCommit = remoteDomainBlockCommit, /* 0.10.2 */ + .domainBlockBackup = remoteDomainBlockBackup, /* 1.3.6 */
Is this something that would be better to expose by adding new flags to the existing virDomainBlockCopy() command (basically, a new VIR_DOMAIN_BLOCK_COPY_BACKUP), rather than inventing a new API? -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

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@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

support blockbackup in virsh command. Use it as: blockbackup <domain> <path> <dest> [--mode <string>] [--bandwidth <number>] [--format <string>] [--wait] [--verbose] Signed-off-by: longyou <longyou@mogujie.com> --- tools/virsh-domain.c | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c index 8d7ff61..40181da 100644 --- a/tools/virsh-domain.c +++ b/tools/virsh-domain.c @@ -2922,6 +2922,149 @@ cmdBlockResize(vshControl *ctl, const vshCmd *cmd) return ret; } +/* + * "blockbackup" command + */ +static const vshCmdInfo info_block_backup[] = { + {.name = "help", + .data = N_("Start a block backup operation.") + }, + {.name = "desc", + .data = N_("Backup a disk image chain to dest image.") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_block_backup[] = { + {.name = "domain", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("domain name, id or uuid") + }, + {.name = "path", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("fully-qualified path of source disk") + }, + {.name = "dest", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("path of the backup image to create") + }, + {.name = "mode", + .type = VSH_OT_STRING, + .help = N_("image backup mode") + }, + {.name = "bandwidth", + .type = VSH_OT_INT, + .help = N_("bandwidth limit in MiB/s") + }, + {.name = "format", + .type = VSH_OT_STRING, + .help = N_("dest image format") + }, + {.name = "wait", + .type = VSH_OT_BOOL, + .help = N_("wait for job to reach backup phase") + }, + {.name = "verbose", + .type = VSH_OT_BOOL, + .help = N_("with --wait, display the progress") + }, + {.name = NULL} +}; + +static bool +cmdBlockBackup(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom = NULL; + const char *path = NULL; + const char *dest = NULL; + const char *mode = NULL; + const char *format = NULL; + unsigned long bandwidth = 0; + unsigned int flags = 0; + bool ret = false; + bool verbose = vshCommandOptBool(cmd, "verbose"); + bool blocking = vshCommandOptBool(cmd, "wait") || verbose; + virshBlockJobWaitDataPtr bjWait = NULL; + unsigned long long limit = MIN(ULONG_MAX, ULLONG_MAX >> 20); + + if (vshCommandOptStringReq(ctl, cmd, "path", &path) < 0) + return false; + if (vshCommandOptStringReq(ctl, cmd, "dest", &dest) < 0) + return false; + if (vshCommandOptStringReq(ctl, cmd, "mode", &mode) < 0) + return false; + if (vshCommandOptStringReq(ctl, cmd, "format", &format) < 0) + return false; + + if (vshCommandOptULWrap(ctl, cmd, "bandwidth", &bandwidth) < 0) + return false; + + if (!mode || !strcmp(mode, "full")) { + flags |= VIR_DOMAIN_BLOCK_BACKUP_FULL; + } else if (!strcmp(mode, "top")) { + flags |= VIR_DOMAIN_BLOCK_BACKUP_TOP; + } else if (!strcmp(mode, "incremental")) { + flags |= VIR_DOMAIN_BLOCK_BACKUP_INCREMENTAL; + } else { + return false; + } + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (blocking && + !(bjWait = virshBlockJobWaitInit(ctl, dom, path, _("Block Backup"), + verbose, 0, 0))) + goto cleanup; + + /* bandwidth is ulong MiB/s, but the typed parameter is + * ullong bytes/s; make sure we don't overflow */ + if (bandwidth > limit) { + vshError(ctl, _("bandwidth must be less than %llu"), limit); + goto cleanup; + } + + if (virDomainBlockBackup(dom, path, dest, + bandwidth, format, flags) < 0) + goto cleanup; + + if (!blocking) { + vshPrint(ctl, "%s", _("Block Backup started")); + ret = true; + goto cleanup; + } + + /* Execution continues here only if --wait or friends were specified */ + switch (virshBlockJobWait(bjWait)) { + case -1: + goto cleanup; + + case VIR_DOMAIN_BLOCK_JOB_CANCELED: + vshPrint(ctl, "\n%s", _("Backup aborted")); + goto cleanup; + break; + + case VIR_DOMAIN_BLOCK_JOB_FAILED: + vshPrint(ctl, "\n%s", _("Backup failed")); + goto cleanup; + break; + + case VIR_DOMAIN_BLOCK_JOB_READY: + case VIR_DOMAIN_BLOCK_JOB_COMPLETED: + break; + } + + ret = true; + +cleanup: + virDomainFree(dom); + virshBlockJobWaitFree(bjWait); + return ret; +} + #ifndef WIN32 /* * "console" command @@ -13074,6 +13217,12 @@ const vshCmdDef domManagementCmds[] = { .info = info_block_resize, .flags = 0 }, + {.name = "blockbackup", + .handler = cmdBlockBackup, + .opts = opts_block_backup, + .info = info_block_backup, + .flags = 0 + }, {.name = "change-media", .handler = cmdChangeMedia, .opts = opts_change_media, -- 2.6.4

libvirt test supports drive-backup. Signed-off-by: longyou <longyou@mogujie.com> --- tests/qemucaps2xmldata/all_1.6.0-1.caps | 1 + tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/qemucaps2xmldata/all_1.6.0-1.caps b/tests/qemucaps2xmldata/all_1.6.0-1.caps index d39d0be..27ca24e 100644 --- a/tests/qemucaps2xmldata/all_1.6.0-1.caps +++ b/tests/qemucaps2xmldata/all_1.6.0-1.caps @@ -84,6 +84,7 @@ <flag name='block-commit'/> <flag name='vnc'/> <flag name='drive-mirror'/> + <flag name='drive-backup'/> <flag name='usb-host.bootindex'/> <flag name='blockdev-snapshot-sync'/> <flag name='qxl'/> diff --git a/tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps b/tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps index 5a0372c..8d8434f 100644 --- a/tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps +++ b/tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps @@ -84,6 +84,7 @@ <flag name='block-commit'/> <flag name='vnc'/> <flag name='drive-mirror'/> + <flag name='drive-backup'/> <flag name='usb-host.bootindex'/> <flag name='qxl'/> <flag name='VGA'/> -- 2.6.4

On 06/08/2016 02:21 AM, Rudy Zhang wrote:
Add new function about block backup, it supports the drive-backup function in qemu, it supports three modes: full, top, incremental.
Cheers Rudy! I assume you've CC'd me as I authored a lot of the incremental backup infrastructure in QEMU. I'm not qualified to review libvirt patches, so I'm just going to offer some words of caution from the QEMU side: (1) I didn't expect to see this integrated into libvirt yet as we aren't quite 'finished' with the feature in its totality in QEMU. (2) The specification for qcow2-backed bitmap persistence was merged in QEMU 2.6, but the API for managing persistent dirty bitmap information via QMP is still a WIP and could impact the design of the eventual libvirt implementation. (3) Migration of dirty bitmap information is also still a WIP. Our migration strategy is still not really solidified. I'm not sure if these features will hit QEMU 2.7 due to the short development window, but we are fairly far along in design iterations for these features and I expect them to be in 2.8. I don't personally recommend any features be exposed to users prior to the completion of bitmap persistence and migration, but don't let that stop you from developing your own WIP in the meantime. --js
How to use it? As usual, we must do the full backup at first, if it's successfull, next time, we can do the incremental backup, because it has the bitmap to trace the dirty io. The incermental backup is faster. If backup fail at some time, we must do the full backup again, only in this way, we can keep the backup data correctly.
Rudy Zhang (4): Introduce virDomainBlockBackup API qemu drive support block backup. virsh: support blockbackup in virsh command libvirt-test: libvirt test supports drive-backup
docs/apibuild.py | 3 +- include/libvirt/libvirt-domain.h | 15 ++ src/conf/domain_conf.h | 12 ++ src/driver-hypervisor.h | 9 + src/libvirt-domain.c | 56 ++++++ src/libvirt_public.syms | 5 + 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 +++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +- src/remote_protocol-structs | 9 + tests/qemucaps2xmldata/all_1.6.0-1.caps | 1 + tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps | 1 + tools/virsh-domain.c | 149 ++++++++++++++++ 20 files changed, 713 insertions(+), 2 deletions(-)

08.06.2016 9:21, Rudy Zhang пишет:
Add new function about block backup, it supports the drive-backup function in qemu, it supports three modes: full, top, incremental.
How to use it? As usual, we must do the full backup at first, if it's successfull, next time, we can do the incremental backup, because it has the bitmap to trace the dirty io. The incermental backup is faster. If backup fail at some time, we must do the full backup again, only in this way, we can keep the backup data correctly.
Hi Rudy, A couple of months ago I started a thread regading backup API and we came to a sort of agreement on how it should look like, see [1]. Though it's not ready yet for sending to mailing list I already started to work on it and I will certainly send first reference implementation shortly. Once it appears in the list I will appriciate any help. ETA - end of this month. Best, Maxim [1] https://www.redhat.com/archives/libvir-list/2016-April/msg00401.html
Rudy Zhang (4): Introduce virDomainBlockBackup API qemu drive support block backup. virsh: support blockbackup in virsh command libvirt-test: libvirt test supports drive-backup
docs/apibuild.py | 3 +- include/libvirt/libvirt-domain.h | 15 ++ src/conf/domain_conf.h | 12 ++ src/driver-hypervisor.h | 9 + src/libvirt-domain.c | 56 ++++++ src/libvirt_public.syms | 5 + 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 +++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +- src/remote_protocol-structs | 9 + tests/qemucaps2xmldata/all_1.6.0-1.caps | 1 + tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps | 1 + tools/virsh-domain.c | 149 ++++++++++++++++ 20 files changed, 713 insertions(+), 2 deletions(-)

On 16/6/9 下午7:22, Maxim Nestratov wrote:
08.06.2016 9:21, Rudy Zhang пишет:
Add new function about block backup, it supports the drive-backup function in qemu, it supports three modes: full, top, incremental.
How to use it? As usual, we must do the full backup at first, if it's successfull, next time, we can do the incremental backup, because it has the bitmap to trace the dirty io. The incermental backup is faster. If backup fail at some time, we must do the full backup again, only in this way, we can keep the backup data correctly.
Hi Rudy,
A couple of months ago I started a thread regading backup API and we came to a sort of agreement on how it should look like, see [1]. Though it's not ready yet for sending to mailing list I already started to work on it and I will certainly send first reference implementation shortly. Once it appears in the list I will appriciate any help. ETA - end of this month.
Best, Maxim
[1] https://www.redhat.com/archives/libvir-list/2016-April/msg00401.html
Hi Maxim, I have not noticed your idea about backup API before.I did this, only want to use backup function in openstack, so in libvirt I just have implemented the easiest interface function to qemu. I will be careful to know your idea, and then send my v2 patch, or wait for your patch. -- Best regards, Rudy Zhang
Rudy Zhang (4): Introduce virDomainBlockBackup API qemu drive support block backup. virsh: support blockbackup in virsh command libvirt-test: libvirt test supports drive-backup
docs/apibuild.py | 3 +- include/libvirt/libvirt-domain.h | 15 ++ src/conf/domain_conf.h | 12 ++ src/driver-hypervisor.h | 9 + src/libvirt-domain.c | 56 ++++++ src/libvirt_public.syms | 5 + 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 +++ src/remote/remote_driver.c | 1 + src/remote/remote_protocol.x | 17 +- src/remote_protocol-structs | 9 + tests/qemucaps2xmldata/all_1.6.0-1.caps | 1 + tests/qemucaps2xmldata/nodisksnapshot_1.6.0-1.caps | 1 + tools/virsh-domain.c | 149 ++++++++++++++++ 20 files changed, 713 insertions(+), 2 deletions(-)
participants (4)
-
Eric Blake
-
John Snow
-
Maxim Nestratov
-
Rudy Zhang