d03e1b3ce3
User-facing breaking change: The slirp submodule for user networking got removed. It would be necessary to add the --enable-slirp option to the build and/or install the appropriate library to continue building it. Since PVE is not explicitly supporting it, it would require additionally installing the libslirp0 package on all installations and there is *very* little mention on the community forum when searching for "slirp" or "netdev user", the plan is to only enable it again if there is some real demand for it. Notable changes: * The big change for this release is the rework of job locking, using a job mutex and introducing _locked() variants of job API functions moving away from call-side AioContext locking. See (in the qemu submodule) commit 6f592e5aca ("job.c: enable job lock/unlock and remove Aiocontext locks") and previous commits for context. Changes required for the backup patches: * Use WITH_JOB_LOCK_GUARD() and call the _locked() variant of job API functions where appropriate (many are only availalbe as a _locked() variant). * Remove acquiring/releasing AioContext around functions taking the job mutex lock internally. The patch introducing sequential transaction support for jobs needs to temporarily unlock the job mutex to call job_start() when starting the next job in the transaction. * The zeroinit block driver now marks its child as primary. The documentation in include/block/block-common.h states: > Filter node has exactly one FILTERED|PRIMARY child, and may have > other children which must not have these bits Without this, an assert will trigger when copying to a zeroinit target with qemu-img convert, because bdrv_child_cb_attach() expects any non-PRIMARY child to be not FILTERED: > qemu-img convert -n -p -f raw -O raw input.raw zeroinit:output.raw > qemu-img: ../block.c:1476: bdrv_child_cb_attach: Assertion > `!(child->role & BDRV_CHILD_FILTERED)' failed. 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 670f783515..d819e5fc36 100644
|
|
--- a/monitor/hmp-cmds.c
|
|
+++ b/monitor/hmp-cmds.c
|
|
@@ -202,6 +202,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);
|
|
|
|
@@ -232,26 +233,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 9318ca4f0c..c85b2ecd83 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;
|
|
@@ -669,7 +670,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
|
|
}
|
|
|
|
size_t total = 0;
|
|
- size_t dirty = 0;
|
|
|
|
l = di_list;
|
|
while (l) {
|
|
@@ -690,18 +690,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)
|
|
@@ -728,12 +743,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;
|
|
@@ -744,6 +759,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;
|
|
@@ -752,49 +769,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 */
|
|
@@ -804,7 +831,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);
|
|
@@ -812,16 +839,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;
|
|
|
|
@@ -838,18 +863,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;
|
|
}
|
|
|
|
|
|
@@ -857,7 +882,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;
|
|
}
|
|
}
|
|
|
|
@@ -865,12 +890,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);
|
|
@@ -890,10 +914,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);
|
|
|
|
@@ -910,6 +933,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;
|
|
@@ -1073,11 +1099,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 8b0e0d92de..7fde927621 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:
|
|
#
|