Patchwork [4/5] add backup related monitor commands

login
register
mail settings
Submitter Dietmar Maurer
Date Nov. 21, 2012, 9:01 a.m.
Message ID <1353488464-82756-4-git-send-email-dietmar@proxmox.com>
Download mbox | patch
Permalink /patch/200610/
State New
Headers show

Comments

Dietmar Maurer - Nov. 21, 2012, 9:01 a.m.
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 <dietmar@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(-)
Eric Blake - Nov. 21, 2012, 4:16 p.m.
On 11/21/2012 02:01 AM, Dietmar Maurer wrote:
> 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.
> 

> +++ 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' } }
> +

Missing documentation for what all these fields mean.

>  ##
>  # @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
> +#

Needs to mention when it was introduced (at best, it will be since 1.4).

> +##
> +{ '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

s/_/-/, you should prefer dashes in QMP

> +#
> +# 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' }
> +

You are basically adding a new asynchronous job.  Do you really need to
add a 'backup-cancel' command, or should we be reusing a generic
framework for canceling arbitrary jobs?
Dietmar Maurer - Nov. 21, 2012, 5:59 p.m.
> > +{ 'type': 'BackupStatus',

> > +  'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',

> > +           '*transferred': 'int', '*zero-bytes': 'int',

> > +           '*start-time': 'int', '*end-time': 'int',

> > +           '*backupfile': 'str', '*uuid': 'str' } }

> > +

> 

> Missing documentation for what all these fields mean.


Yes, sorry for that. I just wanted to get an early feedback from the community
before I work out the details.
Dietmar Maurer - Nov. 21, 2012, 6:49 p.m.
> > +{ 'command': 'backup_cancel' }

> > +

> 

> You are basically adding a new asynchronous job.  Do you really need to add

> a 'backup-cancel' command, or should we be reusing a generic framework

> for canceling arbitrary jobs?


No, we basically add several asynchronous job - one for each blockdev. And we want
to cancel them all together.

Patch

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
 -------