We currently create 'vma' archives without any configuration inside. Future versions may support other formats...
Another option would be to simply dump <devid,cluster_num,cluster_data> to the output fh (pipe), and an external binary saves the data. That way we could move the whole archive format related code out of qemu. Signed-off-by: Dietmar Maurer <diet...@proxmox.com> --- Makefile.objs | 2 +- blockdev.c | 263 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hmp-commands.hx | 31 +++++++ hmp.c | 63 +++++++++++++ hmp.h | 3 + monitor.c | 7 ++ qapi-schema.json | 46 ++++++++++ qmp-commands.hx | 27 ++++++ 8 files changed, 441 insertions(+), 1 deletions(-) diff --git a/Makefile.objs b/Makefile.objs index cb46be5..b5732e2 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -48,7 +48,7 @@ coroutine-obj-$(CONFIG_WIN32) += coroutine-win32.o block-obj-y = iov.o cache-utils.o qemu-option.o module.o async.o block-obj-y += nbd.o block.o blockjob.o aes.o qemu-config.o block-obj-y += thread-pool.o qemu-progress.o qemu-sockets.o uri.o notify.o -block-obj-y += backup.o +block-obj-y += vma-writer.o backup.o block-obj-y += $(coroutine-obj-y) $(qobject-obj-y) $(version-obj-y) block-obj-$(CONFIG_POSIX) += event_notifier-posix.o aio-posix.o block-obj-$(CONFIG_WIN32) += event_notifier-win32.o aio-win32.o diff --git a/blockdev.c b/blockdev.c index e73fd6e..de5db5e 100644 --- a/blockdev.c +++ b/blockdev.c @@ -20,6 +20,7 @@ #include "qmp-commands.h" #include "trace.h" #include "arch_init.h" +#include "vma.h" static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives); @@ -1321,6 +1322,268 @@ void qmp_drive_mirror(const char *device, const char *target, drive_get_ref(drive_get_by_blockdev(bs)); } +/* Backup related function */ + +static struct VmaBackupState { + time_t start_time; + time_t end_time; + char *backupfile; + VmaWriter *vmaw; + VmaStatus status; +} backup_state; + +typedef struct BackupCB { + BlockDriverState *bs; + VmaWriter *vmaw; + uint8_t dev_id; +} BackupCB; + +static int backup_dump_cb(void *opaque, BlockDriverState *bs, + int64_t cluster_num, unsigned char *buf) +{ + BackupCB *bcb = opaque; + + if (vma_writer_write(bcb->vmaw, bcb->dev_id, cluster_num, buf) < 0) { + return -1; + } + return 0; +} + +static void backup_complete_cb(void *opaque, int ret) +{ + BackupCB *bcb = opaque; + + printf("backup_complete_cb start %d\n", ret); + drive_put_ref_bh_schedule(drive_get_by_blockdev(bcb->bs)); + + if (ret < 0) { + vma_writer_set_error(bcb->vmaw, "backup_complete_cb %d", ret); + } + + if (vma_writer_close_stream(bcb->vmaw, bcb->dev_id) <= 0) { + printf("all backup jobs completed\n"); + + backup_state.end_time = time(NULL); + + int res = vma_writer_close(bcb->vmaw); + vma_writer_get_status(bcb->vmaw, &backup_state.status); + vma_writer_destroy(bcb->vmaw); + + backup_state.vmaw = NULL; + if (res < 0) { + printf("backup failed\n"); + } else { + printf("backup successful\n"); + } + + } + + g_free(bcb); +} + +void qmp_backup_cancel(Error **errp) +{ + if (backup_state.vmaw) { + vma_writer_set_error(backup_state.vmaw, "backup canceled"); + } +} + +char *qmp_backup(const char * backupfile, bool has_devlist, const char *devlist, + bool has_speed, int64_t speed, Error **errp) +{ + BlockDriverState *bs; + Error *local_err = NULL; + VmaWriter *vmaw = NULL; + gchar **devs = NULL; + GList *bcblist = NULL; + + if (has_devlist) { + devs = g_strsplit(devlist, ",;:", -1); + + gchar **d = devs; + while (d && *d) { + bs = bdrv_find(*d); + if (bs) { + if (bdrv_is_read_only(bs)) { + error_set(errp, QERR_DEVICE_IS_READ_ONLY, *d); + goto err; + } + if (!bdrv_is_inserted(bs)) { + error_set(errp, QERR_DEVICE_HAS_NO_MEDIUM, *d); + goto err; + } + BackupCB *bcb = g_new0(BackupCB, 1); + bcb->bs = bs; + bcblist = g_list_append(bcblist, bcb); + } else { + error_set(errp, QERR_DEVICE_NOT_FOUND, *d); + goto err; + } + d++; + } + + } else { + + bs = NULL; + while ((bs = bdrv_next(bs))) { + + if (!bdrv_is_inserted(bs) || bdrv_is_read_only(bs)) { + continue; + } + + BackupCB *bcb = g_new0(BackupCB, 1); + bcb->bs = bs; + bcblist = g_list_append(bcblist, bcb); + } + } + + if (!bcblist) { + error_set(errp, ERROR_CLASS_GENERIC_ERROR, "empty device list"); + goto err; + } + + GList *l = bcblist; + while (l) { + BackupCB *bcb = l->data; + l = g_list_next(l); + if (bcb->bs->job) { + error_set(errp, QERR_DEVICE_IN_USE, bdrv_get_device_name(bcb->bs)); + goto err; + } + } + + vmaw = vma_writer_create(backupfile, speed, &local_err); + if (!vmaw) { + if (error_is_set(&local_err)) { + error_propagate(errp, local_err); + } + goto err; + } + + /* register all devices for vma writer */ + l = bcblist; + while (l) { + BackupCB *bcb = l->data; + l = g_list_next(l); + bcb->vmaw = vmaw; + bcb->dev_id = vma_writer_register_stream( + vmaw, bdrv_get_device_name(bcb->bs), bdrv_getlength(bcb->bs)); + if (bcb->dev_id <= 0) { + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "vma_writer_register_stream failed"); + goto err; + } + } + + backup_state.start_time = time(NULL); + backup_state.end_time = 0; + backup_state.backupfile = g_strdup(backupfile); + backup_state.vmaw = vmaw; + + vma_writer_get_status(vmaw, &backup_state.status); + + /* start all jobs (one for each device) */ + l = bcblist; + while (l) { + BackupCB *bcb = l->data; + l = g_list_next(l); + + if (bdrv_backup_init(bcb->bs, backup_dump_cb, + backup_complete_cb, bcb)) { + /* Grab a reference so hotplug does not delete the + * BlockDriverState from underneath us. + */ + drive_get_ref(drive_get_by_blockdev(bcb->bs)); + } + } + + return g_strdup(backup_state.status.uuid_str); + +err: + + l = bcblist; + while (l) { + g_free(l->data); + l = g_list_next(l); + } + g_list_free(bcblist); + + if (devs) { + g_strfreev(devs); + } + + if (vmaw) { + unlink(backupfile); + vma_writer_close(vmaw); + vma_writer_destroy(vmaw); + } + + return NULL; +} + +BackupStatus *qmp_query_backup(Error **errp) +{ + int i; + BackupStatus *info = g_malloc0(sizeof(*info)); + + if (!backup_state.start_time) { + /* not started, return {} */ + return info; + } + + info->has_status = true; + info->has_start_time = true; + info->start_time = backup_state.start_time; + + if (backup_state.backupfile) { + info->has_backupfile = true; + info->backupfile = g_strdup(backup_state.backupfile); + } + + info->has_uuid = true; + info->uuid = g_strdup(backup_state.status.uuid_str); + + if (backup_state.end_time) { + if (backup_state.status.status >= 0) { + info->status = g_strdup("done"); + } else { + info->status = g_strdup("error"); + if (backup_state.status.errmsg[0]) { + info->has_errmsg = true; + info->errmsg = g_strdup(backup_state.status.errmsg); + } + } + info->has_end_time = true; + info->end_time = backup_state.end_time; + } else { + if (backup_state.vmaw) { + vma_writer_get_status(backup_state.vmaw, &backup_state.status); + } + info->status = g_strdup("active"); + } + + uint64_t total = 0; + uint64_t zero_bytes = 0; + uint64_t transferred = 0; + + for (i = 0; i <= 255; i++) { + if (backup_state.status.stream_info[i].size) { + total += backup_state.status.stream_info[i].size; + zero_bytes += backup_state.status.stream_info[i].zero_bytes; + transferred += backup_state.status.stream_info[i].transferred; + } + } + + info->has_total = true; + info->total = total; + info->has_zero_bytes = true; + info->zero_bytes = zero_bytes; + info->has_transferred = true; + info->transferred = transferred; + + return info; +} + static BlockJob *find_block_job(const char *device) { BlockDriverState *bs; diff --git a/hmp-commands.hx b/hmp-commands.hx index 010b8c9..57be357 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -83,6 +83,35 @@ STEXI Copy data from a backing file into a block device. ETEXI + { + .name = "backup", + .args_type = "backupfile:s,speed:o?,devlist:s?", + .params = "backupfile [speed [devlist]]", + .help = "create a VM Backup.", + .mhandler.cmd = hmp_backup, + }, + +STEXI +@item backup +@findex backup +Create a VM backup. +ETEXI + + { + .name = "backup_cancel", + .args_type = "", + .params = "", + .help = "cancel the current VM backup", + .mhandler.cmd = hmp_backup_cancel, + }, + +STEXI +@item backup_cancel +@findex backup_cancel +Cancel the current VM backup. + +ETEXI + { .name = "block_job_set_speed", .args_type = "device:B,speed:o", @@ -1558,6 +1587,8 @@ show CPU statistics show user network stack connection states @item info migrate show migration status +@item info backup +show backup status @item info migrate_capabilities show current migration capabilities @item info migrate_cache_size diff --git a/hmp.c b/hmp.c index 180ba2b..be84d01 100644 --- a/hmp.c +++ b/hmp.c @@ -130,6 +130,38 @@ void hmp_info_mice(Monitor *mon) qapi_free_MouseInfoList(mice_list); } +void hmp_info_backup(Monitor *mon) +{ + BackupStatus *info; + + info = qmp_query_backup(NULL); + if (info->has_status) { + if (info->has_errmsg) { + monitor_printf(mon, "Backup status: %s - %s\n", + info->status, info->errmsg); + } else { + monitor_printf(mon, "Backup status: %s\n", info->status); + } + } + if (info->has_backupfile) { + int per = (info->has_total && info->total && + info->has_transferred && info->transferred) ? + (info->transferred * 100)/info->total : 0; + int zero_per = (info->has_total && info->total && + info->has_zero_bytes && info->zero_bytes) ? + (info->zero_bytes * 100)/info->total : 0; + monitor_printf(mon, "Backup file: %s\n", info->backupfile); + monitor_printf(mon, "Backup uuid: %s\n", info->uuid); + monitor_printf(mon, "Total size: %zd\n", info->total); + monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n", + info->transferred, per); + monitor_printf(mon, "Zero bytes: %zd (%d%%)\n", + info->zero_bytes, zero_per); + } + + qapi_free_BackupStatus(info); +} + void hmp_info_migrate(Monitor *mon) { MigrationInfo *info; @@ -977,6 +1009,37 @@ void hmp_block_stream(Monitor *mon, const QDict *qdict) hmp_handle_error(mon, &error); } +void hmp_backup_cancel(Monitor *mon, const QDict *qdict) +{ + Error *errp = NULL; + + qmp_backup_cancel(&errp); + + if (error_is_set(&errp)) { + monitor_printf(mon, "%s\n", error_get_pretty(errp)); + error_free(errp); + return; + } +} + +void hmp_backup(Monitor *mon, const QDict *qdict) +{ + const char *backupfile = qdict_get_str(qdict, "backupfile"); + const char *devlist = qdict_get_try_str(qdict, "devlist"); + int64_t speed = qdict_get_try_int(qdict, "speed", 0); + + Error *errp = NULL; + + qmp_backup(backupfile, !!devlist, devlist, + qdict_haskey(qdict, "speed"), speed, &errp); + + if (error_is_set(&errp)) { + monitor_printf(mon, "%s\n", error_get_pretty(errp)); + error_free(errp); + return; + } +} + void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict) { Error *error = NULL; diff --git a/hmp.h b/hmp.h index 0ab03be..20c9a62 100644 --- a/hmp.h +++ b/hmp.h @@ -28,6 +28,7 @@ void hmp_info_mice(Monitor *mon); void hmp_info_migrate(Monitor *mon); void hmp_info_migrate_capabilities(Monitor *mon); void hmp_info_migrate_cache_size(Monitor *mon); +void hmp_info_backup(Monitor *mon); void hmp_info_cpus(Monitor *mon); void hmp_info_block(Monitor *mon); void hmp_info_blockstats(Monitor *mon); @@ -63,6 +64,8 @@ void hmp_eject(Monitor *mon, const QDict *qdict); void hmp_change(Monitor *mon, const QDict *qdict); void hmp_block_set_io_throttle(Monitor *mon, const QDict *qdict); void hmp_block_stream(Monitor *mon, const QDict *qdict); +void hmp_backup(Monitor *mon, const QDict *qdict); +void hmp_backup_cancel(Monitor *mon, const QDict *qdict); void hmp_block_job_set_speed(Monitor *mon, const QDict *qdict); void hmp_block_job_cancel(Monitor *mon, const QDict *qdict); void hmp_block_job_pause(Monitor *mon, const QDict *qdict); diff --git a/monitor.c b/monitor.c index c0e32d6..85cf47e 100644 --- a/monitor.c +++ b/monitor.c @@ -2680,6 +2680,13 @@ static mon_cmd_t info_cmds[] = { }, #endif { + .name = "backup", + .args_type = "", + .params = "", + .help = "show backup status", + .mhandler.info = hmp_info_backup, + }, + { .name = "migrate", .args_type = "", .params = "", diff --git a/qapi-schema.json b/qapi-schema.json index 5dfa052..5536ddc 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -357,6 +357,13 @@ ## { 'type': 'EventInfo', 'data': {'name': 'str'} } + +{ 'type': 'BackupStatus', + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', + '*transferred': 'int', '*zero-bytes': 'int', + '*start-time': 'int', '*end-time': 'int', + '*backupfile': 'str', '*uuid': 'str' } } + ## # @query-events: # @@ -1764,6 +1771,45 @@ 'data': { 'path': 'str' }, 'returns': [ 'ObjectPropertyInfo' ] } + +## +# @backup: +# +# Starts a VM backup. +# +# @backupfile: the backup file name +# +# @speed: #optional the maximum speed, in bytes per second +# +# Returns: the uuid of the backup job +# +## +{ 'command': 'backup', 'data': { 'backupfile': 'str', '*devlist': 'str', + '*speed': 'int' }, + 'returns': 'str' } + +## +# @query-backup +# +# Returns information about current/last backup task. +# +# Returns: @BackupStatus +# +## +{ 'command': 'query-backup', 'returns': 'BackupStatus' } + +## +# @backup_cancel +# +# Cancel the current executing backup process. +# +# Returns: nothing on success +# +# Notes: This command succeeds even if there is no backup process running. +# +## +{ 'command': 'backup_cancel' } + ## # @qom-get: # diff --git a/qmp-commands.hx b/qmp-commands.hx index 5c692d0..e83c275 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -822,6 +822,18 @@ EQMP }, { + .name = "backup", + .args_type = "backupfile:s,speed:o?,devlist:s?", + .mhandler.cmd_new = qmp_marshal_input_backup, + }, + + { + .name = "backup_cancel", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_backup_cancel, + }, + + { .name = "block-job-set-speed", .args_type = "device:B,speed:o", .mhandler.cmd_new = qmp_marshal_input_block_job_set_speed, @@ -2491,6 +2503,21 @@ EQMP }, SQMP + +query-backup +------------- + +Backup status. + +EQMP + + { + .name = "query-backup", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_backup, + }, + +SQMP migrate-set-capabilities ------- -- 1.7.2.5