qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [RFC qemu 0/6] mirror: implement incremental and bitmap modes
@ 2020-02-18 10:07 Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 1/6] drive-mirror: add support for sync=bitmap mode=never Fabian Grünbichler
                   ` (8 more replies)
  0 siblings, 9 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-02-18 10:07 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, qemu-block, John Snow, Markus Armbruster, Max Reitz

picking up on John's in-progress patch series from last summer, this is
a stab at rebasing and adding test cases for the low-hanging fruits:

- bitmap mirror mode with always/on-success/never bitmap sync mode
- incremental mirror mode as sugar for bitmap + on-success

Fabian Grünbichler (4):
  mirror: add check for bitmap-mode without bitmap
  mirror: switch to bdrv_dirty_bitmap_merge_internal
  iotests: add test for bitmap mirror
  mirror: move some checks to QMP

John Snow (2):
  drive-mirror: add support for sync=bitmap mode=never
  drive-mirror: add support for conditional and always bitmap sync modes

 include/block/block_int.h   |    4 +-
 block/mirror.c              |   96 +-
 blockdev.c                  |   71 +-
 tests/test-block-iothread.c |    4 +-
 qapi/block-core.json        |   29 +-
 tests/qemu-iotests/284      |  547 +++++++
 tests/qemu-iotests/284.out  | 2846 +++++++++++++++++++++++++++++++++++
 tests/qemu-iotests/group    |    1 +
 8 files changed, 3567 insertions(+), 31 deletions(-)
 create mode 100755 tests/qemu-iotests/284
 create mode 100644 tests/qemu-iotests/284.out

-- 
2.20.1




^ permalink raw reply	[flat|nested] 20+ messages in thread

* [RFC qemu 1/6] drive-mirror: add support for sync=bitmap mode=never
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
@ 2020-02-18 10:07 ` Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 2/6] drive-mirror: add support for conditional and always bitmap sync modes Fabian Grünbichler
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-02-18 10:07 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, qemu-block, Ma Haocong, John Snow, Markus Armbruster,
	Max Reitz

From: John Snow <jsnow@redhat.com>

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@didichuxing.com>
Signed-off-by: Ma Haocong <mahaocong@didichuxing.com>
Signed-off-by: John Snow <jsnow@redhat.com>
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
Note: this is just rebased, and broken - see patch #4

I intentionally kept those separate as initial RFC, they will be
squashed in the next iteration!

 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 640fb82c78..f0b747b09b 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -1175,7 +1175,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 c6a727cca9..8f7b7ba5eb 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3770,6 +3770,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,
@@ -3788,6 +3792,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;
@@ -3842,6 +3847,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;
@@ -3879,8 +3907,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);
 }
@@ -4021,6 +4049,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,
@@ -4043,6 +4073,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,
@@ -4093,7 +4125,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 13dad62f44..c63708e830 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1976,10 +1976,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).
@@ -2017,7 +2026,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',
@@ -2284,10 +2295,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
@@ -2336,7 +2356,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




^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC qemu 2/6] drive-mirror: add support for conditional and always bitmap sync modes
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 1/6] drive-mirror: add support for sync=bitmap mode=never Fabian Grünbichler
@ 2020-02-18 10:07 ` Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 3/6] mirror: add check for bitmap-mode without bitmap Fabian Grünbichler
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-02-18 10:07 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, qemu-block, John Snow, Markus Armbruster, Max Reitz

From: John Snow <jsnow@redhat.com>

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.
---
Note: original commit by John Snow <jsnow@redhat.com>, but missing SOB -
please ACK/reply and I'll add it.

This is also broken without patch #4, since it's fairly straight-forward
we might even squash all three into a single commit?

 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




^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC qemu 3/6] mirror: add check for bitmap-mode without bitmap
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 1/6] drive-mirror: add support for sync=bitmap mode=never Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 2/6] drive-mirror: add support for conditional and always bitmap sync modes Fabian Grünbichler
@ 2020-02-18 10:07 ` Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 4/6] mirror: switch to bdrv_dirty_bitmap_merge_internal Fabian Grünbichler
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-02-18 10:07 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, qemu-block, John Snow, Markus Armbruster, Max Reitz

as one without the other does not make much sense with the current set
of modes.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
---
 blockdev.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/blockdev.c b/blockdev.c
index 8f7b7ba5eb..23df9f76ba 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3868,6 +3868,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




^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC qemu 4/6] mirror: switch to bdrv_dirty_bitmap_merge_internal
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
                   ` (2 preceding siblings ...)
  2020-02-18 10:07 ` [RFC qemu 3/6] mirror: add check for bitmap-mode without bitmap Fabian Grünbichler
