[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