]> git.proxmox.com Git - pve-qemu.git/blob - debian/patches/pve/0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
update submodule and patches to 7.2.0
[pve-qemu.git] / debian / patches / pve / 0031-PVE-Backup-Add-dirty-bitmap-tracking-for-incremental.patch
1 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2 From: Stefan Reiter <s.reiter@proxmox.com>
3 Date: Mon, 29 Jun 2020 11:06:03 +0200
4 Subject: [PATCH] PVE-Backup: Add dirty-bitmap tracking for incremental backups
5
6 Uses QEMU's existing MIRROR_SYNC_MODE_BITMAP and a dirty-bitmap on top
7 of all backed-up drives. This will only execute the data-write callback
8 for any changed chunks, the PBS rust code will reuse chunks from the
9 previous index for everything it doesn't receive if reuse_index is true.
10
11 On error or cancellation, remove all dirty bitmaps to ensure
12 consistency.
13
14 Add PBS/incremental specific information to query backup info QMP and
15 HMP commands.
16
17 Only supported for PBS backups.
18
19 Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
20 Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
21 Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
22 ---
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 +
27 pve-backup.c | 103 ++++++++++++++++++++++++++++++---
28 qapi/block-core.json | 12 +++-
29 6 files changed, 142 insertions(+), 23 deletions(-)
30
31 diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
32 index 477044c54a..556af25861 100644
33 --- a/block/monitor/block-hmp-cmds.c
34 +++ b/block/monitor/block-hmp-cmds.c
35 @@ -1042,6 +1042,7 @@ void hmp_backup(Monitor *mon, const QDict *qdict)
36 false, NULL, // PBS fingerprint
37 false, NULL, // PBS backup-id
38 false, 0, // PBS backup-time
39 + false, false, // PBS incremental
40 true, dir ? BACKUP_FORMAT_DIR : BACKUP_FORMAT_VMA,
41 false, NULL, false, NULL, !!devlist,
42 devlist, qdict_haskey(qdict, "speed"), speed, &error);
43 diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
44 index a40b25e906..670f783515 100644
45 --- a/monitor/hmp-cmds.c
46 +++ b/monitor/hmp-cmds.c
47 @@ -225,19 +225,42 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
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);
101 diff --git a/proxmox-backup-client.c b/proxmox-backup-client.c
102 index a8f6653a81..4ce7bc0b5e 100644
103 --- a/proxmox-backup-client.c
104 +++ b/proxmox-backup-client.c
105 @@ -89,6 +89,7 @@ proxmox_backup_co_register_image(
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();
113 @@ -98,7 +99,7 @@ proxmox_backup_co_register_image(
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");
122 diff --git a/proxmox-backup-client.h b/proxmox-backup-client.h
123 index 1dda8b7d8f..8cbf645b2c 100644
124 --- a/proxmox-backup-client.h
125 +++ b/proxmox-backup-client.h
126 @@ -32,6 +32,7 @@ proxmox_backup_co_register_image(
127 ProxmoxBackupHandle *pbs,
128 const char *device_name,
129 uint64_t size,
130 + bool incremental,
131 Error **errp);
132
133
134 diff --git a/pve-backup.c b/pve-backup.c
135 index 3d28975eaa..abd7062afe 100644
136 --- a/pve-backup.c
137 +++ b/pve-backup.c
138 @@ -28,6 +28,8 @@
139 *
140 */
141
142 +const char *PBS_BITMAP_NAME = "pbs-incremental-dirty-bitmap";
143 +
144 static struct PVEBackupState {
145 struct {
146 // Everithing accessed from qmp_backup_query command is protected using lock
147 @@ -39,7 +41,9 @@ static struct PVEBackupState {
148 uuid_t uuid;
149 char uuid_str[37];
150 size_t total;
151 + size_t dirty;
152 size_t transferred;
153 + size_t reused;
154 size_t zero_bytes;
155 } stat;
156 int64_t speed;
157 @@ -66,6 +70,7 @@ typedef struct PVEBackupDevInfo {
158 uint8_t dev_id;
159 bool completed;
160 char targetfile[PATH_MAX];
161 + BdrvDirtyBitmap *bitmap;
162 BlockDriverState *target;
163 } PVEBackupDevInfo;
164
165 @@ -107,11 +112,12 @@ static bool pvebackup_error_or_canceled(void)
166 return error_or_canceled;
167 }
168
169 -static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes)
170 +static void pvebackup_add_transfered_bytes(size_t transferred, size_t zero_bytes, size_t reused)
171 {
172 qemu_mutex_lock(&backup_state.stat.lock);
173 backup_state.stat.zero_bytes += zero_bytes;
174 backup_state.stat.transferred += transferred;
175 + backup_state.stat.reused += reused;
176 qemu_mutex_unlock(&backup_state.stat.lock);
177 }
178
179 @@ -150,7 +156,8 @@ pvebackup_co_dump_pbs_cb(
180 pvebackup_propagate_error(local_err);
181 return pbs_res;
182 } else {
183 - pvebackup_add_transfered_bytes(size, !buf ? size : 0);
184 + size_t reused = (pbs_res == 0) ? size : 0;
185 + pvebackup_add_transfered_bytes(size, !buf ? size : 0, reused);
186 }
187
188 return size;
189 @@ -210,11 +217,11 @@ pvebackup_co_dump_vma_cb(
190 } else {
191 if (remaining >= VMA_CLUSTER_SIZE) {
192 assert(ret == VMA_CLUSTER_SIZE);
193 - pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes);
194 + pvebackup_add_transfered_bytes(VMA_CLUSTER_SIZE, zero_bytes, 0);
195 remaining -= VMA_CLUSTER_SIZE;
196 } else {
197 assert(ret == remaining);
198 - pvebackup_add_transfered_bytes(remaining, zero_bytes);
199 + pvebackup_add_transfered_bytes(remaining, zero_bytes, 0);
200 remaining = 0;
201 }
202 }
203 @@ -250,6 +257,18 @@ static void coroutine_fn pvebackup_co_cleanup(void *unused)
204 if (local_err != NULL) {
205 pvebackup_propagate_error(local_err);
206 }
207 + } else {
208 + // on error or cancel we cannot ensure synchronization of dirty
209 + // bitmaps with backup server, so remove all and do full backup next
210 + GList *l = backup_state.di_list;
211 + while (l) {
212 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
213 + l = g_list_next(l);
214 +
215 + if (di->bitmap) {
216 + bdrv_release_dirty_bitmap(di->bitmap);
217 + }
218 + }
219 }
220
221 proxmox_backup_disconnect(backup_state.pbs);
222 @@ -305,6 +324,12 @@ static void pvebackup_complete_cb(void *opaque, int ret)
223 // remove self from job queue
224 backup_state.di_list = g_list_remove(backup_state.di_list, di);
225
226 + if (di->bitmap && ret < 0) {
227 + // on error or cancel we cannot ensure synchronization of dirty
228 + // bitmaps with backup server, so remove all and do full backup next
229 + bdrv_release_dirty_bitmap(di->bitmap);
230 + }
231 +
232 g_free(di);
233
234 qemu_mutex_unlock(&backup_state.backup_mutex);
235 @@ -469,12 +494,18 @@ static bool create_backup_jobs(void) {
236
237 assert(di->target != NULL);
238
239 + MirrorSyncMode sync_mode = MIRROR_SYNC_MODE_FULL;
240 + BitmapSyncMode bitmap_mode = BITMAP_SYNC_MODE_NEVER;
241 + if (di->bitmap) {
242 + sync_mode = MIRROR_SYNC_MODE_BITMAP;
243 + bitmap_mode = BITMAP_SYNC_MODE_ON_SUCCESS;
244 + }
245 AioContext *aio_context = bdrv_get_aio_context(di->bs);
246 aio_context_acquire(aio_context);
247
248 BlockJob *job = backup_job_create(
249 - NULL, di->bs, di->target, backup_state.speed, MIRROR_SYNC_MODE_FULL, NULL,
250 - BITMAP_SYNC_MODE_NEVER, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
251 + NULL, di->bs, di->target, backup_state.speed, sync_mode, di->bitmap,
252 + bitmap_mode, false, NULL, &perf, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
253 JOB_DEFAULT, pvebackup_complete_cb, di, NULL, &local_err);
254
255 aio_context_release(aio_context);
256 @@ -525,6 +556,8 @@ typedef struct QmpBackupTask {
257 const char *fingerprint;
258 bool has_fingerprint;
259 int64_t backup_time;
260 + bool has_use_dirty_bitmap;
261 + bool use_dirty_bitmap;
262 bool has_format;
263 BackupFormat format;
264 bool has_config_file;
265 @@ -616,6 +649,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
266 }
267
268 size_t total = 0;
269 + size_t dirty = 0;
270
271 l = di_list;
272 while (l) {
273 @@ -653,6 +687,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
274 int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
275 firewall_name = "fw.conf";
276
277 + bool use_dirty_bitmap = task->has_use_dirty_bitmap && task->use_dirty_bitmap;
278 +
279 char *pbs_err = NULL;
280 pbs = proxmox_backup_new(
281 task->backup_file,
282 @@ -672,7 +708,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
283 goto err;
284 }
285
286 - if (proxmox_backup_co_connect(pbs, task->errp) < 0)
287 + int connect_result = proxmox_backup_co_connect(pbs, task->errp);
288 + if (connect_result < 0)
289 goto err;
290
291 /* register all devices */
292 @@ -683,9 +720,40 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
293
294 const char *devname = bdrv_get_device_name(di->bs);
295
296 - int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, task->errp);
297 - if (dev_id < 0)
298 + BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
299 + bool expect_only_dirty = false;
300 +
301 + if (use_dirty_bitmap) {
302 + if (bitmap == NULL) {
303 + bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
304 + if (!bitmap) {
305 + goto err;
306 + }
307 + } else {
308 + expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
309 + }
310 +
311 + if (expect_only_dirty) {
312 + dirty += bdrv_get_dirty_count(bitmap);
313 + } else {
314 + /* mark entire bitmap as dirty to make full backup */
315 + bdrv_set_dirty_bitmap(bitmap, 0, di->size);
316 + dirty += di->size;
317 + }
318 + di->bitmap = bitmap;
319 + } else {
320 + dirty += di->size;
321 +
322 + /* after a full backup the old dirty bitmap is invalid anyway */
323 + if (bitmap != NULL) {
324 + bdrv_release_dirty_bitmap(bitmap);
325 + }
326 + }
327 +
328 + int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
329 + if (dev_id < 0) {
330 goto err;
331 + }
332
333 if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
334 goto err;
335 @@ -694,6 +762,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
336 di->dev_id = dev_id;
337 }
338 } else if (format == BACKUP_FORMAT_VMA) {
339 + dirty = total;
340 +
341 vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
342 if (!vmaw) {
343 if (local_err) {
344 @@ -721,6 +791,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
345 }
346 }
347 } else if (format == BACKUP_FORMAT_DIR) {
348 + dirty = total;
349 +
350 if (mkdir(task->backup_file, 0640) != 0) {
351 error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
352 task->backup_file);
353 @@ -793,8 +865,10 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
354 char *uuid_str = g_strdup(backup_state.stat.uuid_str);
355
356 backup_state.stat.total = total;
357 + backup_state.stat.dirty = dirty;
358 backup_state.stat.transferred = 0;
359 backup_state.stat.zero_bytes = 0;
360 + backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
361
362 qemu_mutex_unlock(&backup_state.stat.lock);
363
364 @@ -818,6 +892,10 @@ err:
365 PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data;
366 l = g_list_next(l);
367
368 + if (di->bitmap) {
369 + bdrv_release_dirty_bitmap(di->bitmap);
370 + }
371 +
372 if (di->target) {
373 bdrv_unref(di->target);
374 }
375 @@ -859,6 +937,7 @@ UuidInfo *qmp_backup(
376 bool has_fingerprint, const char *fingerprint,
377 bool has_backup_id, const char *backup_id,
378 bool has_backup_time, int64_t backup_time,
379 + bool has_use_dirty_bitmap, bool use_dirty_bitmap,
380 bool has_format, BackupFormat format,
381 bool has_config_file, const char *config_file,
382 bool has_firewall_file, const char *firewall_file,
383 @@ -877,6 +956,8 @@ UuidInfo *qmp_backup(
384 .backup_id = backup_id,
385 .has_backup_time = has_backup_time,
386 .backup_time = backup_time,
387 + .has_use_dirty_bitmap = has_use_dirty_bitmap,
388 + .use_dirty_bitmap = use_dirty_bitmap,
389 .has_format = has_format,
390 .format = format,
391 .has_config_file = has_config_file,
392 @@ -945,10 +1026,14 @@ BackupStatus *qmp_query_backup(Error **errp)
393
394 info->has_total = true;
395 info->total = backup_state.stat.total;
396 + info->has_dirty = true;
397 + info->dirty = backup_state.stat.dirty;
398 info->has_zero_bytes = true;
399 info->zero_bytes = backup_state.stat.zero_bytes;
400 info->has_transferred = true;
401 info->transferred = backup_state.stat.transferred;
402 + info->has_reused = true;
403 + info->reused = backup_state.stat.reused;
404
405 qemu_mutex_unlock(&backup_state.stat.lock);
406
407 diff --git a/qapi/block-core.json b/qapi/block-core.json
408 index c3b6b93472..992e6c1e3f 100644
409 --- a/qapi/block-core.json
410 +++ b/qapi/block-core.json
411 @@ -753,8 +753,13 @@
412 #
413 # @total: total amount of bytes involved in the backup process
414 #
415 +# @dirty: with incremental mode (PBS) this is the amount of bytes involved
416 +# in the backup process which are marked dirty.
417 +#
418 # @transferred: amount of bytes already backed up.
419 #
420 +# @reused: amount of bytes reused due to deduplication.
421 +#
422 # @zero-bytes: amount of 'zero' bytes detected.
423 #
424 # @start-time: time (epoch) when backup job started.
425 @@ -767,8 +772,8 @@
426 #
427 ##
428 { 'struct': 'BackupStatus',
429 - 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int',
430 - '*transferred': 'int', '*zero-bytes': 'int',
431 + 'data': {'*status': 'str', '*errmsg': 'str', '*total': 'int', '*dirty': 'int',
432 + '*transferred': 'int', '*zero-bytes': 'int', '*reused': 'int',
433 '*start-time': 'int', '*end-time': 'int',
434 '*backup-file': 'str', '*uuid': 'str' } }
435
436 @@ -811,6 +816,8 @@
437 #
438 # @backup-time: backup timestamp (Unix epoch, required for format 'pbs')
439 #
440 +# @use-dirty-bitmap: use dirty bitmap to detect incremental changes since last job (optional for format 'pbs')
441 +#
442 # Returns: the uuid of the backup job
443 #
444 ##
445 @@ -821,6 +828,7 @@
446 '*fingerprint': 'str',
447 '*backup-id': 'str',
448 '*backup-time': 'int',
449 + '*use-dirty-bitmap': 'bool',
450 '*format': 'BackupFormat',
451 '*config-file': 'str',
452 '*firewall-file': 'str',