@ 2020-02-18 10:07 ` Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 5/6] iotests: add test for bitmap mirror Fabian Grünbichler
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-02-18 10:07 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, qemu-block, John Snow, Markus Armbruster, Max Reitz

since sync_bitmap is busy at the point of merging, and we checked access
beforehand.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@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




^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC qemu 5/6] iotests: add test for bitmap mirror
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
                   ` (3 preceding siblings ...)
  2020-02-18 10:07 ` [RFC qemu 4/6] mirror: switch to bdrv_dirty_bitmap_merge_internal Fabian Grünbichler
@ 2020-02-18 10:07 ` Fabian Grünbichler
  2020-02-18 10:07 ` [RFC qemu 6/6] mirror: move some checks to QMP Fabian Grünbichler
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-02-18 10:07 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, qemu-block, John Snow, Markus Armbruster, Max Reitz

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 as expected

intentionally keeping copyright and ownership of original test case to
honor provenance.

Signed-off-by: Fabian Grünbichler <f.gruenbichler@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@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 1904223020..818380a8f0 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -290,3 +290,4 @@
 280 rw migration quick
 281 rw quick
 283 auto quick
+284 rw
-- 
2.20.1




^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [RFC qemu 6/6] mirror: move some checks to QMP
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
                   ` (4 preceding siblings ...)
  2020-02-18 10:07 ` [RFC qemu 5/6] iotests: add test for bitmap mirror Fabian Grünbichler
@ 2020-02-18 10:07 ` Fabian Grünbichler
  2020-02-18 10:43 ` [RFC qemu 0/6] mirror: implement incremental and bitmap modes no-reply
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-02-18 10:07 UTC (permalink / raw)
  To: qemu-devel
  Cc: Kevin Wolf, qemu-block, John Snow, Markus Armbruster, Max Reitz

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@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 23df9f76ba..87bde0a4f4 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -3847,7 +3847,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




^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
                   ` (5 preceding siblings ...)
  2020-02-18 10:07 ` [RFC qemu 6/6] mirror: move some checks to QMP Fabian Grünbichler
@ 2020-02-18 10:43 ` no-reply
  2020-02-25 21:54 ` John Snow
  2020-08-21 13:03 ` Max Reitz
  8 siblings, 0 replies; 20+ messages in thread
From: no-reply @ 2020-02-18 10:43 UTC (permalink / raw)
  To: f.gruenbichler; +Cc: kwolf, qemu-block, armbru, qemu-devel, mreitz, jsnow

Patchew URL: https://patchew.org/QEMU/20200218100740.2228521-1-f.gruenbichler@proxmox.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Subject: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
Message-id: 20200218100740.2228521-1-f.gruenbichler@proxmox.com
Type: series

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 * [new tag]         patchew/20200218094402.26625-1-philmd@redhat.com -> patchew/20200218094402.26625-1-philmd@redhat.com
 * [new tag]         patchew/20200218100740.2228521-1-f.gruenbichler@proxmox.com -> patchew/20200218100740.2228521-1-f.gruenbichler@proxmox.com
Switched to a new branch 'test'
50da7dd mirror: move some checks to QMP
2236985 iotests: add test for bitmap mirror
b65aa31 mirror: switch to bdrv_dirty_bitmap_merge_internal
c03e05f mirror: add check for bitmap-mode without bitmap
1e909f2 drive-mirror: add support for conditional and always bitmap sync modes
e7baad3 drive-mirror: add support for sync=bitmap mode=never

=== OUTPUT BEGIN ===
1/6 Checking commit e7baad3a765b (drive-mirror: add support for sync=bitmap mode=never)
2/6 Checking commit 1e909f289147 (drive-mirror: add support for conditional and always bitmap sync modes)
ERROR: Missing Signed-off-by: line(s)

total: 1 errors, 0 warnings, 48 lines checked

Patch 2/6 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.

3/6 Checking commit c03e05f9f733 (mirror: add check for bitmap-mode without bitmap)
4/6 Checking commit b65aa312e64e (mirror: switch to bdrv_dirty_bitmap_merge_internal)
5/6 Checking commit 2236985625be (iotests: add test for bitmap mirror)
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#24: 
new file mode 100755

total: 0 errors, 1 warnings, 3397 lines checked

Patch 5/6 has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
6/6 Checking commit 50da7ddd187c (mirror: move some checks to QMP)
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20200218100740.2228521-1-f.gruenbichler@proxmox.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
                   ` (6 preceding siblings ...)
  2020-02-18 10:43 ` [RFC qemu 0/6] mirror: implement incremental and bitmap modes no-reply
