From patchwork Wed Nov 21 09:01:03 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dietmar Maurer X-Patchwork-Id: 200610 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 99C562C008F for ; Wed, 21 Nov 2012 20:01:48 +1100 (EST) Received: from localhost ([::1]:52498 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tb6Bm-0005oB-Ku for incoming@patchwork.ozlabs.org; Wed, 21 Nov 2012 04:01:46 -0500 Received: from eggs.gnu.org ([208.118.235.92]:59565) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tb6Bc-0005nz-8N for qemu-devel@nongnu.org; Wed, 21 Nov 2012 04:01:38 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Tb6BR-0000Xx-H3 for qemu-devel@nongnu.org; Wed, 21 Nov 2012 04:01:36 -0500 Received: from www.maurer-it.com ([213.129.239.114]:46864 helo=maui.maurer-it.com) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tb6BR-0000W5-14 for qemu-devel@nongnu.org; Wed, 21 Nov 2012 04:01:25 -0500 Received: by maui.maurer-it.com (Postfix, from userid 0) id 9CD6D3B0BBF; Wed, 21 Nov 2012 10:01:15 +0100 (CET) From: Dietmar Maurer To: qemu-devel@nongnu.org Date: Wed, 21 Nov 2012 10:01:03 +0100 Message-Id: <1353488464-82756-4-git-send-email-dietmar@proxmox.com> X-Mailer: git-send-email 1.7.2.5 In-Reply-To: <1353488464-82756-1-git-send-email-dietmar@proxmox.com> References: <1353488464-82756-1-git-send-email-dietmar@proxmox.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 213.129.239.114 Cc: kwolf@redhat.com, Dietmar Maurer Subject: [Qemu-devel] [PATCH 4/5] add backup related monitor commands X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org We currently create 'vma' archives without any configuration inside. Future versions may support other formats... Another option would be to simply dump 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 --- 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 -------