[pve-devel] [PATCH v2 qemu 1/1] add bitmap drive-mirror patches

Fabian Grünbichler f.gruenbichler at proxmox.com
Tue Mar 17 08:55:24 CET 2020


Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
---
I opted to send them as cherry-picks of the series I submitted for
upstream inclusion, to make matching with future versions and/or
dropping when it gets applied easier.

it's of course also possible to squash them into a single patch, drop
the test-case one, etc.

 ...d-support-for-sync-bitmap-mode-never.patch |  443 +++
 ...-support-for-conditional-and-always-.patch |   83 +
 ...check-for-bitmap-mode-without-bitmap.patch |   33 +
 ...-to-bdrv_dirty_bitmap_merge_internal.patch |   45 +
 ...8-iotests-add-test-for-bitmap-mirror.patch | 3447 +++++++++++++++++
 .../0039-mirror-move-some-checks-to-qmp.patch |  275 ++
 debian/patches/series                         |    6 +
 7 files changed, 4332 insertions(+)
 create mode 100644 debian/patches/pve/0034-drive-mirror-add-support-for-sync-bitmap-mode-never.patch
 create mode 100644 debian/patches/pve/0035-drive-mirror-add-support-for-conditional-and-always-.patch
 create mode 100644 debian/patches/pve/0036-mirror-add-check-for-bitmap-mode-without-bitmap.patch
 create mode 100644 debian/patches/pve/0037-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch
 create mode 100644 debian/patches/pve/0038-iotests-add-test-for-bitmap-mirror.patch
 create mode 100644 debian/patches/pve/0039-mirror-move-some-checks-to-qmp.patch