@ 2020-02-25 21:54 ` John Snow
  2020-04-03 11:34   ` Fabian Grünbichler
  2020-08-21 13:03 ` Max Reitz
  8 siblings, 1 reply; 20+ messages in thread
From: John Snow @ 2020-02-25 21:54 UTC (permalink / raw)
  To: Fabian Grünbichler, qemu-devel
  Cc: Kevin Wolf, Markus Armbruster, qemu-block, Max Reitz



On 2/18/20 5:07 AM, Fabian Grünbichler wrote:
> picking up on John's in-progress patch series from last summer, this is
> a stab at rebasing and adding test cases for the low-hanging fruits:
> 
> - bitmap mirror mode with always/on-success/never bitmap sync mode
> - incremental mirror mode as sugar for bitmap + on-success
> 
> Fabian Grünbichler (4):
>   mirror: add check for bitmap-mode without bitmap
>   mirror: switch to bdrv_dirty_bitmap_merge_internal
>   iotests: add test for bitmap mirror
>   mirror: move some checks to QMP
> 
> John Snow (2):
>   drive-mirror: add support for sync=bitmap mode=never
>   drive-mirror: add support for conditional and always bitmap sync modes
> 
>  include/block/block_int.h   |    4 +-
>  block/mirror.c              |   96 +-
>  blockdev.c                  |   71 +-
>  tests/test-block-iothread.c |    4 +-
>  qapi/block-core.json        |   29 +-
>  tests/qemu-iotests/284      |  547 +++++++
>  tests/qemu-iotests/284.out  | 2846 +++++++++++++++++++++++++++++++++++
>  tests/qemu-iotests/group    |    1 +
>  8 files changed, 3567 insertions(+), 31 deletions(-)
>  create mode 100755 tests/qemu-iotests/284
>  create mode 100644 tests/qemu-iotests/284.out
> 

Hi Fabian! Thanks for picking this up. I'm a bit behind on my mail, but
this on my list to look at.

(Hint to other maintainers: It might be a while.)

--js



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-02-25 21:54 ` John Snow
@ 2020-04-03 11:34   ` Fabian Grünbichler
  0 siblings, 0 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-04-03 11:34 UTC (permalink / raw)
  To: John Snow, qemu-devel
  Cc: Kevin Wolf, Markus Armbruster, qemu-block, Max Reitz

On February 25, 2020 10:54 pm, John Snow wrote:
> On 2/18/20 5:07 AM, Fabian Grünbichler wrote:
>> picking up on John's in-progress patch series from last summer, this is
>> a stab at rebasing and adding test cases for the low-hanging fruits:
>> 
>> - bitmap mirror mode with always/on-success/never bitmap sync mode
>> - incremental mirror mode as sugar for bitmap + on-success
>> 
>> Fabian Grünbichler (4):
>>   mirror: add check for bitmap-mode without bitmap
>>   mirror: switch to bdrv_dirty_bitmap_merge_internal
>>   iotests: add test for bitmap mirror
>>   mirror: move some checks to QMP
>> 
>> John Snow (2):
>>   drive-mirror: add support for sync=bitmap mode=never
>>   drive-mirror: add support for conditional and always bitmap sync modes
>> 
>>  include/block/block_int.h   |    4 +-
>>  block/mirror.c              |   96 +-
>>  blockdev.c                  |   71 +-
>>  tests/test-block-iothread.c |    4 +-
>>  qapi/block-core.json        |   29 +-
>>  tests/qemu-iotests/284      |  547 +++++++
>>  tests/qemu-iotests/284.out  | 2846 +++++++++++++++++++++++++++++++++++
>>  tests/qemu-iotests/group    |    1 +
>>  8 files changed, 3567 insertions(+), 31 deletions(-)
>>  create mode 100755 tests/qemu-iotests/284
>>  create mode 100644 tests/qemu-iotests/284.out
>> 
> 
> Hi Fabian! Thanks for picking this up. I'm a bit behind on my mail, but
> this on my list to look at.
> 
> (Hint to other maintainers: It might be a while.)
> 
> --js

Hi,

ping? ;)

(it would be great to get some feedback as we'd like to ship this 
backported to 4.2 or 5.0 with our next point release)

kind regards,
Fabian



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
                   ` (7 preceding siblings ...)
  2020-02-25 21:54 ` John Snow
@ 2020-08-21 13:03 ` Max Reitz
  2020-08-24 15:54   ` John Snow
  2020-09-03 10:13   ` Fabian Grünbichler
  8 siblings, 2 replies; 20+ messages in thread
From: Max Reitz @ 2020-08-21 13:03 UTC (permalink / raw)
  To: Fabian Grünbichler, qemu-devel
  Cc: Kevin Wolf, John Snow, Markus Armbruster, qemu-block


