From patchwork Tue Feb 19 11:31:43 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dietmar Maurer X-Patchwork-Id: 221684 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 639FD2C008D for ; Tue, 19 Feb 2013 22:59:36 +1100 (EST) Received: from localhost ([::1]:51448 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U7lRE-0006v0-NR for incoming@patchwork.ozlabs.org; Tue, 19 Feb 2013 06:32:44 -0500 Received: from eggs.gnu.org ([208.118.235.92]:53896) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U7lQa-0006Gu-U3 for qemu-devel@nongnu.org; Tue, 19 Feb 2013 06:32:14 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1U7lQS-0005XY-GR for qemu-devel@nongnu.org; Tue, 19 Feb 2013 06:32:04 -0500 Received: from www.maurer-it.com ([213.129.239.114]:40572 helo=maui.maurer-it.com) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1U7lQR-0005X6-Uy for qemu-devel@nongnu.org; Tue, 19 Feb 2013 06:31:56 -0500 Received: by maui.maurer-it.com (Postfix, from userid 0) id 3451E3B08FD; Tue, 19 Feb 2013 12:31:45 +0100 (CET) From: Dietmar Maurer To: qemu-devel@nongnu.org Date: Tue, 19 Feb 2013 12:31:43 +0100 Message-Id: <1361273503-974882-6-git-send-email-dietmar@proxmox.com> X-Mailer: git-send-email 1.7.2.5 In-Reply-To: <1361273503-974882-1-git-send-email-dietmar@proxmox.com> References: <1361273503-974882-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: Dietmar Maurer Subject: [Qemu-devel] [PATCH v3 6/6] add vm state to backups 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 Signed-off-by: Dietmar Maurer --- blockdev.c | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++++- hmp.c | 3 +- qapi-schema.json | 6 +- 3 files changed, 200 insertions(+), 5 deletions(-) diff --git a/blockdev.c b/blockdev.c index 50e150d..2f99d08 100644 --- a/blockdev.c +++ b/blockdev.c @@ -22,6 +22,8 @@ #include "sysemu/arch_init.h" #include "backup.h" #include "vma.h" +#include "migration/qemu-file.h" +#include "migration/migration.h" static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives); @@ -1355,6 +1357,10 @@ static struct GenericBackupState { size_t total; size_t transferred; size_t zero_bytes; + unsigned char buf[BACKUP_CLUSTER_SIZE]; + int buf_index; + size_t buf_cluster_num; + guint8 vmstate_dev_id; } backup_state; typedef struct BackupCB { @@ -1510,10 +1516,170 @@ static void backup_start_jobs(void) backup_run_next_job(); } +static int backup_state_close(void *opaque) +{ + if (!backup_state.buf_index) { + return 0; + } + + size_t zero_bytes = 0; + if (backup_state.buf_index < BACKUP_CLUSTER_SIZE) { + memset(backup_state.buf + backup_state.buf_index, 0, + BACKUP_CLUSTER_SIZE - backup_state.buf_index); + } + int bytes = backup_state.driver->dump_cb( + backup_state.writer, backup_state.vmstate_dev_id, + backup_state.buf_cluster_num, + backup_state.buf, &zero_bytes); + backup_state.buf_index = 0; + + return bytes < 0 ? -1 : 0; +} + +static int backup_state_put_buffer(void *opaque, const uint8_t *buf, + int64_t pos, int size) +{ + assert(backup_state.driver); + assert(backup_state.writer); + assert(backup_state.driver->dump_cb); + + /* Note: our backup driver expects to get whole clusters (64KB) */ + + int ret = size; + + while (size > 0) { + int l = BACKUP_CLUSTER_SIZE - backup_state.buf_index; + l = l > size ? size : l; + memcpy(backup_state.buf + backup_state.buf_index, buf, l); + backup_state.buf_index += l; + buf += l; + size -= l; + if (backup_state.buf_index == BACKUP_CLUSTER_SIZE) { + size_t zero_bytes = 0; + int bytes = backup_state.driver->dump_cb( + backup_state.writer, backup_state.vmstate_dev_id, + backup_state.buf_cluster_num++, + backup_state.buf, &zero_bytes); + backup_state.buf_index = 0; + if (bytes < 0) { + return -1; + } + } + } + + return ret; +} + +static const QEMUFileOps backup_file_ops = { + .put_buffer = backup_state_put_buffer, + .close = backup_state_close, +}; + +static void coroutine_fn backup_start_savevm(void *opaque) +{ + assert(backup_state.driver); + assert(backup_state.writer); + int ret; + char *err = NULL; + uint64_t remaining; + int64_t maxlen; + MigrationParams params = { + .blk = 0, + .shared = 0 + }; + + int restart = 0; + + QEMUFile *file = qemu_fopen_ops(NULL, &backup_file_ops); + + ret = qemu_savevm_state_begin(file, ¶ms); + if (ret < 0) { + qemu_fclose(file); + err = g_strdup("qemu_savevm_state_begin failed"); + goto abort; + } + + while (1) { + ret = qemu_savevm_state_iterate(file); + remaining = ram_bytes_remaining(); + + if (ret < 0) { + qemu_fclose(file); + err = g_strdup_printf("qemu_savevm_state_iterate error %d", ret); + goto abort; + } + + /* stop the VM if we use too much space, + * or if remaining is just a few MB + */ + maxlen = ram_bytes_total(); + size_t cpos = backup_state.buf_cluster_num * BACKUP_CLUSTER_SIZE; + if ((remaining < 100000) || ((cpos + remaining) >= maxlen)) { + if (runstate_is_running()) { + restart = 1; + vm_stop(RUN_STATE_SAVE_VM); + } + } + + if (ret == 1) { /* finished */ + if (runstate_is_running()) { + restart = 1; + vm_stop(RUN_STATE_SAVE_VM); + } + + ret = qemu_savevm_state_complete(file); + if (ret < 0) { + qemu_fclose(file); + err = g_strdup("qemu_savevm_state_complete error"); + goto abort; + + } else { + if (qemu_fclose(file) < 0) { + error_setg(&backup_state.error, + "backup_start_savevm: qemu_fclose failed"); + goto abort; + } + if (backup_state.driver->complete_cb(backup_state.writer, + backup_state.vmstate_dev_id, 0) < 0) { + err = g_strdup("backup_start_savevm: complete_cb failed"); + goto abort; + } + backup_run_next_job(); + goto out; + } + } + } + +out: + if (restart) { + vm_start(); + } + return; + +abort: + backup_state.end_time = time(NULL); + + Error *local_err = NULL; + backup_state.driver->close_cb(backup_state.writer, &local_err); + backup_state.writer = NULL; + + error_propagate(&backup_state.error, local_err); + + if (err) { + if (!backup_state.error) { + error_setg(&backup_state.error, "%s", err); + } + g_free(err); + } + + goto out; +} + char *qmp_backup(const char *backupfile, bool has_format, BackupFormat format, bool has_config_filename, const char *config_filename, bool has_devlist, const char *devlist, - bool has_speed, int64_t speed, Error **errp) + bool has_speed, int64_t speed, + bool has_state, bool state, Error **errp) { BlockDriverState *bs; Error *local_err = NULL; @@ -1528,6 +1694,8 @@ char *qmp_backup(const char *backupfile, bool has_format, BackupFormat format, return NULL; } + bool save_state = has_state ? state : false; + /* Todo: try to auto-detect format based on file name */ format = has_format ? format : BACKUP_FORMAT_VMA; @@ -1608,6 +1776,22 @@ char *qmp_backup(const char *backupfile, bool has_format, BackupFormat format, size_t total = 0; /* register all devices for vma writer */ + + guint8 vmstate_dev_id = 0; + if (save_state) { + /* Note: we pass ram_bytes_total() for vmstate size + * The backup driver needs to be aware of the fact + * that the real stream size can be different (we do + * not know that size in advance). + */ + size_t ramsize = ram_bytes_total(); + vmstate_dev_id = driver->register_stream_cb(writer, "vmstate", ramsize); + if (vmstate_dev_id <= 0) { + error_setg(errp, "register vmstate stream failed"); + goto err; + } + } + l = bcblist; while (l) { BackupCB *bcb = l->data; @@ -1675,6 +1859,9 @@ char *qmp_backup(const char *backupfile, bool has_format, BackupFormat format, backup_state.total = total; backup_state.transferred = 0; backup_state.zero_bytes = 0; + backup_state.buf_index = 0; + backup_state.buf_cluster_num = 0; + backup_state.vmstate_dev_id = vmstate_dev_id; /* Grab a reference so hotplug does not delete the * BlockDriverState from underneath us. @@ -1686,7 +1873,12 @@ char *qmp_backup(const char *backupfile, bool has_format, BackupFormat format, drive_get_ref(drive_get_by_blockdev(bcb->bs)); } - backup_start_jobs(); + if (save_state) { + Coroutine *co = qemu_coroutine_create(backup_start_savevm); + qemu_coroutine_enter(co, NULL); + } else { + backup_start_jobs(); + } return g_strdup(backup_state.uuid_str); diff --git a/hmp.c b/hmp.c index 9ac34c5..7b401cc 100644 --- a/hmp.c +++ b/hmp.c @@ -1052,7 +1052,8 @@ void hmp_backup(Monitor *mon, const QDict *qdict) Error *errp = NULL; qmp_backup(backupfile, true, BACKUP_FORMAT_VMA, false, NULL, !!devlist, - devlist, qdict_haskey(qdict, "speed"), speed, &errp); + devlist, qdict_haskey(qdict, "speed"), speed, false, false, + &errp); if (error_is_set(&errp)) { monitor_printf(mon, "%s\n", error_get_pretty(errp)); diff --git a/qapi-schema.json b/qapi-schema.json index efcbf09..11df3de 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -1882,13 +1882,15 @@ # # @speed: #optional the maximum speed, in bytes per second # +# @state: #optional flag to include vm state +# # Returns: the uuid of the backup job # # Since: 1.5.0 ## { 'command': 'backup', 'data': { 'backupfile': 'str', '*format': 'BackupFormat', - '*config-filename': 'str', - '*devlist': 'str', '*speed': 'int' }, + '*config-filename': 'str', '*devlist': 'str', + '*speed': 'int', '*state': 'bool' }, 'returns': 'str' } ##