]> git.proxmox.com Git - pve-qemu.git/blame - debian/patches/pve/0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
PVE backup: don't call no_co_wrapper function from coroutine
[pve-qemu.git] / debian / patches / pve / 0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
CommitLineData
c96a4a38
DM
1From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2From: Stefan Reiter <s.reiter@proxmox.com>
3Date: Mon, 29 Jun 2020 11:06:03 +0200
4Subject: [PATCH] PVE-Backup: Add dirty-bitmap tracking for incremental backups
5
6Uses QEMU's existing MIRROR_SYNC_MODE_BITMAP and a dirty-bitmap on top
7of all backed-up drives. This will only execute the data-write callback
8for any changed chunks, the PBS rust code will reuse chunks from the
9previous index for everything it doesn't receive if reuse_index is true.
10
11On error or cancellation, remove all dirty bitmaps to ensure
12consistency.
13
20be7fa0
TL
14Add PBS/incremental specific information to query backup info QMP and
15HMP commands.
16
c96a4a38
DM
17Only supported for PBS backups.
18
19Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
20Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
20be7fa0 21Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
c96a4a38 22---
0c893fd8
SR
23 block/monitor/block-hmp-cmds.c | 1 +
24 monitor/hmp-cmds.c | 45 ++++++++++----
25 proxmox-backup-client.c | 3 +-
26 proxmox-backup-client.h | 1 +
bf251437 27 pve-backup.c | 104 ++++++++++++++++++++++++++++++---
0c893fd8 28 qapi/block-core.json | 12 +++-
bf251437 29 6 files changed, 143 insertions(+), 23 deletions(-)
c96a4a38
DM
30
31diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
53b56ca7 32index d50e99df26..cda5de792b 100644
c96a4a38
DM
33--- a/block/monitor/block-hmp-cmds.c
34+++ b/block/monitor/block-hmp-cmds.c
53b56ca7 35@@ -1056,6 +1056,7 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
bf251437
FE
36 NULL, // PBS fingerprint
37 NULL, // PBS backup-id
c96a4a38
DM
38 false, 0, // PBS backup-time
39+ false, false, // PBS incremental
40 true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
bf251437 41 NULL, NULL,
c96a4a38 42 devlist, qdict_haskey(qdict, "speed"), speed, &error);
20be7fa0 43diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
bf251437 44index 9e1bd57aeb..087161a967 100644
20be7fa0
TL
45--- a/monitor/hmp-cmds.c
46+++ b/monitor/hmp-cmds.c
bf251437 47@@ -171,19 +171,42 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
20be7fa0
TL
48 monitor_printf(mon, "End time: %s", ctime(&info->end_time));
49 }
50
51- int per = (info->has_total && info->total &&
52- info->has_transferred && info->transferred) ?
53- (info->transferred * 100)/info->total : 0;
54- int zero_per = (info->has_total && info->total &&
55- info->has_zero_bytes && info->zero_bytes) ?
56- (info->zero_bytes * 100)/info->total : 0;
57 monitor_printf(mon, "Backup file: %s\n", info->backup_file);
58 monitor_printf(mon, "Backup uuid: %s\n", info->uuid);
59- monitor_printf(mon, "Total size: %zd\n", info->total);
60- monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
61- info->transferred, per);
62- monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
63- info->zero_bytes, zero_per);
64+
65+ if (!(info->has_total && info->total)) {
66+ // this should not happen normally
67+ monitor_printf(mon, "Total size: %d\n", 0);
68+ } else {
69+ bool incremental = false;
70+ size_t total_or_dirty = info->total;
71+ if (info->has_transferred) {
72+ if (info->has_dirty && info->dirty) {
73+ if (info->dirty < info->total) {
74+ total_or_dirty = info->dirty;
75+ incremental = true;
76+ }
77+ }
78+ }
79+
80+ int per = (info->transferred * 100)/total_or_dirty;
81+
82+ monitor_printf(mon, "Backup mode: %s\n", incremental ? "incremental" : "full");
83+
84+ int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
85+ (info->zero_bytes * 100)/info->total : 0;
86+ monitor_printf(mon, "Total size: %zd\n", info->total);
87+ monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
88+ info->transferred, per);
89+ monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
90+ info->zero_bytes, zero_per);
91+
92+ if (info->has_reused) {
93+ int reused_per = (info->reused * 100)/total_or_dirty;
94+ monitor_printf(mon, "Reused bytes: %zd (%d%%)\n",
95+ info->reused, reused_per);
96+ }
97+ }
98 }
99
100 qapi_free_BackupStatus(info);
c96a4a38 101diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
72ae34ec 102index a8f6653a81..4ce7bc0b5e 100644
c96a4a38
DM
103--- a/proxmox-backup-client.c
104+++ b/proxmox-backup-client.c
72ae34ec 105@@ -89,6 +89,7 @@ proxmox_backup_co_register_image(
c96a4a38
DM
106 ProxmoxBackupHandle *pbs,
107 const char *device_name,
108 uint64_t size,
109+ bool incremental,
110 Error **errp)
111 {
112 Coroutine *co = qemu_coroutine_self();
72ae34ec 113@@ -98,7 +99,7 @@ proxmox_backup_co_register_image(
c96a4a38
DM
114 int pbs_res = -1;
115
116 proxmox_backup_register_image_async(
117- pbs, device_name, size ,proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
118+ pbs, device_name, size, incremental, proxmox_backup_schedule_wake, &waker, &pbs_res, &pbs_err);
119 qemu_coroutine_yield();
120 if (pbs_res < 0) {
121 if (errp) error_setg(errp, "backup register image failed: %s", pbs_err ? pbs_err : "unknown error");
122diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
72ae34ec 123index 1dda8b7d8f..8cbf645b2c 100644
c96a4a38
DM
124--- a/proxmox-backup-client.h
125+++ b/proxmox-backup-client.h
72ae34ec 126@@ -32,6 +32,7 @@ proxmox_backup_co_register_image(
c96a4a38
DM
127 ProxmoxBackupHandle *pbs,
128 const char *device_name,
129 uint64_t size,
130+ bool incremental,
131 Error **errp);
132
133
134diff --git a/pve-backup.c b/pve-backup.c
b64c4dec 135index f77892a509..d9942a14a1 100644
c96a4a38
DM
136--- a/pve-backup.c
137+++ b/pve-backup.c
bf251437
FE
138@@ -7,6 +7,7 @@
139 #include "sysemu/blockdev.h"
140 #include "block/block_int-global-state.h"
141 #include "block/blockjob.h"
142+#include "block/dirty-bitmap.h"
143 #include "qapi/qapi-commands-block.h"
144 #include "qapi/qmp/qerror.h"
145
146@@ -29,6 +30,8 @@
c96a4a38
DM
147 *
148 */
149
150+const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
151+
152 static struct PVEBackupState {
153 struct {
154 // Everithing accessed from qmp_backup_query command is protected using lock
bf251437 155@@ -40,7 +43,9 @@ static struct PVEBackupState {
20be7fa0
TL
156 uuid_t uuid;
157 char uuid_str[37];
158 size_t total;
159+ size_t dirty;
160 size_t transferred;
161+ size_t reused;
162 size_t zero_bytes;
163 } stat;
164 int64_t speed;
bf251437 165@@ -67,6 +72,7 @@ typedef struct PVEBackupDevInfo {
c96a4a38
DM
166 uint8_t dev_id;
167 bool completed;
168 char targetfile[PATH_MAX];
169+ BdrvDirtyBitmap *bitmap;
170 BlockDriverState *target;
171 } PVEBackupDevInfo;
172
bf251437 173@@ -108,11 +114,12 @@ static bool pvebackup_error_or_canceled(void)
20be7fa0
TL
174 return error_or_canceled;
175 }
176
177-static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes)
178+static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes, size_t reused)
179 {
180 qemu_mutex_lock(&backup_state.stat.lock);
181 backup_state.stat.zero_bytes += zero_bytes;
182 backup_state.stat.transferred += transferred;
183+ backup_state.stat.reused += reused;
184 qemu_mutex_unlock(&backup_state.stat.lock);
185 }
186
bf251437 187@@ -151,7 +158,8 @@ pvebackup_co_dump_pbs_cb(
20be7fa0
TL
188 pvebackup_propagate_error(local_err);
189 return pbs_res;
190 } else {
191- pvebackup_add_transfered_bytes(size, !buf ? size : 0);
192+ size_t reused = (pbs_res == 0) ? size : 0;
193+ pvebackup_add_transfered_bytes(size, !buf ? size : 0, reused);
194 }
195
196 return size;
bf251437 197@@ -211,11 +219,11 @@ pvebackup_co_dump_vma_cb(
20be7fa0
TL
198 } else {
199 if (remaining >= VMA_CLUSTER_SIZE) {
200 assert(ret == VMA_CLUSTER_SIZE);
201- pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes);
202+ pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes, 0);
203 remaining -= VMA_CLUSTER_SIZE;
204 } else {
205 assert(ret == remaining);
206- pvebackup_add_transfered_bytes(remaining, zero_bytes);
207+ pvebackup_add_transfered_bytes(remaining, zero_bytes, 0);
208 remaining = 0;
209 }
210 }
bf251437 211@@ -251,6 +259,18 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
c96a4a38
DM
212 if (local_err != NULL) {
213 pvebackup_propagate_error(local_err);
214 }
215+ } else {
216+ // on error or cancel we cannot ensure synchronization of dirty
217+ // bitmaps with backup server, so remove all and do full backup next
218+ GList *l = backup_state.di_list;
219+ while (l) {
220+ PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
221+ l = g_list_next(l);
222+
223+ if (di->bitmap) {
224+ bdrv_release_dirty_bitmap(di->bitmap);
225+ }
226+ }
227 }
228
229 proxmox_backup_disconnect(backup_state.pbs);
bf251437 230@@ -306,6 +326,12 @@ static void pvebackup_complete_cb(void *opaque, int ret)
20be7fa0
TL
231 // remove self from job queue
232 backup_state.di_list = g_list_remove(backup_state.di_list, di);
233
234+ if (di->bitmap && ret < 0) {
235+ // on error or cancel we cannot ensure synchronization of dirty
236+ // bitmaps with backup server, so remove all and do full backup next
237+ bdrv_release_dirty_bitmap(di->bitmap);
238+ }
239+
240 g_free(di);
241
242 qemu_mutex_unlock(&backup_state.backup_mutex);
bf251437 243@@ -470,12 +496,18 @@ static bool create_backup_jobs(void) {
c96a4a38
DM
244
245 assert(di->target != NULL);
246
247+ MirrorSyncMode sync_mode = MIRROR_SYNC_MODE_FULL;
248+ BitmapSyncMode bitmap_mode = BITMAP_SYNC_MODE_NEVER;
249+ if (di->bitmap) {
250+ sync_mode = MIRROR_SYNC_MODE_BITMAP;
251+ bitmap_mode = BITMAP_SYNC_MODE_ON_SUCCESS;
252+ }
253 AioContext *aio_context = bdrv_get_aio_context(di->bs);
254 aio_context_acquire(aio_context);
255
256 BlockJob *job = backup_job_create(
257- NULL, di->bs, di->target, backup_state.speed, MIRROR_SYNC_MODE_FULL, NULL,
8dca018b 258- BITMAP_SYNC_MODE_NEVER, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
c96a4a38 259+ NULL, di->bs, di->target, backup_state.speed, sync_mode, di->bitmap,
8dca018b
SR
260+ bitmap_mode, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
261 JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
c96a4a38
DM
262
263 aio_context_release(aio_context);
bf251437
FE
264@@ -521,6 +553,8 @@ typedef struct QmpBackupTask {
265 bool has_backup_time;
c96a4a38 266 const char *fingerprint;
c96a4a38 267 int64_t backup_time;
0c893fd8
SR
268+ bool has_use_dirty_bitmap;
269+ bool use_dirty_bitmap;
c96a4a38
DM
270 bool has_format;
271 BackupFormat format;
bf251437
FE
272 const char *config_file;
273@@ -609,6 +643,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
20be7fa0
TL
274 }
275
276 size_t total = 0;
277+ size_t dirty = 0;
278
279 l = di_list;
280 while (l) {
bf251437 281@@ -646,6 +681,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
c96a4a38
DM
282 int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
283 firewall_name = "fw.conf";
284
0c893fd8 285+ bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
c96a4a38
DM
286+
287 char *pbs_err = NULL;
288 pbs = proxmox_backup_new(
289 task->backup_file,
bf251437 290@@ -665,7 +702,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
c96a4a38
DM
291 goto err;
292 }
293
294- if (proxmox_backup_co_connect(pbs, task->errp) < 0)
295+ int connect_result = proxmox_backup_co_connect(pbs, task->errp);
296+ if (connect_result < 0)
297 goto err;
298
299 /* register all devices */
bf251437 300@@ -676,9 +714,40 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
c96a4a38
DM
301
302 const char *devname = bdrv_get_device_name(di->bs);
303
304- int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
305- if (dev_id < 0)
306+ BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
0c893fd8 307+ bool expect_only_dirty = false;
c96a4a38 308+
0c893fd8 309+ if (use_dirty_bitmap) {
c96a4a38
DM
310+ if (bitmap == NULL) {
311+ bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
312+ if (!bitmap) {
313+ goto err;
314+ }
c96a4a38 315+ } else {
0c893fd8
SR
316+ expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
317+ }
318+
319+ if (expect_only_dirty) {
20be7fa0 320+ dirty += bdrv_get_dirty_count(bitmap);
0c893fd8
SR
321+ } else {
322+ /* mark entire bitmap as dirty to make full backup */
323+ bdrv_set_dirty_bitmap(bitmap, 0, di->size);
324+ dirty += di->size;
c96a4a38
DM
325+ }
326+ di->bitmap = bitmap;
0c893fd8 327+ } else {
20be7fa0 328+ dirty += di->size;
0c893fd8
SR
329+
330+ /* after a full backup the old dirty bitmap is invalid anyway */
331+ if (bitmap != NULL) {
332+ bdrv_release_dirty_bitmap(bitmap);
333+ }
c96a4a38
DM
334+ }
335+
0c893fd8 336+ int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
c96a4a38
DM
337+ if (dev_id < 0) {
338 goto err;
339+ }
340
341 if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
342 goto err;
bf251437 343@@ -687,6 +756,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
20be7fa0
TL
344 di->dev_id = dev_id;
345 }
346 } else if (format == BACKUP_FORMAT_VMA) {
347+ dirty = total;
348+
349 vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
350 if (!vmaw) {
351 if (local_err) {
bf251437 352@@ -714,6 +785,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
20be7fa0
TL
353 }
354 }
355 } else if (format == BACKUP_FORMAT_DIR) {
356+ dirty = total;
357+
358 if (mkdir(task->backup_file, 0640) != 0) {
359 error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
360 task->backup_file);
bf251437 361@@ -786,8 +859,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
20be7fa0
TL
362 char *uuid_str = g_strdup(backup_state.stat.uuid_str);
363
364 backup_state.stat.total = total;
365+ backup_state.stat.dirty = dirty;
366 backup_state.stat.transferred = 0;
367 backup_state.stat.zero_bytes = 0;
0c893fd8 368+ backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
20be7fa0
TL
369
370 qemu_mutex_unlock(&backup_state.stat.lock);
371
bf251437 372@@ -811,6 +886,10 @@ err:
c96a4a38
DM
373 PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
374 l = g_list_next(l);
375
376+ if (di->bitmap) {
377+ bdrv_release_dirty_bitmap(di->bitmap);
378+ }
379+
380 if (di->target) {
53b56ca7 381 bdrv_co_unref(di->target);
c96a4a38 382 }
bf251437
FE
383@@ -852,6 +931,7 @@ UuidInfo *qmp_backup(
384 const char *fingerprint,
385 const char *backup_id,
c96a4a38 386 bool has_backup_time, int64_t backup_time,
0c893fd8 387+ bool has_use_dirty_bitmap, bool use_dirty_bitmap,
c96a4a38 388 bool has_format, BackupFormat format,
bf251437
FE
389 const char *config_file,
390 const char *firewall_file,
391@@ -866,6 +946,8 @@ UuidInfo *qmp_backup(
c96a4a38
DM
392 .backup_id = backup_id,
393 .has_backup_time = has_backup_time,
394 .backup_time = backup_time,
0c893fd8
SR
395+ .has_use_dirty_bitmap = has_use_dirty_bitmap,
396+ .use_dirty_bitmap = use_dirty_bitmap,
c96a4a38
DM
397 .has_format = has_format,
398 .format = format,
bf251437
FE
399 .config_file = config_file,
400@@ -927,10 +1009,14 @@ BackupStatus *qmp_query_backup(Error **errp)
20be7fa0
TL
401
402 info->has_total = true;
403 info->total = backup_state.stat.total;
404+ info->has_dirty = true;
405+ info->dirty = backup_state.stat.dirty;
406 info->has_zero_bytes = true;
407 info->zero_bytes = backup_state.stat.zero_bytes;
408 info->has_transferred = true;
409 info->transferred = backup_state.stat.transferred;
410+ info->has_reused = true;
411+ info->reused = backup_state.stat.reused;
412
413 qemu_mutex_unlock(&backup_state.stat.lock);
414
c96a4a38 415diff --git a/qapi/block-core.json b/qapi/block-core.json
bf251437 416index 16fb4c5ea0..92f90a898a 100644
c96a4a38
DM
417--- a/qapi/block-core.json
418+++ b/qapi/block-core.json
bf251437 419@@ -848,8 +848,13 @@
20be7fa0
TL
420 #
421 # @total: total amount of bytes involved in the backup process
422 #
0c893fd8 423+# @dirty: with incremental mode (PBS) this is the amount of bytes involved
20be7fa0
TL
424+# in the backup process which are marked dirty.
425+#
426 # @transferred: amount of bytes already backed up.
427 #
428+# @reused: amount of bytes reused due to deduplication.
429+#
430 # @zero-bytes: amount of 'zero' bytes detected.
431 #
432 # @start-time: time (epoch) when backup job started.
bf251437 433@@ -862,8 +867,8 @@
20be7fa0
TL
434 #
435 ##
436 { 'struct': 'BackupStatus',
437- 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
438- '*transferred': 'int', '*zero-bytes': 'int',
439+ 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', '*dirty': 'int',
440+ '*transferred': 'int', '*zero-bytes': 'int', '*reused': 'int',
441 '*start-time': 'int', '*end-time': 'int',
442 '*backup-file': 'str', '*uuid': 'str' } }
443
bf251437 444@@ -906,6 +911,8 @@
c96a4a38
DM
445 #
446 # @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
447 #
0c893fd8 448+# @use-dirty-bitmap: use dirty bitmap to detect incremental changes since last job (optional for format 'pbs')
c96a4a38
DM
449+#
450 # Returns: the uuid of the backup job
451 #
452 ##
bf251437 453@@ -916,6 +923,7 @@
c96a4a38
DM
454 '*fingerprint': 'str',
455 '*backup-id': 'str',
456 '*backup-time': 'int',
0c893fd8 457+ '*use-dirty-bitmap': 'bool',
c96a4a38
DM
458 '*format': 'BackupFormat',
459 '*config-file': 'str',
460 '*firewall-file': 'str',