[-- Attachment #1.1: Type: text/plain, Size: 1314 bytes --]

On 18.02.20 11:07, Fabian Grünbichler wrote:

[Sorry :/]

> picking up on John's in-progress patch series from last summer, this is
> a stab at rebasing and adding test cases for the low-hanging fruits:
> 
> - bitmap mirror mode with always/on-success/never bitmap sync mode
> - incremental mirror mode as sugar for bitmap + on-success
> 
> Fabian Grünbichler (4):
>   mirror: add check for bitmap-mode without bitmap
>   mirror: switch to bdrv_dirty_bitmap_merge_internal
>   iotests: add test for bitmap mirror
>   mirror: move some checks to QMP
> 
> John Snow (2):
>   drive-mirror: add support for sync=bitmap mode=never
>   drive-mirror: add support for conditional and always bitmap sync modes

Looks reasonable to me.  I would indeed merge patches 2 through 4 into a
single one, and perhaps switch patches 5 and 6.

Also, we still need an S-o-b from John on patch 2.

I have one question: When the mirror job completes successfully (or is
cancelled “successfully”), the bitmap is always fully cleared when the
job completes, right?  (Unless in “never” mode.)

Not that I think we should change the current implementation of “clear
sync_bitmap; merge dirty_bitmap into sync_bitmap;”.  Just a question for
understanding.


Soo...  What’s the plan?

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-08-21 13:03 ` Max Reitz
@ 2020-08-24 15:54   ` John Snow
  2020-09-03 10:13   ` Fabian Grünbichler
  1 sibling, 0 replies; 20+ messages in thread
From: John Snow @ 2020-08-24 15:54 UTC (permalink / raw)
  To: Max Reitz, Fabian Grünbichler, qemu-devel
  Cc: Kevin Wolf, Markus Armbruster, qemu-block

On 8/21/20 9:03 AM, Max Reitz wrote:
> On 18.02.20 11:07, Fabian Grünbichler wrote:
> 
> [Sorry :/]
> 
>> picking up on John's in-progress patch series from last summer, this is
>> a stab at rebasing and adding test cases for the low-hanging fruits:
>>
>> - bitmap mirror mode with always/on-success/never bitmap sync mode
>> - incremental mirror mode as sugar for bitmap + on-success
>>
>> Fabian Grünbichler (4):
>>    mirror: add check for bitmap-mode without bitmap
>>    mirror: switch to bdrv_dirty_bitmap_merge_internal
>>    iotests: add test for bitmap mirror
>>    mirror: move some checks to QMP
>>
>> John Snow (2):
>>    drive-mirror: add support for sync=bitmap mode=never
>>    drive-mirror: add support for conditional and always bitmap sync modes
> 
> Looks reasonable to me.  I would indeed merge patches 2 through 4 into a
> single one, and perhaps switch patches 5 and 6.
> 
> Also, we still need an S-o-b from John on patch 2.
> 

Whoops! Will do.

> I have one question: When the mirror job completes successfully (or is
> cancelled “successfully”), the bitmap is always fully cleared when the
> job completes, right?  (Unless in “never” mode.)
> 

I don't remember personally, it's been a minute ...

> Not that I think we should change the current implementation of “clear
> sync_bitmap; merge dirty_bitmap into sync_bitmap;”.  Just a question for
> understanding.
> 
> 
> Soo...  What’s the plan?
> 
> Max
> 



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-08-21 13:03 ` Max Reitz
  2020-08-24 15:54   ` John Snow
@ 2020-09-03 10:13   ` Fabian Grünbichler
  2020-09-03 11:04     ` Max Reitz
  1 sibling, 1 reply; 20+ messages in thread
From: Fabian Grünbichler @ 2020-09-03 10:13 UTC (permalink / raw)
  To: Max Reitz, qemu-devel
  Cc: Kevin Wolf, John Snow, Markus Armbruster, qemu-block

On August 21, 2020 3:03 pm, Max Reitz wrote:
> On 18.02.20 11:07, Fabian Grünbichler wrote:
> 
> [Sorry :/]

same, I've been meaning to ping/pick this back up but other stuff got in 
the way. so thanks for the reminder to get this upstream ;)

> 
>> picking up on John's in-progress patch series from last summer, this is
>> a stab at rebasing and adding test cases for the low-hanging fruits:
>> 
>> - bitmap mirror mode with always/on-success/never bitmap sync mode
>> - incremental mirror mode as sugar for bitmap + on-success
>> 
>> Fabian Grünbichler (4):
>>   mirror: add check for bitmap-mode without bitmap
>>   mirror: switch to bdrv_dirty_bitmap_merge_internal
>>   iotests: add test for bitmap mirror
>>   mirror: move some checks to QMP
>> 
>> John Snow (2):
>>   drive-mirror: add support for sync=bitmap mode=never
>>   drive-mirror: add support for conditional and always bitmap sync modes
> 
> Looks reasonable to me.  I would indeed merge patches 2 through 4 into a
> single one, and perhaps switch patches 5 and 6.
> 
> Also, we still need an S-o-b from John on patch 2.
> 
> I have one question: When the mirror job completes successfully (or is
> cancelled “successfully”), the bitmap is always fully cleared when the
> job completes, right?  (Unless in “never” mode.)

I have to take a closer look as well, it's been a while ;) IIRC the idea 
was that failed mirrors would allow re-using the bitmap for a next 
attempt, unless the mode is always. we are not using that feature (yet) 
though (see below).

> Not that I think we should change the current implementation of “clear
> sync_bitmap; merge dirty_bitmap into sync_bitmap;”.  Just a question for
> understanding.
> 
> 
> Soo...  What’s the plan?

I'll rebase, squash as suggested and resend next week! I am not sure how 
the S-O-B by John is supposed to enter the mix - should I just include 
it in the squashed patch (which would be partly authored, but 
not-yet-signed-off by him otherwise?)? do you pick it up once he's 
replied with one?

FWIW, with been running with this for quite a while downstream with no 
issues, but we are only using the following part:

- create bitmap(s)
- (incrementally) replicate storage volume(s) out of band (using ZFS)
- incrementally drive mirror as part of a live migration of VM
- drop bitmap(s)

so no fancy semi-permanent bitmap that gets re-used here. I've been 
toying with implementing some sort of generic replication feature akin 
to zfs send/recv though, but given that we only have built-in persistent 
bitmaps with qcow2 and the chance of some other tool or the user messing 
up other image formats is high, the safe usage scenarios are a bit 
limited.

we do use such long-running bitmaps for our new backup driver though, 
and it works quite well there!



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-09-03 10:13   ` Fabian Grünbichler
@ 2020-09-03 11:04     ` Max Reitz
  2020-09-03 12:38       ` Kevin Wolf
  0 siblings, 1 reply; 20+ messages in thread
From: Max Reitz @ 2020-09-03 11:04 UTC (permalink / raw)
  To: Fabian Grünbichler, qemu-devel
  Cc: Kevin Wolf, John Snow, Markus Armbruster, qemu-block


[-- Attachment #1.1: Type: text/plain, Size: 3375 bytes --]

On 03.09.20 12:13, Fabian Grünbichler wrote:
> On August 21, 2020 3:03 pm, Max Reitz wrote:
>> On 18.02.20 11:07, Fabian Grünbichler wrote:
>>
>> [Sorry :/]
> 
> same, I've been meaning to ping/pick this back up but other stuff got in 
> the way. so thanks for the reminder to get this upstream ;)
> 
>>
>>> picking up on John's in-progress patch series from last summer, this is
>>> a stab at rebasing and adding test cases for the low-hanging fruits:
>>>
>>> - bitmap mirror mode with always/on-success/never bitmap sync mode
>>> - incremental mirror mode as sugar for bitmap + on-success
>>>
>>> Fabian Grünbichler (4):
>>>   mirror: add check for bitmap-mode without bitmap
>>>   mirror: switch to bdrv_dirty_bitmap_merge_internal
>>>   iotests: add test for bitmap mirror
>>>   mirror: move some checks to QMP
>>>
>>> John Snow (2):
>>>   drive-mirror: add support for sync=bitmap mode=never
>>>   drive-mirror: add support for conditional and always bitmap sync modes
>>
>> Looks reasonable to me.  I would indeed merge patches 2 through 4 into a
>> single one, and perhaps switch patches 5 and 6.
>>
>> Also, we still need an S-o-b from John on patch 2.
>>
>> I have one question: When the mirror job completes successfully (or is
>> cancelled “successfully”), the bitmap is always fully cleared when the
>> job completes, right?  (Unless in “never” mode.)
> 
> I have to take a closer look as well, it's been a while ;)

No problem, I’m... *cough* not exactly in a hurry.

> IIRC the idea 
> was that failed mirrors would allow re-using the bitmap for a next 
> attempt, unless the mode is always. we are not using that feature (yet) 
> though (see below).
> 
>> Not that I think we should change the current implementation of “clear
>> sync_bitmap; merge dirty_bitmap into sync_bitmap;”.  Just a question for
>> understanding.
>>
>>
>> Soo...  What’s the plan?
> 
> I'll rebase, squash as suggested and resend next week!

OK :)

