[pve-devel] [PATCH 4/5] add backup related monitor commands

Dietmar Maurer dietmar at proxmox.com
Mon Nov 19 12:29:31 CET 2012


We currently create 'vma' archives without any configuration inside.
Future versions may support other formats...

Signed-off-by: Dietmar Maurer <dietmar at proxmox.com>
---
 Makefile         |    2 +-
 Makefile.objs    |    2 +-
 blockdev.c       |  259 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hmp-commands.hx  |   31 +++++++
 hmp.c            |   59 ++++++++++++
 hmp.h            |    3 +
 monitor.c        |    7 ++
 qapi-schema.json |   46 ++++++++++
 qmp-commands.hx  |   27 ++++++
 9 files changed, 434 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index f69789f..2f22879 100644
--- a/Makefile
+++ b/Makefile
@@ -186,7 +186,7 @@ tools-obj-$(CONFIG_POSIX) += compatfd.o
 qemu-img$(EXESUF): qemu-img.o $(tools-obj-y) $(block-obj-y)
 qemu-nbd$(EXESUF): qemu-nbd.o $(tools-obj-y) $(block-obj-y)
 qemu-io$(EXESUF): qemu-io.o cmd.o $(tools-obj-y) $(block-obj-y)
-vma$(EXESUF): vma.o vma-writer.o vma-reader.o $(tools-obj-y) $(block-obj-y)
+vma$(EXESUF): vma.o vma-reader.o $(tools-obj-y) $(block-obj-y)
 
 qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o
 
diff --git a/Makefile.objs b/Makefile.objs
index 1a21445..910d407 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -44,7 +44,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..73ae925 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,264 @@ 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) {
+            if ((bs = bdrv_find(*d))) {
+                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;
+        }
+    }
+
+    if (!(vmaw = vma_writer_create(backupfile, speed, &local_err))) {
+        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 b74ef75..2891dcc 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
+ at item backup
+ at 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
+ at item backup_cancel
+ at 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
+ at 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..31927d8 100644
--- a/hmp.c
+++ b/hmp.c
@@ -130,6 +130,34 @@ 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 +1005,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 542e3ac..259ca89 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




More information about the pve-devel mailing list