5b15e2ecaf
Notable changes: * The only big change is the switch to using a custom QIOChannel for savevm-async, because the previously used QEMUFileOps was dropped. Changes to the current implementation: * Switch to vector based methods as required for an IO channel. For short reads the passed-in IO vector is stuffed with zeroes at the end, just to be sure. * For reading: The documentation in include/io/channel.h states that at least one byte should be read, so also error out when whe are at the very end instead of returning 0. * For reading: Fix off-by-one error when request goes beyond end. The wrong code piece was: if ((pos + size) > maxlen) { size = maxlen - pos - 1; } Previously, the last byte would not be read. It's actually possible to get a snapshot .raw file that has content all the way up the final 512 byte (= BDRV_SECTOR_SIZE) boundary without any trailing zero bytes (I wrote a script to do it). Luckily, it didn't cause a real issue, because qemu_loadvm_state() is not interested in the final (i.e. QEMU_VM_VMDESCRIPTION) section. The buffer for reading it is simply freed up afterwards and the function will assume that it read the whole section, even if that's not the case. * For writing: Make use of the generated blk_pwritev() wrapper instead of manually wrapping the coroutine to simplify and save a few lines. * Adapt to changed interfaces for blk_{pread,pwrite}: * a9262f551e ("block: Change blk_{pread,pwrite}() param order") * 3b35d4542c ("block: Add a 'flags' param to blk_pread()") * bf5b16fa40 ("block: Make blk_{pread,pwrite}() return 0 on success") Those changes especially affected the qemu-img dd patches, because the context also changed, but also some of our block drivers used the functions. * Drop qemu-common.h include: it got renamed after essentially everything was moved to other headers. The only remaining user I could find for things dropped from the header between 7.0 and 7.1 was qemu_get_vm_name() in the iscsi-initiatorname patch, but it already includes the header to which the function was moved. Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
442 lines
16 KiB
Diff
442 lines
16 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Stefan Reiter <s.reiter@proxmox.com>
|
|
Date: Wed, 19 Aug 2020 17:02:00 +0200
|
|
Subject: [PATCH] PVE: add query-pbs-bitmap-info QMP call
|
|
|
|
Returns advanced information about dirty bitmaps used (or not used) for
|
|
the latest PBS backup.
|
|
|
|
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
|
|
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
|
|
---
|
|
monitor/hmp-cmds.c | 28 ++++++-----
|
|
pve-backup.c | 117 ++++++++++++++++++++++++++++++++-----------
|
|
qapi/block-core.json | 56 +++++++++++++++++++++
|
|
3 files changed, 159 insertions(+), 42 deletions(-)
|
|
|
|
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
|
|
index 4c1671e289..c1152f55a7 100644
|
|
--- a/monitor/hmp-cmds.c
|
|
+++ b/monitor/hmp-cmds.c
|
|
@@ -200,6 +200,7 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
|
|
void hmp_info_backup(Monitor *mon, const QDict *qdict)
|
|
{
|
|
BackupStatus *info;
|
|
+ PBSBitmapInfoList *bitmap_info;
|
|
|
|
info = qmp_query_backup(NULL);
|
|
|
|
@@ -230,26 +231,29 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
|
|
// this should not happen normally
|
|
monitor_printf(mon, "Total size: %d\n", 0);
|
|
} else {
|
|
- bool incremental = false;
|
|
size_t total_or_dirty = info->total;
|
|
- if (info->has_transferred) {
|
|
- if (info->has_dirty && info->dirty) {
|
|
- if (info->dirty < info->total) {
|
|
- total_or_dirty = info->dirty;
|
|
- incremental = true;
|
|
- }
|
|
- }
|
|
+ bitmap_info = qmp_query_pbs_bitmap_info(NULL);
|
|
+
|
|
+ while (bitmap_info) {
|
|
+ monitor_printf(mon, "Drive %s:\n",
|
|
+ bitmap_info->value->drive);
|
|
+ monitor_printf(mon, " bitmap action: %s\n",
|
|
+ PBSBitmapAction_str(bitmap_info->value->action));
|
|
+ monitor_printf(mon, " size: %zd\n",
|
|
+ bitmap_info->value->size);
|
|
+ monitor_printf(mon, " dirty: %zd\n",
|
|
+ bitmap_info->value->dirty);
|
|
+ bitmap_info = bitmap_info->next;
|
|
}
|
|
|
|
- int per = (info->transferred * 100)/total_or_dirty;
|
|
-
|
|
- monitor_printf(mon, "Backup mode: %s\n", incremental ? "incremental" : "full");
|
|
+ qapi_free_PBSBitmapInfoList(bitmap_info);
|
|
|
|
int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
|
|
(info->zero_bytes * 100)/info->total : 0;
|
|
monitor_printf(mon, "Total size: %zd\n", info->total);
|
|
+ int trans_per = (info->transferred * 100)/total_or_dirty;
|
|
monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
|
|
- info->transferred, per);
|
|
+ info->transferred, trans_per);
|
|
monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
|
|
info->zero_bytes, zero_per);
|
|
|
|
diff --git a/pve-backup.c b/pve-backup.c
|
|
index 4684789813..f90abaa50a 100644
|
|
--- a/pve-backup.c
|
|
+++ b/pve-backup.c
|
|
@@ -46,6 +46,7 @@ static struct PVEBackupState {
|
|
size_t transferred;
|
|
size_t reused;
|
|
size_t zero_bytes;
|
|
+ GList *bitmap_list;
|
|
} stat;
|
|
int64_t speed;
|
|
VmaWriter *vmaw;
|
|
@@ -672,7 +673,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
}
|
|
|
|
size_t total = 0;
|
|
- size_t dirty = 0;
|
|
|
|
l = di_list;
|
|
while (l) {
|
|
@@ -693,18 +693,33 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
|
|
uuid_generate(uuid);
|
|
|
|
+ qemu_mutex_lock(&backup_state.stat.lock);
|
|
+ backup_state.stat.reused = 0;
|
|
+
|
|
+ /* clear previous backup's bitmap_list */
|
|
+ if (backup_state.stat.bitmap_list) {
|
|
+ GList *bl = backup_state.stat.bitmap_list;
|
|
+ while (bl) {
|
|
+ g_free(((PBSBitmapInfo *)bl->data)->drive);
|
|
+ g_free(bl->data);
|
|
+ bl = g_list_next(bl);
|
|
+ }
|
|
+ g_list_free(backup_state.stat.bitmap_list);
|
|
+ backup_state.stat.bitmap_list = NULL;
|
|
+ }
|
|
+
|
|
if (format == BACKUP_FORMAT_PBS) {
|
|
if (!task->has_password) {
|
|
error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
if (!task->has_backup_id) {
|
|
error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
if (!task->has_backup_time) {
|
|
error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
|
|
@@ -731,12 +746,12 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
|
|
"proxmox_backup_new failed: %s", pbs_err);
|
|
proxmox_backup_free_error(pbs_err);
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
int connect_result = proxmox_backup_co_connect(pbs, task->errp);
|
|
if (connect_result < 0)
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
|
|
/* register all devices */
|
|
l = di_list;
|
|
@@ -747,6 +762,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
di->block_size = dump_cb_block_size;
|
|
|
|
const char *devname = bdrv_get_device_name(di->bs);
|
|
+ PBSBitmapAction action = PBS_BITMAP_ACTION_NOT_USED;
|
|
+ size_t dirty = di->size;
|
|
|
|
BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
|
|
bool expect_only_dirty = false;
|
|
@@ -755,49 +772,59 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
if (bitmap == NULL) {
|
|
bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
|
|
if (!bitmap) {
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
+ action = PBS_BITMAP_ACTION_NEW;
|
|
} else {
|
|
expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
|
|
}
|
|
|
|
if (expect_only_dirty) {
|
|
- dirty += bdrv_get_dirty_count(bitmap);
|
|
+ /* track clean chunks as reused */
|
|
+ dirty = MIN(bdrv_get_dirty_count(bitmap), di->size);
|
|
+ backup_state.stat.reused += di->size - dirty;
|
|
+ action = PBS_BITMAP_ACTION_USED;
|
|
} else {
|
|
/* mark entire bitmap as dirty to make full backup */
|
|
bdrv_set_dirty_bitmap(bitmap, 0, di->size);
|
|
- dirty += di->size;
|
|
+ if (action != PBS_BITMAP_ACTION_NEW) {
|
|
+ action = PBS_BITMAP_ACTION_INVALID;
|
|
+ }
|
|
}
|
|
di->bitmap = bitmap;
|
|
} else {
|
|
- dirty += di->size;
|
|
-
|
|
/* after a full backup the old dirty bitmap is invalid anyway */
|
|
if (bitmap != NULL) {
|
|
bdrv_release_dirty_bitmap(bitmap);
|
|
+ action = PBS_BITMAP_ACTION_NOT_USED_REMOVED;
|
|
}
|
|
}
|
|
|
|
int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
|
|
if (dev_id < 0) {
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
di->dev_id = dev_id;
|
|
+
|
|
+ PBSBitmapInfo *info = g_malloc(sizeof(*info));
|
|
+ info->drive = g_strdup(devname);
|
|
+ info->action = action;
|
|
+ info->size = di->size;
|
|
+ info->dirty = dirty;
|
|
+ backup_state.stat.bitmap_list = g_list_append(backup_state.stat.bitmap_list, info);
|
|
}
|
|
} else if (format == BACKUP_FORMAT_VMA) {
|
|
- dirty = total;
|
|
-
|
|
vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
|
|
if (!vmaw) {
|
|
if (local_err) {
|
|
error_propagate(task->errp, local_err);
|
|
}
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
/* register all devices for vma writer */
|
|
@@ -807,7 +834,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
l = g_list_next(l);
|
|
|
|
if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
const char *devname = bdrv_get_device_name(di->bs);
|
|
@@ -815,16 +842,14 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
if (di->dev_id <= 0) {
|
|
error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
|
|
"register_stream failed");
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
}
|
|
} else if (format == BACKUP_FORMAT_DIR) {
|
|
- dirty = total;
|
|
-
|
|
if (mkdir(task->backup_file, 0640) != 0) {
|
|
error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
|
|
task->backup_file);
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
backup_dir = task->backup_file;
|
|
|
|
@@ -841,18 +866,18 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
di->size, flags, false, &local_err);
|
|
if (local_err) {
|
|
error_propagate(task->errp, local_err);
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
|
|
if (!di->target) {
|
|
error_propagate(task->errp, local_err);
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
}
|
|
} else {
|
|
error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
|
|
|
|
@@ -860,7 +885,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
if (task->has_config_file) {
|
|
if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
|
|
vmaw, pbs, task->errp) != 0) {
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
}
|
|
|
|
@@ -868,12 +893,11 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
if (task->has_firewall_file) {
|
|
if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
|
|
vmaw, pbs, task->errp) != 0) {
|
|
- goto err;
|
|
+ goto err_mutex;
|
|
}
|
|
}
|
|
/* initialize global backup_state now */
|
|
-
|
|
- qemu_mutex_lock(&backup_state.stat.lock);
|
|
+ /* note: 'reused' and 'bitmap_list' are initialized earlier */
|
|
|
|
if (backup_state.stat.error) {
|
|
error_free(backup_state.stat.error);
|
|
@@ -893,10 +917,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
char *uuid_str = g_strdup(backup_state.stat.uuid_str);
|
|
|
|
backup_state.stat.total = total;
|
|
- backup_state.stat.dirty = dirty;
|
|
+ backup_state.stat.dirty = total - backup_state.stat.reused;
|
|
backup_state.stat.transferred = 0;
|
|
backup_state.stat.zero_bytes = 0;
|
|
- backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
|
|
|
|
qemu_mutex_unlock(&backup_state.stat.lock);
|
|
|
|
@@ -913,6 +936,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
task->result = uuid_info;
|
|
return;
|
|
|
|
+err_mutex:
|
|
+ qemu_mutex_unlock(&backup_state.stat.lock);
|
|
+
|
|
err:
|
|
|
|
l = di_list;
|
|
@@ -1076,11 +1102,42 @@ BackupStatus *qmp_query_backup(Error **errp)
|
|
return info;
|
|
}
|
|
|
|
+PBSBitmapInfoList *qmp_query_pbs_bitmap_info(Error **errp)
|
|
+{
|
|
+ PBSBitmapInfoList *head = NULL, **p_next = &head;
|
|
+
|
|
+ qemu_mutex_lock(&backup_state.stat.lock);
|
|
+
|
|
+ GList *l = backup_state.stat.bitmap_list;
|
|
+ while (l) {
|
|
+ PBSBitmapInfo *info = (PBSBitmapInfo *)l->data;
|
|
+ l = g_list_next(l);
|
|
+
|
|
+ /* clone bitmap info to avoid auto free after QMP marshalling */
|
|
+ PBSBitmapInfo *info_ret = g_malloc0(sizeof(*info_ret));
|
|
+ info_ret->drive = g_strdup(info->drive);
|
|
+ info_ret->action = info->action;
|
|
+ info_ret->size = info->size;
|
|
+ info_ret->dirty = info->dirty;
|
|
+
|
|
+ PBSBitmapInfoList *info_list = g_malloc0(sizeof(*info_list));
|
|
+ info_list->value = info_ret;
|
|
+
|
|
+ *p_next = info_list;
|
|
+ p_next = &info_list->next;
|
|
+ }
|
|
+
|
|
+ qemu_mutex_unlock(&backup_state.stat.lock);
|
|
+
|
|
+ return head;
|
|
+}
|
|
+
|
|
ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
|
|
{
|
|
ProxmoxSupportStatus *ret = g_malloc0(sizeof(*ret));
|
|
ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
|
|
ret->pbs_dirty_bitmap = true;
|
|
ret->pbs_dirty_bitmap_savevm = true;
|
|
+ ret->query_bitmap_info = true;
|
|
return ret;
|
|
}
|
|
diff --git a/qapi/block-core.json b/qapi/block-core.json
|
|
index 0b453c61d4..16e184dd28 100644
|
|
--- a/qapi/block-core.json
|
|
+++ b/qapi/block-core.json
|
|
@@ -871,6 +871,8 @@
|
|
# @pbs-dirty-bitmap: True if dirty-bitmap-incremental backups to PBS are
|
|
# supported.
|
|
#
|
|
+# @query-bitmap-info: True if the 'query-pbs-bitmap-info' QMP call is supported.
|
|
+#
|
|
# @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
|
|
# safely be set for savevm-async.
|
|
#
|
|
@@ -879,6 +881,7 @@
|
|
##
|
|
{ 'struct': 'ProxmoxSupportStatus',
|
|
'data': { 'pbs-dirty-bitmap': 'bool',
|
|
+ 'query-bitmap-info': 'bool',
|
|
'pbs-dirty-bitmap-savevm': 'bool',
|
|
'pbs-library-version': 'str' } }
|
|
|
|
@@ -892,6 +895,59 @@
|
|
##
|
|
{ 'command': 'query-proxmox-support', 'returns': 'ProxmoxSupportStatus' }
|
|
|
|
+##
|
|
+# @PBSBitmapAction:
|
|
+#
|
|
+# An action taken on a dirty-bitmap when a backup job was started.
|
|
+#
|
|
+# @not-used: Bitmap mode was not enabled.
|
|
+#
|
|
+# @not-used-removed: Bitmap mode was not enabled, but a bitmap from a
|
|
+# previous backup still existed and was removed.
|
|
+#
|
|
+# @new: A new bitmap was attached to the drive for this backup.
|
|
+#
|
|
+# @used: An existing bitmap will be used to only backup changed data.
|
|
+#
|
|
+# @invalid: A bitmap existed, but had to be cleared since it's associated
|
|
+# base snapshot did not match the base given for the current job or
|
|
+# the crypt mode has changed.
|
|
+#
|
|
+##
|
|
+{ 'enum': 'PBSBitmapAction',
|
|
+ 'data': ['not-used', 'not-used-removed', 'new', 'used', 'invalid'] }
|
|
+
|
|
+##
|
|
+# @PBSBitmapInfo:
|
|
+#
|
|
+# Contains information about dirty bitmaps used for each drive in a PBS backup.
|
|
+#
|
|
+# @drive: The underlying drive.
|
|
+#
|
|
+# @action: The action that was taken when the backup started.
|
|
+#
|
|
+# @size: The total size of the drive.
|
|
+#
|
|
+# @dirty: How much of the drive is considered dirty and will be backed up,
|
|
+# or 'size' if everything will be.
|
|
+#
|
|
+##
|
|
+{ 'struct': 'PBSBitmapInfo',
|
|
+ 'data': { 'drive': 'str', 'action': 'PBSBitmapAction', 'size': 'int',
|
|
+ 'dirty': 'int' } }
|
|
+
|
|
+##
|
|
+# @query-pbs-bitmap-info:
|
|
+#
|
|
+# Returns information about dirty bitmaps used on the most recently started
|
|
+# backup. Returns nothing when the last backup was not using PBS or if no
|
|
+# backup occured in this session.
|
|
+#
|
|
+# Returns: @PBSBitmapInfo
|
|
+#
|
|
+##
|
|
+{ 'command': 'query-pbs-bitmap-info', 'returns': ['PBSBitmapInfo'] }
|
|
+
|
|
##
|
|
# @BlockDeviceTimedStats:
|
|
#
|