> I am not sure how 
> the S-O-B by John is supposed to enter the mix - should I just include 
> it in the squashed patch (which would be partly authored, but 
> not-yet-signed-off by him otherwise?)?

I’m not too sure on the proceedings, actually.  I think it should be
fine if you put his S-o-b there, as long as your patch is somehow based
on a patch that he sent earlier with his S-o-b underneath.  But I’m not
sure.

> do you pick it up once he's replied with one?

Yes, that’s what would be best.

> FWIW, with been running with this for quite a while downstream with no 
> issues, but we are only using the following part:
> 
> - create bitmap(s)
> - (incrementally) replicate storage volume(s) out of band (using ZFS)
> - incrementally drive mirror as part of a live migration of VM
> - drop bitmap(s)
> 
> so no fancy semi-permanent bitmap that gets re-used here. I've been 
> toying with implementing some sort of generic replication feature akin 
> to zfs send/recv though, but given that we only have built-in persistent 
> bitmaps with qcow2 and the chance of some other tool or the user messing 
> up other image formats is high, the safe usage scenarios are a bit 
> limited.

OK.

> we do use such long-running bitmaps for our new backup driver though, 
> and it works quite well there!

Good! :)

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-09-03 11:04     ` Max Reitz
@ 2020-09-03 12:38       ` Kevin Wolf
  2020-09-03 12:57         ` Max Reitz
  0 siblings, 1 reply; 20+ messages in thread
From: Kevin Wolf @ 2020-09-03 12:38 UTC (permalink / raw)
  To: Max Reitz
  Cc: qemu-block, qemu-devel, Markus Armbruster,
	Fabian Grünbichler, John Snow

[-- Attachment #1: Type: text/plain, Size: 1129 bytes --]

Am 03.09.2020 um 13:04 hat Max Reitz geschrieben:
> On 03.09.20 12:13, Fabian Grünbichler wrote:
> > On August 21, 2020 3:03 pm, Max Reitz wrote:
> >> On 18.02.20 11:07, Fabian Grünbichler wrote:
> > I am not sure how 
> > the S-O-B by John is supposed to enter the mix - should I just include 
> > it in the squashed patch (which would be partly authored, but 
> > not-yet-signed-off by him otherwise?)?
> 
> I’m not too sure on the proceedings, actually.  I think it should be
> fine if you put his S-o-b there, as long as your patch is somehow based
> on a patch that he sent earlier with his S-o-b underneath.  But I’m not
> sure.

Signed-off-by means that John certifies the DCO for the patch (at least
the original version that you possibly modified), so you cannot just add
it without asking him.

John should reply with a Signed-off-by line to the patch in question.
Then you (Fabian) can add it in the next version of the series (if I
understand correctly, you're going to respin anyway).

I see that patch 2 doesn't have any S-o-b at all. It should have both
John's and Fabian's.

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-09-03 12:38       ` Kevin Wolf
@ 2020-09-03 12:57         ` Max Reitz
  2020-09-03 13:23           ` Kevin Wolf
  0 siblings, 1 reply; 20+ messages in thread
