Patchwork [v3,6/6] add vm state to backups

login
register
mail settings
Submitter Dietmar Maurer
Date Feb. 19, 2013, 11:31 a.m.
Message ID <1361273503-974882-6-git-send-email-dietmar@proxmox.com>
Download mbox | patch
Permalink /patch/221684/
State New
Headers show

Comments

Dietmar Maurer - Feb. 19, 2013, 11:31 a.m.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
 blockdev.c       |  196 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 hmp.c            |    3 +-
 qapi-schema.json |    6 +-
 3 files changed, 200 insertions(+), 5 deletions(-)

Patch

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, &params);
+    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' }
 
 ##