diff --git a/debian/patches/pve/0034-drive-mirror-add-support-for-sync-bitmap-mode-never.patch b/debian/patches/pve/0034-drive-mirror-add-support-for-sync-bitmap-mode-never.patch
new file mode 100644
index 0000000..50229d0
--- /dev/null
+++ b/debian/patches/pve/0034-drive-mirror-add-support-for-sync-bitmap-mode-never.patch
@@ -0,0 +1,443 @@
+From 1bfdb0492d8b70983342cf96576b294d60172e05 Mon Sep 17 00:00:00 2001
+From: John Snow <jsnow at redhat.com>
+Date: Tue, 9 Jul 2019 21:12:21 -0400
+Subject: [PATCH qemu 34/39] drive-mirror: add support for sync=bitmap
+ mode=never
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This patch adds support for the "BITMAP" sync mode to drive-mirror and
+blockdev-mirror. It adds support only for the BitmapSyncMode "never,"
+because it's the simplest mode.
+
+This mode simply uses a user-provided bitmap as an initial copy
+manifest, and then does not clear any bits in the bitmap at the
+conclusion of the operation.
+
+Any new writes dirtied during the operation are copied out, in contrast
+to backup. Note that whether these writes are reflected in the bitmap
+at the conclusion of the operation depends on whether that bitmap is
+actually recording!
+
+This patch was originally based on one by Ma Haocong, but it has since
+been modified pretty heavily.
+
+Suggested-by: Ma Haocong <mahaocong at didichuxing.com>
+Signed-off-by: Ma Haocong <mahaocong at didichuxing.com>
+Signed-off-by: John Snow <jsnow at redhat.com>
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
+---
+ include/block/block_int.h   |  4 +-
+ block/mirror.c              | 98 ++++++++++++++++++++++++++++++-------
+ blockdev.c                  | 39 +++++++++++++--
+ tests/test-block-iothread.c |  4 +-
+ qapi/block-core.json        | 29 +++++++++--
+ 5 files changed, 145 insertions(+), 29 deletions(-)
+
+diff --git a/include/block/block_int.h b/include/block/block_int.h
+index 43b00c15ac..bd2e11c4d9 100644
+--- a/include/block/block_int.h
++++ b/include/block/block_int.h
+@@ -1206,7 +1206,9 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
+                   BlockDriverState *target, const char *replaces,
+                   int creation_flags, int64_t speed,
+                   uint32_t granularity, int64_t buf_size,
+-                  MirrorSyncMode mode, BlockMirrorBackingMode backing_mode,
++                  MirrorSyncMode mode, BdrvDirtyBitmap *bitmap,
++                  BitmapSyncMode bitmap_mode,
++                  BlockMirrorBackingMode backing_mode,
+                   bool zero_target,
+                   BlockdevOnError on_source_error,
+                   BlockdevOnError on_target_error,
+diff --git a/block/mirror.c b/block/mirror.c
+index f0f2d9dff1..fd7f574365 100644
+--- a/block/mirror.c
++++ b/block/mirror.c
+@@ -49,7 +49,7 @@ typedef struct MirrorBlockJob {
+     BlockDriverState *to_replace;
+     /* Used to block operations on the drive-mirror-replace target */
+     Error *replace_blocker;
+-    bool is_none_mode;
++    MirrorSyncMode sync_mode;
+     BlockMirrorBackingMode backing_mode;
+     /* Whether the target image requires explicit zero-initialization */
+     bool zero_target;
+@@ -64,6 +64,8 @@ typedef struct MirrorBlockJob {
+     size_t buf_size;
+     int64_t bdev_length;
+     unsigned long *cow_bitmap;
++    BdrvDirtyBitmap *sync_bitmap;
++    BitmapSyncMode bitmap_mode;
+     BdrvDirtyBitmap *dirty_bitmap;
+     BdrvDirtyBitmapIter *dbi;
+     uint8_t *buf;
+@@ -668,7 +670,8 @@ static int mirror_exit_common(Job *job)
+     bdrv_child_refresh_perms(mirror_top_bs, mirror_top_bs->backing,
+                              &error_abort);
+     if (!abort && s->backing_mode == MIRROR_SOURCE_BACKING_CHAIN) {
+-        BlockDriverState *backing = s->is_none_mode ? src : s->base;
++        BlockDriverState *backing;
++        backing = s->sync_mode == MIRROR_SYNC_MODE_NONE ? src : s->base;
+         if (backing_bs(target_bs) != backing) {
+             bdrv_set_backing_hd(target_bs, backing, &local_err);
+             if (local_err) {
+@@ -750,6 +753,16 @@ static void mirror_abort(Job *job)
+     assert(ret == 0);
+ }
+ 
++/* Always called after commit/abort. */
++static void mirror_clean(Job *job)
++{
++    MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
++
++    if (s->sync_bitmap) {
++        bdrv_dirty_bitmap_set_busy(s->sync_bitmap, false);
++    }
++}
++
+ static void coroutine_fn mirror_throttle(MirrorBlockJob *s)
+ {
+     int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+@@ -928,7 +941,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp)
+     mirror_free_init(s);
+ 
+     s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+-    if (!s->is_none_mode) {
++    if ((s->sync_mode == MIRROR_SYNC_MODE_TOP) ||
++        (s->sync_mode == MIRROR_SYNC_MODE_FULL)) {
+         ret = mirror_dirty_init(s);
+         if (ret < 0 || job_is_cancelled(&s->common.job)) {
+             goto immediate_exit;
+@@ -1160,6 +1174,7 @@ static const BlockJobDriver mirror_job_driver = {
+         .run                    = mirror_run,
+         .prepare                = mirror_prepare,
+         .abort                  = mirror_abort,
++        .clean                  = mirror_clean,
+         .pause                  = mirror_pause,
+         .complete               = mirror_complete,
+     },
+@@ -1175,6 +1190,7 @@ static const BlockJobDriver commit_active_job_driver = {
+         .run                    = mirror_run,
+         .prepare                = mirror_prepare,
+         .abort                  = mirror_abort,
++        .clean                  = mirror_clean,
+         .pause                  = mirror_pause,
+         .complete               = mirror_complete,
+     },
+@@ -1520,7 +1536,10 @@ static BlockJob *mirror_start_job(
+                              BlockCompletionFunc *cb,
+                              void *opaque,
+                              const BlockJobDriver *driver,
+-                             bool is_none_mode, BlockDriverState *base,
++                             MirrorSyncMode sync_mode,
++                             BdrvDirtyBitmap *bitmap,
++                             BitmapSyncMode bitmap_mode,
++                             BlockDriverState *base,
+                              bool auto_complete, const char *filter_node_name,
+                              bool is_mirror, MirrorCopyMode copy_mode,
+                              Error **errp)
+@@ -1533,10 +1552,39 @@ static BlockJob *mirror_start_job(
+     Error *local_err = NULL;
+     int ret;
+ 
+-    if (granularity == 0) {
+-        granularity = bdrv_get_default_bitmap_granularity(target);
++    if (sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
++        error_setg(errp, "Sync mode '%s' not supported",
++                   MirrorSyncMode_str(sync_mode));
++        return NULL;
++    } else if (sync_mode == MIRROR_SYNC_MODE_BITMAP) {
++        if (!bitmap) {
++            error_setg(errp, "Must provide a valid bitmap name for '%s'"
++                       " sync mode",
++                       MirrorSyncMode_str(sync_mode));
++            return NULL;
++        } else if (bitmap_mode != BITMAP_SYNC_MODE_NEVER) {
++            error_setg(errp,
++                       "Bitmap Sync Mode '%s' is not supported by Mirror",
++                       BitmapSyncMode_str(bitmap_mode));
++        }
++    } else if (bitmap) {
++        error_setg(errp,
++                   "sync mode '%s' is not compatible with bitmaps",
++                   MirrorSyncMode_str(sync_mode));
++        return NULL;
+     }
+ 
++    if (bitmap) {
++        if (granularity) {
++            error_setg(errp, "granularity (%d)"
++                       "cannot be specified when a bitmap is provided",
++                       granularity);
++            return NULL;
++        }
++        granularity = bdrv_dirty_bitmap_granularity(bitmap);
++    } else if (granularity == 0) {
++        granularity = bdrv_get_default_bitmap_granularity(target);
++    }
+     assert(is_power_of_2(granularity));
+ 
+     if (buf_size < 0) {
+@@ -1640,7 +1688,9 @@ static BlockJob *mirror_start_job(
+     s->replaces = g_strdup(replaces);
+     s->on_source_error = on_source_error;
+     s->on_target_error = on_target_error;
+-    s->is_none_mode = is_none_mode;
++    s->sync_mode = sync_mode;
++    s->sync_bitmap = bitmap;
++    s->bitmap_mode = bitmap_mode;
+     s->backing_mode = backing_mode;
+     s->zero_target = zero_target;
+     s->copy_mode = copy_mode;
+@@ -1660,6 +1710,18 @@ static BlockJob *mirror_start_job(
+         bdrv_disable_dirty_bitmap(s->dirty_bitmap);
+     }
+ 
++    if (s->sync_bitmap) {
++        bdrv_dirty_bitmap_set_busy(s->sync_bitmap, true);
++    }
++
++    if (s->sync_mode == MIRROR_SYNC_MODE_BITMAP) {
++        bdrv_merge_dirty_bitmap(s->dirty_bitmap, s->sync_bitmap,
++                                NULL, &local_err);
++        if (local_err) {
++            goto fail;
++        }
++    }
++
+     ret = block_job_add_bdrv(&s->common, "source", bs, 0,
+                              BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE |
+                              BLK_PERM_CONSISTENT_READ,
+@@ -1713,6 +1775,9 @@ fail:
+         if (s->dirty_bitmap) {
+             bdrv_release_dirty_bitmap(s->dirty_bitmap);
+         }
++        if (s->sync_bitmap) {
++            bdrv_dirty_bitmap_set_busy(s->sync_bitmap, false);
++        }
+         job_early_fail(&s->common.job);
+     }
+ 
+@@ -1730,29 +1795,23 @@ void mirror_start(const char *job_id, BlockDriverState *bs,
+                   BlockDriverState *target, const char *replaces,
+                   int creation_flags, int64_t speed,
+                   uint32_t granularity, int64_t buf_size,
+-                  MirrorSyncMode mode, BlockMirrorBackingMode backing_mode,
++                  MirrorSyncMode mode, BdrvDirtyBitmap *bitmap,
++                  BitmapSyncMode bitmap_mode,
++                  BlockMirrorBackingMode backing_mode,
+                   bool zero_target,
+                   BlockdevOnError on_source_error,
+                   BlockdevOnError on_target_error,
+                   bool unmap, const char *filter_node_name,
+                   MirrorCopyMode copy_mode, Error **errp)
+ {
+-    bool is_none_mode;
+     BlockDriverState *base;
+ 
+-    if ((mode == MIRROR_SYNC_MODE_INCREMENTAL) ||
+-        (mode == MIRROR_SYNC_MODE_BITMAP)) {
+-        error_setg(errp, "Sync mode '%s' not supported",
+-                   MirrorSyncMode_str(mode));
+-        return;
+-    }
+-    is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
+     base = mode == MIRROR_SYNC_MODE_TOP ? backing_bs(bs) : NULL;
+     mirror_start_job(job_id, bs, creation_flags, target, replaces,
+                      speed, granularity, buf_size, backing_mode, zero_target,
+                      on_source_error, on_target_error, unmap, NULL, NULL,
+-                     &mirror_job_driver, is_none_mode, base, false,
+-                     filter_node_name, true, copy_mode, errp);
++                     &mirror_job_driver, mode, bitmap, bitmap_mode, base,
++                     false, filter_node_name, true, copy_mode, errp);
+ }
+ 
+ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs,
+@@ -1778,7 +1837,8 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs,
+                      job_id, bs, creation_flags, base, NULL, speed, 0, 0,
+                      MIRROR_LEAVE_BACKING_CHAIN, false,
+                      on_error, on_error, true, cb, opaque,
+-                     &commit_active_job_driver, false, base, auto_complete,
++                     &commit_active_job_driver, MIRROR_SYNC_MODE_FULL,
++                     NULL, 0, base, auto_complete,
+                      filter_node_name, false, MIRROR_COPY_MODE_BACKGROUND,
+                      &local_err);
+     if (local_err) {
+diff --git a/blockdev.c b/blockdev.c
+index e5310cb939..08285b9e86 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -3763,6 +3763,10 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
+                                    BlockDriverState *target,
+                                    bool has_replaces, const char *replaces,
+                                    enum MirrorSyncMode sync,
++                                   bool has_bitmap,
++                                   const char *bitmap_name,
++                                   bool has_bitmap_mode,
++                                   BitmapSyncMode bitmap_mode,
+                                    BlockMirrorBackingMode backing_mode,
+                                    bool zero_target,
+                                    bool has_speed, int64_t speed,
+@@ -3781,6 +3785,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
+                                    Error **errp)
+ {
+     int job_flags = JOB_DEFAULT;
++    BdrvDirtyBitmap *bitmap = NULL;
+ 
+     if (!has_speed) {
+         speed = 0;
+@@ -3835,6 +3840,29 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
+         sync = MIRROR_SYNC_MODE_FULL;
+     }
+ 
++    if (has_bitmap) {
++        if (granularity) {
++            error_setg(errp, "Granularity and bitmap cannot both be set");
++            return;
++        }
++
++        if (!has_bitmap_mode) {
++            error_setg(errp, "bitmap-mode must be specified if"
++                       " a bitmap is provided");
++            return;
++        }
++
++        bitmap = bdrv_find_dirty_bitmap(bs, bitmap_name);
++        if (!bitmap) {
++            error_setg(errp, "Dirty bitmap '%s' not found", bitmap_name);
++            return;
++        }
++
++        if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_ALLOW_RO, errp)) {
++            return;
++        }
++    }
++
+     if (has_replaces) {
+         BlockDriverState *to_replace_bs;
+         AioContext *replace_aio_context;
+@@ -3872,8 +3900,8 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
+      * and will allow to check whether the node still exist at mirror completion
+      */
+     mirror_start(job_id, bs, target,
+-                 has_replaces ? replaces : NULL, job_flags,
+-                 speed, granularity, buf_size, sync, backing_mode, zero_target,
++                 has_replaces ? replaces : NULL, job_flags, speed, granularity,
++                 buf_size, sync, bitmap, bitmap_mode, backing_mode, zero_target,
+                  on_source_error, on_target_error, unmap, filter_node_name,
+                  copy_mode, errp);
+ }
+@@ -4003,6 +4031,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp)
+ 
+     blockdev_mirror_common(arg->has_job_id ? arg->job_id : NULL, bs, target_bs,
+                            arg->has_replaces, arg->replaces, arg->sync,
++                           arg->has_bitmap, arg->bitmap,
++                           arg->has_bitmap_mode, arg->bitmap_mode,
+                            backing_mode, zero_target,
+                            arg->has_speed, arg->speed,
+                            arg->has_granularity, arg->granularity,
+@@ -4025,6 +4055,8 @@ void qmp_blockdev_mirror(bool has_job_id, const char *job_id,
+                          const char *device, const char *target,
+                          bool has_replaces, const char *replaces,
+                          MirrorSyncMode sync,
++                         bool has_bitmap, const char *bitmap,
++                         bool has_bitmap_mode, BitmapSyncMode bitmap_mode,
+                          bool has_speed, int64_t speed,
+                          bool has_granularity, uint32_t granularity,
+                          bool has_buf_size, int64_t buf_size,
+@@ -4068,7 +4100,8 @@ void qmp_blockdev_mirror(bool has_job_id, const char *job_id,
+     }
+ 
+     blockdev_mirror_common(has_job_id ? job_id : NULL, bs, target_bs,
+-                           has_replaces, replaces, sync, backing_mode,
++                           has_replaces, replaces, sync, has_bitmap,
++                           bitmap, has_bitmap_mode, bitmap_mode, backing_mode,
+                            zero_target, has_speed, speed,
+                            has_granularity, granularity,
+                            has_buf_size, buf_size,
+diff --git a/tests/test-block-iothread.c b/tests/test-block-iothread.c
+index 0c861809f0..da87a67a57 100644
+--- a/tests/test-block-iothread.c
++++ b/tests/test-block-iothread.c
+@@ -611,8 +611,8 @@ static void test_propagate_mirror(void)
+ 
+     /* Start a mirror job */
+     mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0,
+-                 MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false,
+-                 BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
++                 MIRROR_SYNC_MODE_NONE, NULL, 0, MIRROR_OPEN_BACKING_CHAIN,
++                 false, BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
+                  false, "filter_node", MIRROR_COPY_MODE_BACKGROUND,
+                  &error_abort);
+     job = job_get("job0");
+diff --git a/qapi/block-core.json b/qapi/block-core.json
+index 0b987ad6e3..e2050bab1d 100644
+--- a/qapi/block-core.json
++++ b/qapi/block-core.json
+@@ -2086,10 +2086,19 @@
+ #        (all the disk, only the sectors allocated in the topmost image, or
+ #        only new I/O).
+ #
++# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must
++#          be present for bitmap mode and absent otherwise. The bitmap's
++#          granularity is used instead of @granularity (since 4.1).
++#
++# @bitmap-mode: Specifies the type of data the bitmap should contain after
++#               the operation concludes. Must be present if sync is "bitmap".
++#               Must NOT be present otherwise. (Since 4.1)
++#
+ # @granularity: granularity of the dirty bitmap, default is 64K
+ #               if the image format doesn't have clusters, 4K if the clusters
+ #               are smaller than that, else the cluster size.  Must be a
+-#               power of 2 between 512 and 64M (since 1.4).
++#               power of 2 between 512 and 64M. Must not be specified if
++#               @bitmap is present (since 1.4).
+ #
+ # @buf-size: maximum amount of data in flight from source to
+ #            target (since 1.4).
+@@ -2127,7 +2136,9 @@
+ { 'struct': 'DriveMirror',
+   'data': { '*job-id': 'str', 'device': 'str', 'target': 'str',
+             '*format': 'str', '*node-name': 'str', '*replaces': 'str',
+-            'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
++            'sync': 'MirrorSyncMode', '*bitmap': 'str',
++            '*bitmap-mode': 'BitmapSyncMode',
++            '*mode': 'NewImageMode',
+             '*speed': 'int', '*granularity': 'uint32',
+             '*buf-size': 'int', '*on-source-error': 'BlockdevOnError',
+             '*on-target-error': 'BlockdevOnError',
+@@ -2394,10 +2405,19 @@
+ #        (all the disk, only the sectors allocated in the topmost image, or
+ #        only new I/O).
+ #
++# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must
++#          be present for bitmap mode and absent otherwise. The bitmap's
++#          granularity is used instead of @granularity (since 4.1).
++#
++# @bitmap-mode: Specifies the type of data the bitmap should contain after
++#               the operation concludes. Must be present if sync is "bitmap".
++#               Must NOT be present otherwise. (Since 4.1)
++#
+ # @granularity: granularity of the dirty bitmap, default is 64K
+ #               if the image format doesn't have clusters, 4K if the clusters
+ #               are smaller than that, else the cluster size.  Must be a
+-#               power of 2 between 512 and 64M
++#               power of 2 between 512 and 64M . Must not be specified if
++#               @bitmap is present.
+ #
+ # @buf-size: maximum amount of data in flight from source to
+ #            target
+@@ -2446,7 +2466,8 @@
+ { 'command': 'blockdev-mirror',
+   'data': { '*job-id': 'str', 'device': 'str', 'target': 'str',
+             '*replaces': 'str',
+-            'sync': 'MirrorSyncMode',
++            'sync': 'MirrorSyncMode', '*bitmap': 'str',
++            '*bitmap-mode': 'BitmapSyncMode',
+             '*speed': 'int', '*granularity': 'uint32',
+             '*buf-size': 'int', '*on-source-error': 'BlockdevOnError',
+             '*on-target-error': 'BlockdevOnError',
+-- 
+2.20.1
+
diff --git a/debian/patches/pve/0035-drive-mirror-add-support-for-conditional-and-always-.patch b/debian/patches/pve/0035-drive-mirror-add-support-for-conditional-and-always-.patch
new file mode 100644
index 0000000..3a2e5b9
--- /dev/null
+++ b/debian/patches/pve/0035-drive-mirror-add-support-for-conditional-and-always-.patch
@@ -0,0 +1,83 @@
+From 4c46f8b0f6fd8efba5e4d0b738b4f01ffcb92d33 Mon Sep 17 00:00:00 2001
+From: John Snow <jsnow at redhat.com>
+Date: Tue, 9 Jul 2019 21:12:30 -0400
+Subject: [PATCH qemu 35/39] drive-mirror: add support for conditional and
+ always bitmap sync modes
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Teach mirror two new tricks for using bitmaps:
+
+Always: no matter what, we synchronize the copy_bitmap back to the
+sync_bitmap. In effect, this allows us resume a failed mirror at a later
+date.
+
+Conditional: On success only, we sync the bitmap. This is akin to
+incremental backup modes; we can use this bitmap to later refresh a
+successfully created mirror.
+
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
+---
+ block/mirror.c | 24 ++++++++++++++++++------
+ 1 file changed, 18 insertions(+), 6 deletions(-)
+
+diff --git a/block/mirror.c b/block/mirror.c
+index fd7f574365..40d174a625 100644
+--- a/block/mirror.c
++++ b/block/mirror.c
+@@ -645,8 +645,6 @@ static int mirror_exit_common(Job *job)
+         bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs);
+     }
+ 
+-    bdrv_release_dirty_bitmap(s->dirty_bitmap);
+-
+     /* Make sure that the source BDS doesn't go away during bdrv_replace_node,
+      * before we can call bdrv_drained_end */
+     bdrv_ref(src);
+@@ -731,6 +729,18 @@ static int mirror_exit_common(Job *job)
+     blk_set_perm(bjob->blk, 0, BLK_PERM_ALL, &error_abort);
+     blk_insert_bs(bjob->blk, mirror_top_bs, &error_abort);
+ 
++    if (s->sync_bitmap) {
++        if (s->bitmap_mode == BITMAP_SYNC_MODE_ALWAYS ||
++            (s->bitmap_mode == BITMAP_SYNC_MODE_ON_SUCCESS &&
++             job->ret == 0 && ret == 0)) {
++            /* Success; synchronize copy back to sync. */
++            bdrv_clear_dirty_bitmap(s->sync_bitmap, NULL);
++            bdrv_merge_dirty_bitmap(s->sync_bitmap, s->dirty_bitmap,
++                                    NULL, &error_abort);
++        }
++    }
++    bdrv_release_dirty_bitmap(s->dirty_bitmap);
++
+     bs_opaque->job = NULL;
+ 
+     bdrv_drained_end(src);
+@@ -1562,10 +1572,6 @@ static BlockJob *mirror_start_job(
+                        " sync mode",
+                        MirrorSyncMode_str(sync_mode));
+             return NULL;
+-        } else if (bitmap_mode != BITMAP_SYNC_MODE_NEVER) {
+-            error_setg(errp,
+-                       "Bitmap Sync Mode '%s' is not supported by Mirror",
+-                       BitmapSyncMode_str(bitmap_mode));
+         }
+     } else if (bitmap) {
+         error_setg(errp,
+@@ -1582,6 +1588,12 @@ static BlockJob *mirror_start_job(
+             return NULL;
+         }
+         granularity = bdrv_dirty_bitmap_granularity(bitmap);
++
++        if (bitmap_mode != BITMAP_SYNC_MODE_NEVER) {
++            if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_DEFAULT, errp)) {
++                return NULL;
++            }
++        }
+     } else if (granularity == 0) {
+         granularity = bdrv_get_default_bitmap_granularity(target);
+     }
+-- 
+2.20.1
+
diff --git a/debian/patches/pve/0036-mirror-add-check-for-bitmap-mode-without-bitmap.patch b/debian/patches/pve/0036-mirror-add-check-for-bitmap-mode-without-bitmap.patch
new file mode 100644
index 0000000..879e516
--- /dev/null
+++ b/debian/patches/pve/0036-mirror-add-check-for-bitmap-mode-without-bitmap.patch
@@ -0,0 +1,33 @@
+From daec2f64e120a23a8d263f7d058bd84c48397bc8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= <f.gruenbichler at proxmox.com>
+Date: Mon, 17 Feb 2020 11:59:20 +0100
+Subject: [PATCH qemu 36/39] mirror: add check for bitmap-mode without bitmap
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+as one without the other does not make much sense with the current set
+of modes.
+
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
+---
+ blockdev.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/blockdev.c b/blockdev.c
+index 08285b9e86..768a0b0fd4 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -3861,6 +3861,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
+         if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_ALLOW_RO, errp)) {
+             return;
+         }
++    } else if (has_bitmap_mode) {
++        error_setg(errp, "Cannot specify bitmap sync mode without a bitmap");
++        return;
+     }
+ 
+     if (has_replaces) {
+-- 
+2.20.1
+
diff --git a/debian/patches/pve/0037-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch b/debian/patches/pve/0037-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch
new file mode 100644
index 0000000..40d36e8
--- /dev/null
+++ b/debian/patches/pve/0037-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch
@@ -0,0 +1,45 @@
+From 31ea5889c25012189aabe51157b32cfbe87a63be Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= <f.gruenbichler at proxmox.com>
+Date: Mon, 17 Feb 2020 10:43:22 +0100
+Subject: [PATCH qemu 37/39] mirror: switch to bdrv_dirty_bitmap_merge_internal
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+since sync_bitmap is busy at the point of merging, and we checked access
+beforehand.
+
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
+---
+ block/mirror.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/block/mirror.c b/block/mirror.c
+index 40d174a625..d6aca2874e 100644
+--- a/block/mirror.c
++++ b/block/mirror.c
+@@ -735,8 +735,8 @@ static int mirror_exit_common(Job *job)
+              job->ret == 0 && ret == 0)) {
+             /* Success; synchronize copy back to sync. */
+             bdrv_clear_dirty_bitmap(s->sync_bitmap, NULL);
+-            bdrv_merge_dirty_bitmap(s->sync_bitmap, s->dirty_bitmap,
+-                                    NULL, &error_abort);
++            bdrv_dirty_bitmap_merge_internal(s->sync_bitmap, s->dirty_bitmap,
++                                             NULL, true);
+         }
+     }
+     bdrv_release_dirty_bitmap(s->dirty_bitmap);
+@@ -1727,8 +1727,8 @@ static BlockJob *mirror_start_job(
+     }
+ 
+     if (s->sync_mode == MIRROR_SYNC_MODE_BITMAP) {
+-        bdrv_merge_dirty_bitmap(s->dirty_bitmap, s->sync_bitmap,
+-                                NULL, &local_err);
++        bdrv_dirty_bitmap_merge_internal(s->dirty_bitmap, s->sync_bitmap,
++                                         NULL, true);
+         if (local_err) {
+             goto fail;
+         }
+-- 
+2.20.1
+
diff --git a/debian/patches/pve/0038-iotests-add-test-for-bitmap-mirror.patch b/debian/patches/pve/0038-iotests-add-test-for-bitmap-mirror.patch
new file mode 100644
index 0000000..112250e
--- /dev/null
+++ b/debian/patches/pve/0038-iotests-add-test-for-bitmap-mirror.patch
@@ -0,0 +1,3447 @@
+From 145eea36a4a34919e2551cc14774c47dfb32ac3c Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= <f.gruenbichler at proxmox.com>
+Date: Mon, 17 Feb 2020 10:47:07 +0100
+Subject: [PATCH qemu 38/39] iotests: add test for bitmap mirror
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+heavily based on/practically forked off iotest 257 for bitmap backups,
+but:
+
+- no writes to filter node 'mirror-top' between completion and
+finalization, as those seem to deadlock?
+- no inclusion of not-yet-available full/top sync modes in combination
+with bitmaps
+- extra set of reference/test mirrors to verify that writes in parallel
+with active mirror work
+
+intentionally keeping copyright and ownership of original test case to
+honor provenance.
+
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
+---
+ tests/qemu-iotests/284     |  547 +++++++
+ tests/qemu-iotests/284.out | 2846 ++++++++++++++++++++++++++++++++++++
+ tests/qemu-iotests/group   |    1 +
+ 3 files changed, 3394 insertions(+)
+ create mode 100755 tests/qemu-iotests/284
+ create mode 100644 tests/qemu-iotests/284.out
+
+diff --git a/tests/qemu-iotests/284 b/tests/qemu-iotests/284
+new file mode 100755
+index 0000000000..b04a8e651a
+--- /dev/null
++++ b/tests/qemu-iotests/284
+@@ -0,0 +1,547 @@
++#!/usr/bin/env python3
++#
++# Test bitmap-sync mirrors (incremental, differential, and partials)
++#
++# Copyright (c) 2019 John Snow for Red Hat, Inc.
++#
++# This program is free software; you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation; either version 2 of the License, or
++# (at your option) any later version.
++#
++# This program is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program.  If not, see <http://www.gnu.org/licenses/>.
++#
++# owner=jsnow at redhat.com
++
++import math
++import os
++
++import iotests
++from iotests import log, qemu_img
++
++SIZE = 64 * 1024 * 1024
++GRANULARITY = 64 * 1024
++
++
++class Pattern:
++    def __init__(self, byte, offset, size=GRANULARITY):
++        self.byte = byte
++        self.offset = offset
++        self.size = size
++
++    def bits(self, granularity):
++        lower = self.offset // granularity
++        upper = (self.offset + self.size - 1) // granularity
++        return set(range(lower, upper + 1))
++
++
++class PatternGroup:
++    """Grouping of Pattern objects. Initialize with an iterable of Patterns."""
++    def __init__(self, patterns):
++        self.patterns = patterns
++
++    def bits(self, granularity):
++        """Calculate the unique bits dirtied by this pattern grouping"""
++        res = set()
++        for pattern in self.patterns:
++            res |= pattern.bits(granularity)
++        return res
++
++
++GROUPS = [
++    PatternGroup([
++        # Batch 0: 4 clusters
++        Pattern('0x49', 0x0000000),
++        Pattern('0x6c', 0x0100000),   # 1M
++        Pattern('0x6f', 0x2000000),   # 32M
++        Pattern('0x76', 0x3ff0000)]), # 64M - 64K
++    PatternGroup([
++        # Batch 1: 6 clusters (3 new)
++        Pattern('0x65', 0x0000000),   # Full overwrite
++        Pattern('0x77', 0x00f8000),   # Partial-left (1M-32K)
++        Pattern('0x72', 0x2008000),   # Partial-right (32M+32K)
++        Pattern('0x69', 0x3fe0000)]), # Adjacent-left (64M - 128K)
++    PatternGroup([
++        # Batch 2: 7 clusters (3 new)
++        Pattern('0x74', 0x0010000),   # Adjacent-right
++        Pattern('0x69', 0x00e8000),   # Partial-left  (1M-96K)
++        Pattern('0x6e', 0x2018000),   # Partial-right (32M+96K)
++        Pattern('0x67', 0x3fe0000,
++                2*GRANULARITY)]),     # Overwrite [(64M-128K)-64M)
++    PatternGroup([
++        # Batch 3: 8 clusters (5 new)
++        # Carefully chosen such that nothing re-dirties the one cluster
++        # that copies out successfully before failure in Group #1.
++        Pattern('0xaa', 0x0010000,
++                3*GRANULARITY),       # Overwrite and 2x Adjacent-right
++        Pattern('0xbb', 0x00d8000),   # Partial-left (1M-160K)
++        Pattern('0xcc', 0x2028000),   # Partial-right (32M+160K)
++        Pattern('0xdd', 0x3fc0000)]), # New; leaving a gap to the right
++]
++
++
++class EmulatedBitmap:
++    def __init__(self, granularity=GRANULARITY):
++        self._bits = set()
++        self.granularity = granularity
++
++    def dirty_bits(self, bits):
++        self._bits |= set(bits)
++
++    def dirty_group(self, n):
++        self.dirty_bits(GROUPS[n].bits(self.granularity))
++
++    def clear(self):
++        self._bits = set()
++
++    def clear_bits(self, bits):
++        self._bits -= set(bits)
++
++    def clear_bit(self, bit):
++        self.clear_bits({bit})
++
++    def clear_group(self, n):
++        self.clear_bits(GROUPS[n].bits(self.granularity))
++
++    @property
++    def first_bit(self):
++        return sorted(self.bits)[0]
++
++    @property
++    def bits(self):
++        return self._bits
++
++    @property
++    def count(self):
++        return len(self.bits)
++
++    def compare(self, qmp_bitmap):
++        """
++        Print a nice human-readable message checking that a bitmap as reported
++        by the QMP interface has as many bits set as we expect it to.
++        """
++
++        name = qmp_bitmap.get('name', '(anonymous)')
++        log("= Checking Bitmap {:s} =".format(name))
++
++        want = self.count
++        have = qmp_bitmap['count'] // qmp_bitmap['granularity']
++
++        log("expecting {:d} dirty sectors; have {:d}. {:s}".format(
++            want, have, "OK!" if want == have else "ERROR!"))
++        log('')
++
++
++class Drive:
++    """Represents, vaguely, a drive attached to a VM.
++    Includes format, graph, and device information."""
++
++    def __init__(self, path, vm=None):
++        self.path = path
++        self.vm = vm
++        self.fmt = None
++        self.size = None
++        self.node = None
++
++    def img_create(self, fmt, size):
++        self.fmt = fmt
++        self.size = size
++        iotests.qemu_img_create('-f', self.fmt, self.path, str(self.size))
++
++    def create_target(self, name, fmt, size):
++        basename = os.path.basename(self.path)
++        file_node_name = "file_{}".format(basename)
++        vm = self.vm
++
++        log(vm.command('blockdev-create', job_id='bdc-file-job',
++                       options={
++                           'driver': 'file',
++                           'filename': self.path,
++                           'size': 0,
++                       }))
++        vm.run_job('bdc-file-job')
++        log(vm.command('blockdev-add', driver='file',
++                       node_name=file_node_name, filename=self.path))
++
++        log(vm.command('blockdev-create', job_id='bdc-fmt-job',
++                       options={
++                           'driver': fmt,
++                           'file': file_node_name,
++                           'size': size,
++                       }))
++        vm.run_job('bdc-fmt-job')
++        log(vm.command('blockdev-add', driver=fmt,
++                       node_name=name,
++                       file=file_node_name))
++        self.fmt = fmt
++        self.size = size
++        self.node = name
++
++def blockdev_mirror(vm, device, target, sync, **kwargs):
++    # Strip any arguments explicitly nulled by the caller:
++    kwargs = {key: val for key, val in kwargs.items() if val is not None}
++    result = vm.qmp_log('blockdev-mirror',
++                        device=device,
++                        target=target,
++                        sync=sync,
++                        filter_node_name='mirror-top',
++                        **kwargs)
++    return result
++
++def blockdev_mirror_mktarget(drive, target_id, filepath, sync, **kwargs):
++    target_drive = Drive(filepath, vm=drive.vm)
++    target_drive.create_target(target_id, drive.fmt, drive.size)
++    blockdev_mirror(drive.vm, drive.node, target_id, sync, **kwargs)
++
++def reference_mirror(drive, n, filepath):
++    log("--- Reference mirror #{:d} ---\n".format(n))
++    target_id = "ref_target_{:d}".format(n)
++    job_id = "ref_mirror_{:d}".format(n)
++    blockdev_mirror_mktarget(drive, target_id, filepath, "full",
++                             job_id=job_id)
++    drive.vm.run_job(job_id, auto_dismiss=True)
++    log('')
++
++def mirror(drive, n, filepath, sync, **kwargs):
++    log("--- Test mirror #{:d} ---\n".format(n))
++    target_id = "mirror_target_{:d}".format(n)
++    job_id = "mirror_{:d}".format(n)
++    kwargs.setdefault('auto-finalize', False)
++    blockdev_mirror_mktarget(drive, target_id, filepath, sync,
++                             job_id=job_id, **kwargs)
++    return job_id
++
++def perform_writes(drive, n, filter_node_name=None):
++    log("--- Write #{:d} ---\n".format(n))
++    for pattern in GROUPS[n].patterns:
++        cmd = "write -P{:s} 0x{:07x} 0x{:x}".format(
++            pattern.byte,
++            pattern.offset,
++            pattern.size)
++        log(cmd)
++        log(drive.vm.hmp_qemu_io(filter_node_name or drive.node, cmd))
++    bitmaps = drive.vm.query_bitmaps()
++    log({'bitmaps': bitmaps}, indent=2)
++    log('')
++    return bitmaps
++
++
++def compare_images(image, reference, baseimg=None, expected_match=True):
++    """
++    Print a nice human-readable message comparing these images.
++    """
++    expected_ret = 0 if expected_match else 1
++    if baseimg:
++        assert qemu_img("rebase", "-u", "-b", baseimg, image) == 0
++    ret = qemu_img("compare", image, reference)
++    log('qemu_img compare "{:s}" "{:s}" ==> {:s}, {:s}'.format(
++        image, reference,
++        "Identical" if ret == 0 else "Mismatch",
++        "OK!" if ret == expected_ret else "ERROR!"),
++        filters=[iotests.filter_testfiles])
++
++def test_bitmap_sync(bsync_mode, msync_mode='bitmap', failure=None):
++    """
++    Test bitmap mirror routines.
++
++    :param bsync_mode: Is the Bitmap Sync mode, and can be any of:
++        - on-success: This is the "incremental" style mode. Bitmaps are
++                      synchronized to what was copied out only on success.
++                      (Partial images must be discarded.)
++        - never:      This is the "differential" style mode.
++                      Bitmaps are never synchronized.
++        - always:     This is a "best effort" style mode.
++                      Bitmaps are always synchronized, regardless of failure.
++                      (Partial images must be kept.)
++
++    :param msync_mode: The mirror sync mode to use for the first mirror.
++                       Can be any one of:
++        - bitmap: mirrors based on bitmap manifest.
++        - full:   Full mirrors.
++        - top:    Full mirrors of the top layer only.
++
++    :param failure: Is the (optional) failure mode, and can be any of:
++        - None:         No failure. Test the normative path. Default.
++        - simulated:    Cancel the job right before it completes.
++                        This also tests writes "during" the job.
++        - intermediate: This tests a job that fails mid-process and produces
++                        an incomplete mirror. Testing limitations prevent
++                        testing competing writes.
++    """
++    with iotests.FilePaths(['img', 'bsync1', 'bsync2', 'bsync3',
++                            'fmirror0', 'fmirror1', 'fmirror2', 'fmirror3']) as \
++                            (img_path, bsync1, bsync2, bsync3,
++                             fmirror0, fmirror1, fmirror2, fmirror3), \
++         iotests.VM() as vm:
++
++        mode = "Mode {:s}; Bitmap Sync {:s}".format(msync_mode, bsync_mode)
++        preposition = "with" if failure else "without"
++        cond = "{:s} {:s}".format(preposition,
++                                  "{:s} failure".format(failure) if failure
++                                  else "failure")
++        log("\n=== {:s} {:s} ===\n".format(mode, cond))
++
++        log('--- Preparing image & VM ---\n')
++        drive0 = Drive(img_path, vm=vm)
++        drive0.img_create(iotests.imgfmt, SIZE)
++        vm.add_device("{},id=scsi0".format(iotests.get_virtio_scsi_device()))
++        vm.launch()
++
++        file_config = {
++            'driver': 'file',
++            'filename': drive0.path
++        }
++
++        if failure == 'intermediate':
++            file_config = {
++                'driver': 'blkdebug',
++                'image': file_config,
++                'set-state': [{
++                    'event': 'flush_to_disk',
++                    'state': 1,
++                    'new_state': 2
++                }, {
++                    'event': 'read_aio',
++                    'state': 2,
++                    'new_state': 3
++                }, {
++                    'event': 'read_aio',
++                    'state': 3,
++                    'new_state': 4
++                }],
++                'inject-error': [{
++                    'event': 'read_aio',
++                    'errno': 5,
++                    'state': 3,
++                    'immediately': False,
++                    'once': True
++                }, {
++                    'event': 'read_aio',
++                    'errno': 5,
++                    'state': 4,
++                    'immediately': False,
++                    'once': True
++                }]
++            }
++
++        drive0.node = 'drive0'
++        vm.qmp_log('blockdev-add',
++                   filters=[iotests.filter_qmp_testfiles],
++                   node_name=drive0.node,
++                   driver=drive0.fmt,
++                   file=file_config)
++        log('')
++
++        # 0 - Writes and Reference mirror
++        perform_writes(drive0, 0)
++        reference_mirror(drive0, 0, fmirror0)
++        log('--- Add Bitmap ---\n')
++        vm.qmp_log("block-dirty-bitmap-add", node=drive0.node,
++                   name="bitmap0", granularity=GRANULARITY)
++        log('')
++        ebitmap = EmulatedBitmap()
++
++        # 1 - Writes and Reference mirror
++        bitmaps = perform_writes(drive0, 1)
++        ebitmap.dirty_group(1)
++        bitmap = vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps)
++        ebitmap.compare(bitmap)
++        reference_mirror(drive0, 1, fmirror1)
++
++        # 1 - Test mirror (w/ Optional induced failure)
++        if failure == 'intermediate':
++            # Activate blkdebug induced failure for second-to-next read
++            log(vm.hmp_qemu_io(drive0.node, 'flush'))
++            log('')
++        job = mirror(drive0, 1, bsync1, msync_mode,
++                     bitmap="bitmap0", bitmap_mode=bsync_mode)
++
++        vm.run_job(job, auto_dismiss=True, auto_finalize=False,
++                   cancel=(failure == 'simulated'))
++        bitmaps = vm.query_bitmaps()
++        log({'bitmaps': bitmaps}, indent=2)
++        log('')
++
++        if bsync_mode == 'always':
++            if failure == 'intermediate':
++                # We manage to copy one sector (one bit) before the error.
++                ebitmap.clear_bit(ebitmap.first_bit)
++            else:
++                # successful mirror / cancelled complete mirror
++                ebitmap.clear()
++
++        if bsync_mode == 'on-success' and not failure:
++            ebitmap.clear()
++
++        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
++
++        # 2 - Reference mirror
++        reference_mirror(drive0, 2, fmirror2)
++
++        # 2 - Bitmap mirror with writes before completion
++        job = mirror(drive0, 2, bsync2, "bitmap",
++                     bitmap="bitmap0", bitmap_mode=bsync_mode)
++
++        bitmaps = perform_writes(drive0, 2)
++        ebitmap.dirty_group(2)
++        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
++
++        # don't use run_job as that logs too much even with use_log=False
++        events = [('JOB_STATUS_CHANGE', {'data': {'id': job}})]
++        while True:
++            ev = iotests.filter_qmp_event(vm.events_wait(events, timeout=10))
++            status = ev['data']['status']
++            if status == 'ready':
++                vm.qmp('job-complete', id=job)
++            elif status == 'standby':
++                vm.qmp('job-resume', id=job)
++            elif status == 'pending':
++                vm.qmp('job-finalize', id=job)
++            elif status == 'null':
++                break
++
++        if bsync_mode != 'never':
++            ebitmap.clear()
++
++        bitmaps = vm.query_bitmaps()
++        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
++
++        # 3 - Writes and Reference mirror
++        bitmaps = perform_writes(drive0, 3)
++        ebitmap.dirty_group(3)
++        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
++        reference_mirror(drive0, 3, fmirror3)
++
++        # 3 - Bitmap mirror (In failure modes, this is a recovery.)
++        job = mirror(drive0, 3, bsync3, "bitmap",
++                     bitmap="bitmap0", bitmap_mode=bsync_mode)
++
++        vm.run_job(job, auto_dismiss=True, auto_finalize=False)
++        bitmaps = vm.query_bitmaps()
++
++        log({'bitmaps': bitmaps}, indent=2)
++        log('')
++        if bsync_mode != 'never':
++            ebitmap.clear()
++        ebitmap.compare(vm.get_bitmap(drive0.node, 'bitmap0', bitmaps=bitmaps))
++
++        log('--- Cleanup ---\n')
++        vm.qmp_log("block-dirty-bitmap-remove",
++                   node=drive0.node, name="bitmap0")
++        bitmaps = vm.query_bitmaps()
++        log({'bitmaps': bitmaps}, indent=2)
++        vm.shutdown()
++        log('')
++
++        log('--- Verification ---\n')
++        compare_images(bsync1, fmirror1, baseimg=fmirror0,
++                       expected_match=failure != 'intermediate')
++        if not failure or bsync_mode == 'always':
++            # Always keep the last mirror on success or when using 'always'
++            base = bsync1
++        else:
++            base = fmirror1
++
++        compare_images(bsync2, fmirror2, baseimg=base, expected_match=0)
++        compare_images(bsync3, fmirror3, baseimg=bsync2)
++        compare_images(img_path, fmirror3)
++        log('')
++
++def test_mirror_api():
++    """
++    Test malformed and prohibited invocations of the mirror API.
++    """
++    with iotests.FilePaths(['img', 'bsync1']) as \
++         (img_path, mirror_path), \
++         iotests.VM() as vm:
++
++        log("\n=== API failure tests ===\n")
++        log('--- Preparing image & VM ---\n')
++        drive0 = Drive(img_path, vm=vm)
++        drive0.img_create(iotests.imgfmt, SIZE)
++        vm.add_device("{},id=scsi0".format(iotests.get_virtio_scsi_device()))
++        vm.launch()
++
++        file_config = {
++            'driver': 'file',
++            'filename': drive0.path
++        }
++
++        drive0.node = 'drive0'
++        vm.qmp_log('blockdev-add',
++                   filters=[iotests.filter_qmp_testfiles],
++                   node_name=drive0.node,
++                   driver=drive0.fmt,
++                   file=file_config)
++        log('')
++
++        target0 = Drive(mirror_path, vm=vm)
++        target0.create_target("mirror_target", drive0.fmt, drive0.size)
++        log('')
++
++        vm.qmp_log("block-dirty-bitmap-add", node=drive0.node,
++                   name="bitmap0", granularity=GRANULARITY)
++        log('')
++
++        log('-- Testing invalid QMP commands --\n')
++
++        error_cases = {
++            'incremental': {
++                None:        ['on-success', 'always', 'never', None],
++                'bitmap404': ['on-success', 'always', 'never', None],
++                'bitmap0':   ['always', 'never']
++            },
++            'bitmap': {
++                None:        ['on-success', 'always', 'never', None],
++                'bitmap404': ['on-success', 'always', 'never', None],
++                'bitmap0':   [None],
++            },
++            'full': {
++                None:        ['on-success', 'always', 'never'],
++                'bitmap404': ['on-success', 'always', 'never', None],
++                'bitmap0':   ['on-success', 'always', 'never', None],
++            },
++            'top': {
++                None:        ['on-success', 'always', 'never'],
++                'bitmap404': ['on-success', 'always', 'never', None],
++                'bitmap0':   ['on-success', 'always', 'never', None],
++            },
++            'none': {
++                None:        ['on-success', 'always', 'never'],
++                'bitmap404': ['on-success', 'always', 'never', None],
++                'bitmap0':   ['on-success', 'always', 'never', None],
++            }
++        }
++
++        # Dicts, as always, are not stably-ordered prior to 3.7, so use tuples:
++        for sync_mode in ('incremental', 'bitmap', 'full', 'top', 'none'):
++            log("-- Sync mode {:s} tests --\n".format(sync_mode))
++            for bitmap in (None, 'bitmap404', 'bitmap0'):
++                for policy in error_cases[sync_mode][bitmap]:
++                    blockdev_mirror(drive0.vm, drive0.node, "mirror_target",
++                                    sync_mode, job_id='api_job',
++                                    bitmap=bitmap, bitmap_mode=policy)
++                    log('')
++
++
++def main():
++    for bsync_mode in ("never", "on-success", "always"):
++        for failure in ("simulated", "intermediate", None):
++            test_bitmap_sync(bsync_mode, "bitmap", failure)
++
++#    for sync_mode in ('full', 'top'):
++#        for bsync_mode in ('on-success', 'always'):
++#            for failure in ('simulated', 'intermediate', None):
++#                test_bitmap_sync(bsync_mode, sync_mode, failure)
++
++    test_mirror_api()
++
++if __name__ == '__main__':
++    iotests.script_main(main, supported_fmts=['qcow2'],
++                        supported_protocols=['file'])
+diff --git a/tests/qemu-iotests/284.out b/tests/qemu-iotests/284.out
+new file mode 100644
+index 0000000000..9b7408b6d6
+--- /dev/null
++++ b/tests/qemu-iotests/284.out
+@@ -0,0 +1,2846 @@
++
++=== Mode bitmap; Bitmap Sync never with simulated failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-cancel", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"id": "mirror_1", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 262144,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 655360,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 983040,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 15 dirty sectors; have 15. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 983040, "offset": 983040, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 983040, "offset": 983040, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 983040,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 15 dirty sectors; have 15. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync never with intermediate failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "blkdebug", "image": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "inject-error": [{"errno": 5, "event": "read_aio", "immediately": false, "once": true, "state": 3}, {"errno": 5, "event": "read_aio", "immediately": false, "once": true, "state": 4}], "set-state": [{"event": "flush_to_disk", "new-state": 2, "state": 1}, {"event": "read_aio", "new-state": 3, "state": 2}, {"event": "read_aio", "new-state": 4, "state": 3}]}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++{"return": ""}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"data": {"action": "report", "device": "mirror_1", "operation": "read"}, "event": "BLOCK_JOB_ERROR", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"action": "report", "device": "mirror_1", "operation": "read"}, "event": "BLOCK_JOB_ERROR", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "error": "Input/output error", "len": 393216, "offset": 65536, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 262144,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 655360,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 983040,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 15 dirty sectors; have 15. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 983040, "offset": 983040, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 983040, "offset": 983040, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 983040,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 15 dirty sectors; have 15. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync never without failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"id": "mirror_1", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 262144,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 655360,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 983040,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 15 dirty sectors; have 15. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 983040, "offset": 983040, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 983040, "offset": 983040, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 983040,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 15 dirty sectors; have 15. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync on-success with simulated failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-cancel", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"id": "mirror_1", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 262144,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 655360,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 524288,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 8 dirty sectors; have 8. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync on-success with intermediate failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "blkdebug", "image": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "inject-error": [{"errno": 5, "event": "read_aio", "immediately": false, "once": true, "state": 3}, {"errno": 5, "event": "read_aio", "immediately": false, "once": true, "state": 4}], "set-state": [{"event": "flush_to_disk", "new-state": 2, "state": 1}, {"event": "read_aio", "new-state": 3, "state": 2}, {"event": "read_aio", "new-state": 4, "state": 3}]}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++{"return": ""}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"data": {"action": "report", "device": "mirror_1", "operation": "read"}, "event": "BLOCK_JOB_ERROR", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"action": "report", "device": "mirror_1", "operation": "read"}, "event": "BLOCK_JOB_ERROR", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "error": "Input/output error", "len": 393216, "offset": 65536, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 262144,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 655360,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 10 dirty sectors; have 10. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 524288,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 8 dirty sectors; have 8. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync on-success without failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"id": "mirror_1", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 458752,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 458752,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 7 dirty sectors; have 7. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 524288,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 8 dirty sectors; have 8. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync always with simulated failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-cancel", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"id": "mirror_1", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_CANCELLED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 458752,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 458752,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 7 dirty sectors; have 7. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 524288,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 8 dirty sectors; have 8. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync always with intermediate failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "blkdebug", "image": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "inject-error": [{"errno": 5, "event": "read_aio", "immediately": false, "once": true, "state": 3}, {"errno": 5, "event": "read_aio", "immediately": false, "once": true, "state": 4}], "set-state": [{"event": "flush_to_disk", "new-state": 2, "state": 1}, {"event": "read_aio", "new-state": 3, "state": 2}, {"event": "read_aio", "new-state": 4, "state": 3}]}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++{"return": ""}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"data": {"action": "report", "device": "mirror_1", "operation": "read"}, "event": "BLOCK_JOB_ERROR", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"action": "report", "device": "mirror_1", "operation": "read"}, "event": "BLOCK_JOB_ERROR", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "error": "Input/output error", "len": 393216, "offset": 65536, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 327680,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 5 dirty sectors; have 5. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 262144,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 589824,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 9 dirty sectors; have 9. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 524288,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 8 dirty sectors; have 8. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== Mode bitmap; Bitmap Sync always without failure ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "node-name": "drive0"}}
++{"return": {}}
++
++--- Write #0 ---
++
++write -P0x49 0x0000000 0x10000
++{"return": ""}
++write -P0x6c 0x0100000 0x10000
++{"return": ""}
++write -P0x6f 0x2000000 0x10000
++{"return": ""}
++write -P0x76 0x3ff0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {}
++}
++
++--- Reference mirror #0 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_0", "sync": "full", "target": "ref_target_0"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_0"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_0", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Add Bitmap ---
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++--- Write #1 ---
++
++write -P0x65 0x0000000 0x10000
++{"return": ""}
++write -P0x77 0x00f8000 0x10000
++{"return": ""}
++write -P0x72 0x2008000 0x10000
++{"return": ""}
++write -P0x69 0x3fe0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 393216,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 6 dirty sectors; have 6. OK!
++
++--- Reference mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_1", "sync": "full", "target": "ref_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_1"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_1", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #1 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_1", "sync": "bitmap", "target": "mirror_target_1"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_1"}}
++{"return": {}}
++{"data": {"id": "mirror_1", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_1", "len": 393216, "offset": 393216, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Reference mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_2", "sync": "full", "target": "ref_target_2"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_2"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_2", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #2 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_2", "sync": "bitmap", "target": "mirror_target_2"}}
++{"return": {}}
++--- Write #2 ---
++
++write -P0x74 0x0010000 0x10000
++{"return": ""}
++write -P0x69 0x00e8000 0x10000
++{"return": ""}
++write -P0x6e 0x2018000 0x10000
++{"return": ""}
++write -P0x67 0x3fe0000 0x20000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 458752,
++        "granularity": 65536,
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      },
++      {
++        "busy": true,
++        "count": 458752,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "locked"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 7 dirty sectors; have 7. OK!
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Write #3 ---
++
++write -P0xaa 0x0010000 0x30000
++{"return": ""}
++write -P0xbb 0x00d8000 0x10000
++{"return": ""}
++write -P0xcc 0x2028000 0x10000
++{"return": ""}
++write -P0xdd 0x3fc0000 0x10000
++{"return": ""}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 524288,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 8 dirty sectors; have 8. OK!
++
++--- Reference mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "ref_mirror_3", "sync": "full", "target": "ref_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "ref_mirror_3"}}
++{"return": {}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "ref_mirror_3", "len": 67108864, "offset": 67108864, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++
++--- Test mirror #3 ---
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++{"execute": "blockdev-mirror", "arguments": {"auto-finalize": false, "bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "mirror_3", "sync": "bitmap", "target": "mirror_target_3"}}
++{"return": {}}
++{"execute": "job-complete", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_READY", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"execute": "job-finalize", "arguments": {"id": "mirror_3"}}
++{"return": {}}
++{"data": {"id": "mirror_3", "type": "mirror"}, "event": "BLOCK_JOB_PENDING", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{"data": {"device": "mirror_3", "len": 524288, "offset": 524288, "speed": 0, "type": "mirror"}, "event": "BLOCK_JOB_COMPLETED", "timestamp": {"microseconds": "USECS", "seconds": "SECS"}}
++{
++  "bitmaps": {
++    "drive0": [
++      {
++        "busy": false,
++        "count": 0,
++        "granularity": 65536,
++        "name": "bitmap0",
++        "persistent": false,
++        "recording": true,
++        "status": "active"
++      }
++    ]
++  }
++}
++
++= Checking Bitmap bitmap0 =
++expecting 0 dirty sectors; have 0. OK!
++
++--- Cleanup ---
++
++{"execute": "block-dirty-bitmap-remove", "arguments": {"name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++{
++  "bitmaps": {}
++}
++
++--- Verification ---
++
++qemu_img compare "TEST_DIR/PID-bsync1" "TEST_DIR/PID-fmirror1" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-bsync2" "TEST_DIR/PID-fmirror2" ==> Mismatch, OK!
++qemu_img compare "TEST_DIR/PID-bsync3" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
++
++
++=== API failure tests ===
++
++--- Preparing image & VM ---
++
++{"execute": "blockdev-add", "arguments": {"driver": "qcow2", "file": {"driver": "file", "filename": "TEST_DIR/PID-img"}, "node-name": "drive0"}}
++{"return": {}}
++
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-file-job"}}
++{"return": {}}
++{}
++{}
++{"execute": "job-dismiss", "arguments": {"id": "bdc-fmt-job"}}
++{"return": {}}
++{}
++
++{"execute": "block-dirty-bitmap-add", "arguments": {"granularity": 65536, "name": "bitmap0", "node": "drive0"}}
++{"return": {}}
++
++-- Testing invalid QMP commands --
++
++-- Sync mode incremental tests --
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'incremental' not supported"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'incremental' not supported"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'incremental' not supported"}}
++
++-- Sync mode bitmap tests --
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'bitmap' sync mode"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++-- Sync mode full tests --
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++-- Sync mode top tests --
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++-- Sync mode none tests --
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'none' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'none' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "sync mode 'none' is not compatible with bitmaps"}}
++
++{"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
++{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++
+diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
+index 6b10a6a762..a4db887d5d 100644
+--- a/tests/qemu-iotests/group
++++ b/tests/qemu-iotests/group
+@@ -286,3 +286,4 @@
+ 272 rw
+ 273 backing quick
+ 277 rw quick
++284 rw
+-- 
+2.20.1
+
diff --git a/debian/patches/pve/0039-mirror-move-some-checks-to-qmp.patch b/debian/patches/pve/0039-mirror-move-some-checks-to-qmp.patch
new file mode 100644
index 0000000..4f18849
--- /dev/null
+++ b/debian/patches/pve/0039-mirror-move-some-checks-to-qmp.patch
@@ -0,0 +1,275 @@
+From 1beb9c370adff5e8c674d57e7a17672c0a8eaa2a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= <f.gruenbichler at proxmox.com>
+Date: Mon, 10 Feb 2020 10:15:16 +0100
+Subject: [PATCH qemu 39/39] mirror: move some checks to qmp
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+and assert the passing conditions in block/mirror.c. while incremental
+mode was never available for drive-mirror, it makes the interface more
+uniform w.r.t. backup block jobs.
+
+Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
+---
+ block/mirror.c             | 28 +++------------
+ blockdev.c                 | 29 +++++++++++++++
+ tests/qemu-iotests/284.out | 72 +++++++++++++++++++-------------------
+ 3 files changed, 70 insertions(+), 59 deletions(-)
+
+diff --git a/block/mirror.c b/block/mirror.c
+index d6aca2874e..7a3373fca7 100644
+--- a/block/mirror.c
++++ b/block/mirror.c
+@@ -1562,31 +1562,13 @@ static BlockJob *mirror_start_job(
+     Error *local_err = NULL;
+     int ret;
+ 
+-    if (sync_mode == MIRROR_SYNC_MODE_INCREMENTAL) {
+-        error_setg(errp, "Sync mode '%s' not supported",
+-                   MirrorSyncMode_str(sync_mode));
+-        return NULL;
+-    } else if (sync_mode == MIRROR_SYNC_MODE_BITMAP) {
+-        if (!bitmap) {
+-            error_setg(errp, "Must provide a valid bitmap name for '%s'"
+-                       " sync mode",
+-                       MirrorSyncMode_str(sync_mode));
+-            return NULL;
+-        }
+-    } else if (bitmap) {
+-        error_setg(errp,
+-                   "sync mode '%s' is not compatible with bitmaps",
+-                   MirrorSyncMode_str(sync_mode));
+-        return NULL;
+-    }
++    /* QMP interface protects us from these cases */
++    assert(sync_mode != MIRROR_SYNC_MODE_INCREMENTAL);
++    assert((bitmap && sync_mode == MIRROR_SYNC_MODE_BITMAP) ||
++           (!bitmap && sync_mode != MIRROR_SYNC_MODE_BITMAP));
++    assert(!(bitmap && granularity));
+ 
+     if (bitmap) {
+-        if (granularity) {
+-            error_setg(errp, "granularity (%d)"
+-                       "cannot be specified when a bitmap is provided",
+-                       granularity);
+-            return NULL;
+-        }
+         granularity = bdrv_dirty_bitmap_granularity(bitmap);
+ 
+         if (bitmap_mode != BITMAP_SYNC_MODE_NEVER) {
+diff --git a/blockdev.c b/blockdev.c
+index 768a0b0fd4..4b196a2785 100644
+--- a/blockdev.c
++++ b/blockdev.c
+@@ -3840,7 +3840,36 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs,
+         sync = MIRROR_SYNC_MODE_FULL;
+     }
+ 
++    if ((sync == MIRROR_SYNC_MODE_BITMAP) ||
++        (sync == MIRROR_SYNC_MODE_INCREMENTAL)) {
++        /* done before desugaring 'incremental' to print the right message */
++        if (!has_bitmap) {
++            error_setg(errp, "Must provide a valid bitmap name for "
++                       "'%s' sync mode", MirrorSyncMode_str(sync));
++            return;
++        }
++    }
++
++    if (sync == MIRROR_SYNC_MODE_INCREMENTAL) {
++        if (has_bitmap_mode &&
++            bitmap_mode != BITMAP_SYNC_MODE_ON_SUCCESS) {
++            error_setg(errp, "Bitmap sync mode must be '%s' "
++                       "when using sync mode '%s'",
++                       BitmapSyncMode_str(BITMAP_SYNC_MODE_ON_SUCCESS),
++                       MirrorSyncMode_str(sync));
++            return;
++        }
++        has_bitmap_mode = true;
++        sync = MIRROR_SYNC_MODE_BITMAP;
++        bitmap_mode = BITMAP_SYNC_MODE_ON_SUCCESS;
++    }
++
+     if (has_bitmap) {
++        if (sync != MIRROR_SYNC_MODE_BITMAP) {
++            error_setg(errp, "Sync mode '%s' not supported with bitmap.",
++                       MirrorSyncMode_str(sync));
++            return;
++        }
+         if (granularity) {
+             error_setg(errp, "Granularity and bitmap cannot both be set");
+             return;
+diff --git a/tests/qemu-iotests/284.out b/tests/qemu-iotests/284.out
+index 9b7408b6d6..06a2e29058 100644
+--- a/tests/qemu-iotests/284.out
++++ b/tests/qemu-iotests/284.out
+@@ -2681,45 +2681,45 @@ qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
+ -- Sync mode incremental tests --
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'incremental' sync mode"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'incremental' sync mode"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'incremental' sync mode"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Sync mode 'incremental' not supported"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'incremental' sync mode"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+ {"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Bitmap sync mode must be 'on-success' when using sync mode 'incremental'"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Bitmap sync mode must be 'on-success' when using sync mode 'incremental'"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Sync mode 'incremental' not supported"}}
++{"error": {"class": "GenericError", "desc": "Bitmap sync mode must be 'on-success' when using sync mode 'incremental'"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "incremental", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Sync mode 'incremental' not supported"}}
++{"error": {"class": "GenericError", "desc": "Bitmap sync mode must be 'on-success' when using sync mode 'incremental'"}}
+ 
+ -- Sync mode bitmap tests --
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'bitmap' sync mode"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'bitmap' sync mode"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
++{"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'bitmap' sync mode"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "bitmap", "target": "mirror_target"}}
+ {"error": {"class": "GenericError", "desc": "Must provide a valid bitmap name for 'bitmap' sync mode"}}
+@@ -2751,28 +2751,28 @@ qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
+ {"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "full", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ -- Sync mode top tests --
+ 
+@@ -2786,28 +2786,28 @@ qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
+ {"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'full' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "top", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'full' not supported with bitmap."}}
+ 
+ -- Sync mode none tests --
+ 
+@@ -2821,26 +2821,26 @@ qemu_img compare "TEST_DIR/PID-img" "TEST_DIR/PID-fmirror3" ==> Identical, OK!
+ {"error": {"class": "GenericError", "desc": "Cannot specify bitmap sync mode without a bitmap"}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "Dirty bitmap 'bitmap404' not found"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap404", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "on-success", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'none' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "always", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'none' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "bitmap-mode": "never", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "sync mode 'none' is not compatible with bitmaps"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+ {"execute": "blockdev-mirror", "arguments": {"bitmap": "bitmap0", "device": "drive0", "filter-node-name": "mirror-top", "job-id": "api_job", "sync": "none", "target": "mirror_target"}}
+-{"error": {"class": "GenericError", "desc": "bitmap-mode must be specified if a bitmap is provided"}}
++{"error": {"class": "GenericError", "desc": "Sync mode 'none' not supported with bitmap."}}
+ 
+-- 
+2.20.1
+
diff --git a/debian/patches/series b/debian/patches/series
index 0d4ca37..957eeb7 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -30,3 +30,9 @@ pve/0030-PVE-Backup-add-backup-dump-block-driver.patch
 pve/0031-PVE-Backup-proxmox-backup-patches-for-qemu.patch
 pve/0032-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch
 pve/0033-PVE-Backup-aquire-aio_context-before-calling-backup_.patch
+pve/0034-drive-mirror-add-support-for-sync-bitmap-mode-never.patch
+pve/0035-drive-mirror-add-support-for-conditional-and-always-.patch
+pve/0036-mirror-add-check-for-bitmap-mode-without-bitmap.patch
+pve/0037-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch
+pve/0038-iotests-add-test-for-bitmap-mirror.patch
+pve/0039-mirror-move-some-checks-to-qmp.patch
-- 
2.20.1





More information about the pve-devel mailing list