From: Max Reitz @ 2020-09-03 12:57 UTC (permalink / raw)
  To: Kevin Wolf
  Cc: qemu-block, qemu-devel, Markus Armbruster,
	Fabian Grünbichler, John Snow


[-- Attachment #1.1: Type: text/plain, Size: 1378 bytes --]

On 03.09.20 14:38, Kevin Wolf wrote:
> Am 03.09.2020 um 13:04 hat Max Reitz geschrieben:
>> On 03.09.20 12:13, Fabian Grünbichler wrote:
>>> On August 21, 2020 3:03 pm, Max Reitz wrote:
>>>> On 18.02.20 11:07, Fabian Grünbichler wrote:
>>> I am not sure how 
>>> the S-O-B by John is supposed to enter the mix - should I just include 
>>> it in the squashed patch (which would be partly authored, but 
>>> not-yet-signed-off by him otherwise?)?
>>
>> I’m not too sure on the proceedings, actually.  I think it should be
>> fine if you put his S-o-b there, as long as your patch is somehow based
>> on a patch that he sent earlier with his S-o-b underneath.  But I’m not
>> sure.
> 
> Signed-off-by means that John certifies the DCO for the patch (at least
> the original version that you possibly modified), so you cannot just add
> it without asking him.

But what if you take a patch from someone and heavily modify it –
wouldn’t you keep the original S-o-b and explain the modifications in
the commit message?

Max

> John should reply with a Signed-off-by line to the patch in question.
> Then you (Fabian) can add it in the next version of the series (if I
> understand correctly, you're going to respin anyway).
> 
> I see that patch 2 doesn't have any S-o-b at all. It should have both
> John's and Fabian's.
> 
> Kevin
> 



[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-09-03 12:57         ` Max Reitz
@ 2020-09-03 13:23           ` Kevin Wolf
  2020-09-03 13:36             ` Fabian Grünbichler
  0 siblings, 1 reply; 20+ messages in thread
From: Kevin Wolf @ 2020-09-03 13:23 UTC (permalink / raw)
  To: Max Reitz
  Cc: qemu-block, qemu-devel, Markus Armbruster,
	Fabian Grünbichler, John Snow

[-- Attachment #1: Type: text/plain, Size: 1506 bytes --]

Am 03.09.2020 um 14:57 hat Max Reitz geschrieben:
> On 03.09.20 14:38, Kevin Wolf wrote:
> > Am 03.09.2020 um 13:04 hat Max Reitz geschrieben:
> >> On 03.09.20 12:13, Fabian Grünbichler wrote:
> >>> On August 21, 2020 3:03 pm, Max Reitz wrote:
> >>>> On 18.02.20 11:07, Fabian Grünbichler wrote:
> >>> I am not sure how 
> >>> the S-O-B by John is supposed to enter the mix - should I just include 
> >>> it in the squashed patch (which would be partly authored, but 
> >>> not-yet-signed-off by him otherwise?)?
> >>
> >> I’m not too sure on the proceedings, actually.  I think it should be
> >> fine if you put his S-o-b there, as long as your patch is somehow based
> >> on a patch that he sent earlier with his S-o-b underneath.  But I’m not
> >> sure.
> > 
> > Signed-off-by means that John certifies the DCO for the patch (at least
> > the original version that you possibly modified), so you cannot just add
> > it without asking him.
> 
> But what if you take a patch from someone and heavily modify it –
> wouldn’t you keep the original S-o-b and explain the modifications in
> the commit message?

Ah, if that patch already had a S-o-b, then yes. You keep it not only to
show who touched the patch, but also because your own S-o-b depends on
the one from the original author (you only have the rights to contribute
it because the original author had them and could pass them on to you).

I thought it was based on a patch that came without S-o-b.

Kevin

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-09-03 13:23           ` Kevin Wolf
@ 2020-09-03 13:36             ` Fabian Grünbichler
  2020-09-03 13:43               ` Kevin Wolf
  2020-09-03 13:51               ` Max Reitz
  0 siblings, 2 replies; 20+ messages in thread
From: Fabian Grünbichler @ 2020-09-03 13:36 UTC (permalink / raw)
  To: Kevin Wolf, Max Reitz
  Cc: John Snow, Markus Armbruster, qemu-block, qemu-devel

On September 3, 2020 3:23 pm, Kevin Wolf wrote:
> Am 03.09.2020 um 14:57 hat Max Reitz geschrieben:
>> On 03.09.20 14:38, Kevin Wolf wrote:
>> > Am 03.09.2020 um 13:04 hat Max Reitz geschrieben:
>> >> On 03.09.20 12:13, Fabian Grünbichler wrote:
>> >>> On August 21, 2020 3:03 pm, Max Reitz wrote:
>> >>>> On 18.02.20 11:07, Fabian Grünbichler wrote:
>> >>> I am not sure how 
>> >>> the S-O-B by John is supposed to enter the mix - should I just include 
>> >>> it in the squashed patch (which would be partly authored, but 
>> >>> not-yet-signed-off by him otherwise?)?
>> >>
>> >> I’m not too sure on the proceedings, actually.  I think it should be
>> >> fine if you put his S-o-b there, as long as your patch is somehow based
>> >> on a patch that he sent earlier with his S-o-b underneath.  But I’m not
>> >> sure.
>> > 
>> > Signed-off-by means that John certifies the DCO for the patch (at least
>> > the original version that you possibly modified), so you cannot just add
>> > it without asking him.
>> 
>> But what if you take a patch from someone and heavily modify it –
>> wouldn’t you keep the original S-o-b and explain the modifications in
>> the commit message?
> 
> Ah, if that patch already had a S-o-b, then yes. You keep it not only to
> show who touched the patch, but also because your own S-o-b depends on
> the one from the original author (you only have the rights to contribute
> it because the original author had them and could pass them on to you).
> 
> I thought it was based on a patch that came without S-o-b.

it is (taken from John's git, with his approval, and implicit admission 
that S-O-B is just missing because it was a WIP branch/tree that I 
started from). that was also the reason why I kept that patch unmodified 
and sent my modifications as patches on-top, to make it easier for John 
to verify that that one patch is his original WIP one and add this 
missing S-O-B ;)



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-09-03 13:36             ` Fabian Grünbichler
@ 2020-09-03 13:43               ` Kevin Wolf
  2020-09-03 13:51               ` Max Reitz
  1 sibling, 0 replies; 20+ messages in thread
From: Kevin Wolf @ 2020-09-03 13:43 UTC (permalink / raw)
  To: Fabian Grünbichler
  Cc: qemu-devel, John Snow, Markus Armbruster, qemu-block, Max Reitz

Am 03.09.2020 um 15:36 hat Fabian Grünbichler geschrieben:
> On September 3, 2020 3:23 pm, Kevin Wolf wrote:
> > Am 03.09.2020 um 14:57 hat Max Reitz geschrieben:
> >> On 03.09.20 14:38, Kevin Wolf wrote:
> >> > Am 03.09.2020 um 13:04 hat Max Reitz geschrieben:
> >> >> On 03.09.20 12:13, Fabian Grünbichler wrote:
> >> >>> On August 21, 2020 3:03 pm, Max Reitz wrote:
> >> >>>> On 18.02.20 11:07, Fabian Grünbichler wrote:
> >> >>> I am not sure how 
> >> >>> the S-O-B by John is supposed to enter the mix - should I just include 
> >> >>> it in the squashed patch (which would be partly authored, but 
> >> >>> not-yet-signed-off by him otherwise?)?
> >> >>
> >> >> I’m not too sure on the proceedings, actually.  I think it should be
> >> >> fine if you put his S-o-b there, as long as your patch is somehow based
> >> >> on a patch that he sent earlier with his S-o-b underneath.  But I’m not
> >> >> sure.
> >> > 
> >> > Signed-off-by means that John certifies the DCO for the patch (at least
> >> > the original version that you possibly modified), so you cannot just add
> >> > it without asking him.
> >> 
> >> But what if you take a patch from someone and heavily modify it –
> >> wouldn’t you keep the original S-o-b and explain the modifications in
> >> the commit message?
> > 
> > Ah, if that patch already had a S-o-b, then yes. You keep it not only to
> > show who touched the patch, but also because your own S-o-b depends on
> > the one from the original author (you only have the rights to contribute
> > it because the original author had them and could pass them on to you).
> > 
> > I thought it was based on a patch that came without S-o-b.
> 
> it is (taken from John's git, with his approval, and implicit admission 
> that S-O-B is just missing because it was a WIP branch/tree that I 
> started from). that was also the reason why I kept that patch unmodified 
> and sent my modifications as patches on-top, to make it easier for John 
> to verify that that one patch is his original WIP one and add this 
> missing S-O-B ;)

Yeah, then John should just reply to the patch and add the S-o-b.

Complications like this are why I always use 'git commit -s' and have it
already in my development branches rather than only adding it when
preparing the patch emails to send.

Kevin



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [RFC qemu 0/6] mirror: implement incremental and bitmap modes
  2020-09-03 13:36             ` Fabian Grünbichler
  2020-09-03 13:43               ` Kevin Wolf
@ 2020-09-03 13:51               ` Max Reitz
  1 sibling, 0 replies; 20+ messages in thread
From: Max Reitz @ 2020-09-03 13:51 UTC (permalink / raw)
  To: Fabian Grünbichler, Kevin Wolf
  Cc: John Snow, Markus Armbruster, qemu-block, qemu-devel


[-- Attachment #1.1: Type: text/plain, Size: 2134 bytes --]

On 03.09.20 15:36, Fabian Grünbichler wrote:
> On September 3, 2020 3:23 pm, Kevin Wolf wrote:
>> Am 03.09.2020 um 14:57 hat Max Reitz geschrieben:
>>> On 03.09.20 14:38, Kevin Wolf wrote:
>>>> Am 03.09.2020 um 13:04 hat Max Reitz geschrieben:
>>>>> On 03.09.20 12:13, Fabian Grünbichler wrote:
>>>>>> On August 21, 2020 3:03 pm, Max Reitz wrote:
>>>>>>> On 18.02.20 11:07, Fabian Grünbichler wrote:
>>>>>> I am not sure how 
>>>>>> the S-O-B by John is supposed to enter the mix - should I just include 
>>>>>> it in the squashed patch (which would be partly authored, but 
>>>>>> not-yet-signed-off by him otherwise?)?
>>>>>
>>>>> I’m not too sure on the proceedings, actually.  I think it should be
>>>>> fine if you put his S-o-b there, as long as your patch is somehow based
>>>>> on a patch that he sent earlier with his S-o-b underneath.  But I’m not
>>>>> sure.
>>>>
>>>> Signed-off-by means that John certifies the DCO for the patch (at least
>>>> the original version that you possibly modified), so you cannot just add
>>>> it without asking him.
>>>
>>> But what if you take a patch from someone and heavily modify it –
>>> wouldn’t you keep the original S-o-b and explain the modifications in
>>> the commit message?
>>
>> Ah, if that patch already had a S-o-b, then yes. You keep it not only to
>> show who touched the patch, but also because your own S-o-b depends on
>> the one from the original author (you only have the rights to contribute
>> it because the original author had them and could pass them on to you).
>>
>> I thought it was based on a patch that came without S-o-b.
> 
> it is (taken from John's git, with his approval, and implicit admission 
> that S-O-B is just missing because it was a WIP branch/tree that I 
> started from). that was also the reason why I kept that patch unmodified 
> and sent my modifications as patches on-top, to make it easier for John 
> to verify that that one patch is his original WIP one and add this 
> missing S-O-B ;)

OK, I see now.  I thought the S-o-b got lost somewhere, but was present
originally.

Max


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2020-09-03 13:52 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-02-18 10:07 [RFC qemu 0/6] mirror: implement incremental and bitmap modes Fabian Grünbichler
2020-02-18 10:07 ` [RFC qemu 1/6] drive-mirror: add support for sync=bitmap mode=never Fabian Grünbichler
2020-02-18 10:07 ` [RFC qemu 2/6] drive-mirror: add support for conditional and always bitmap sync modes Fabian Grünbichler
2020-02-18 10:07 ` [RFC qemu 3/6] mirror: add check for bitmap-mode without bitmap Fabian Grünbichler
2020-02-18 10:07 ` [RFC qemu 4/6] mirror: switch to bdrv_dirty_bitmap_merge_internal Fabian Grünbichler
2020-02-18 10:07 ` [RFC qemu 5/6] iotests: add test for bitmap mirror Fabian Grünbichler
2020-02-18 10:07 ` [RFC qemu 6/6] mirror: move some checks to QMP Fabian Grünbichler
2020-02-18 10:43 ` [RFC qemu 0/6] mirror: implement incremental and bitmap modes no-reply
2020-02-25 21:54 ` John Snow
2020-04-03 11:34   ` Fabian Grünbichler
2020-08-21 13:03 ` Max Reitz
2020-08-24 15:54   ` John Snow
2020-09-03 10:13   ` Fabian Grünbichler
2020-09-03 11:04     ` Max Reitz
2020-09-03 12:38       ` Kevin Wolf
2020-09-03 12:57         ` Max Reitz
2020-09-03 13:23           ` Kevin Wolf
2020-09-03 13:36             ` Fabian Grünbichler
2020-09-03 13:43               ` Kevin Wolf
2020-09-03 13:51               ` Max Reitz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).