[PATCH 2/2] qapi: implementation of the block-dirty-bitmap-dump command

Patrik Janoušek posted 2 patches 3 years, 7 months ago
[PATCH 2/2] qapi: implementation of the block-dirty-bitmap-dump command
Posted by Patrik Janoušek 3 years, 7 months ago
Currently, dirty bitmaps are for internal use only and there is
no support for accessing their content from third party-apps.
This patch implements new command block-dirty-bitmap-dump, which
returns content of the dirty bitmap encoded in base64. This is
very useful especially in combination with a drive that uses raw
format because third-party apps can easily use it to create
incremental or differential backup.

Signed-off-by: Patrik Janoušek <pj@patrikjanousek.cz>
---
 block/monitor/bitmap-qmp-cmds.c | 61 +++++++++++++++++++++++++++++++
 qapi/block-core.json            | 64 ++++++++++++++++++++++++++++++++-
 2 files changed, 124 insertions(+), 1 deletion(-)

diff --git a/block/monitor/bitmap-qmp-cmds.c b/block/monitor/bitmap-qmp-cmds.c
index 9f11deec64..7f296e9ba7 100644
--- a/block/monitor/bitmap-qmp-cmds.c
+++ b/block/monitor/bitmap-qmp-cmds.c
@@ -146,6 +146,67 @@ out:
     aio_context_release(aio_context);
 }
 
+BlockDirtyBitmapContent *qmp_block_dirty_bitmap_dump(const char *node,
+                                                     const char *name,
+                                                     bool has_clear, bool clear,
+                                                     Error **errp)
+{
+    BlockDriverState *bs;
+    BdrvDirtyBitmap *bitmap;
+    BlockDirtyBitmapContent *bdbc;
+    HBitmap *hb;
+    AioContext *aio_context;
+
+    if (!name || name[0] == '\0') {
+        error_setg(errp, "Bitmap name cannot be empty");
+        return NULL;
+    }
+
+    bs = bdrv_lookup_bs(node, node, errp);
+    if (!bs) {
+        return NULL;
+    }
+
+    aio_context = bdrv_get_aio_context(bs);
+    aio_context_acquire(aio_context);
+
+    bitmap = block_dirty_bitmap_lookup(node, name, &bs, errp);
+    if (!bitmap || !bs) {
+        return NULL;
+    }
+
+    if (has_clear && clear) {
+        /**
+         * Transactions cannot return value, so "clear" functionality must be
+         * implemented here while holding AiO context
+         */
+
+        bdrv_clear_dirty_bitmap(bitmap, &hb);
+
+        uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap);
+        uint64_t tb_size = hbitmap_serialization_size(hb, 0, bm_size);
+        uint8_t *buf = g_malloc(tb_size);
+
+        hbitmap_serialize_part(hb, buf, 0, bm_size);
+
+        bdbc = g_new0(BlockDirtyBitmapContent, 1);
+        bdbc->content = g_base64_encode((guchar *) buf, tb_size);
+    } else {
+        uint64_t bm_size = bdrv_dirty_bitmap_size(bitmap);
+        uint64_t tb_size = bdrv_dirty_bitmap_serialization_size(bitmap, 0, bm_size);
+        uint8_t *buf = g_malloc(tb_size);
+
+        bdrv_dirty_bitmap_serialize_part(bitmap, buf, 0, bm_size);
+
+        bdbc = g_new0(BlockDirtyBitmapContent, 1);
+        bdbc->content = g_base64_encode((guchar *) buf, tb_size);
+    }
+
+    aio_context_release(aio_context);
+
+    return bdbc;
+}
+
 BdrvDirtyBitmap *block_dirty_bitmap_remove(const char *node, const char *name,
                                            bool release,
                                            BlockDriverState **bitmap_bs,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 04ad80bc1e..cbe3dac384 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -2031,6 +2031,14 @@
 { 'struct': 'BlockDirtyBitmap',
   'data': { 'node': 'str', 'name': 'str' } }
 
+##
+# @BlockDirtyBitmapContent:
+#
+# @content: content of dirty bitmap (encoded in base64)
+##
+{ 'struct': 'BlockDirtyBitmapContent',
+  'data': { 'content': 'str' } }
+
 ##
 # @BlockDirtyBitmapAdd:
 #
@@ -2056,6 +2064,18 @@
   'data': { 'node': 'str', 'name': 'str', '*granularity': 'uint32',
             '*persistent': 'bool', '*disabled': 'bool' } }
 
+##
+# @BlockDirtyBitmapDump:
+#
+# @node: name of device/node which the bitmap is tracking
+#
+# @name: name of the dirty bitmap (must be less than 1024 bytes)
+#
+# @clear: true if bitmap should be cleared after dump
+##
+{ 'struct': 'BlockDirtyBitmapDump',
+  'data': { 'node': 'str', 'name': 'str', '*clear': 'bool' } }
+
 ##
 # @BlockDirtyBitmapMergeSource:
 #
@@ -2086,6 +2106,26 @@
   'data': { 'node': 'str', 'target': 'str',
             'bitmaps': ['BlockDirtyBitmapMergeSource'] } }
 
+##
+# @block-dirty-bitmap-dump:
+#
+# Dump a dirty bitmap with a name on the node.
+#
+# Returns: - nothing on success
+#          - If @node is not a valid block device or node, DeviceNotFound
+#          - If @name is already taken, GenericError with an explanation
+#
+# Example:
+#
+# -> { "execute": "block-dirty-bitmap-dump",
+#      "arguments": { "node": "drive0", "name": "bitmap0" } }
+# <- { "return": { "content": "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFt... (trunc)" } }
+#
+##
+{ 'command': 'block-dirty-bitmap-dump',
+  'data': 'BlockDirtyBitmapDump',
+  'returns': 'BlockDirtyBitmapContent' }
+
 ##
 # @block-dirty-bitmap-add:
 #
@@ -3908,6 +3948,26 @@
             '*x-dirty-bitmap': 'str',
             '*reconnect-delay': 'uint32' } }
 
+##
+# @BlockdevOptionsRawDirtyBitmap:
+#
+# Dirty bitmap options for the raw driver.
+#
+# @name: the name of the dirty bitmap (Since 2.4)
+#
+# @filename: the filename of the dirty bitmap
+#
+# @granularity: granularity of the dirty bitmap in bytes (since 1.4)
+#
+# @persistent: true if the bitmap was stored on disk, is scheduled to be stored
+#              on disk, or both. (since 4.0)
+#
+# @disabled: true if the bitmap should not be loaded (and saved) automatically
+##
+{ 'struct': 'BlockdevOptionsRawDirtyBitmap',
+  'data': {'*name': 'str', 'filename': 'str', 'granularity': 'uint32',
+           'persistent': 'bool', '*disabled': 'bool' } }
+
 ##
 # @BlockdevOptionsRaw:
 #
@@ -3915,12 +3975,14 @@
 #
 # @offset: position where the block device starts
 # @size: the assumed size of the device
+# @dirty-bitmaps: dirty bitmaps of the raw block device
 #
 # Since: 2.9
 ##
 { 'struct': 'BlockdevOptionsRaw',
   'base': 'BlockdevOptionsGenericFormat',
-  'data': { '*offset': 'int', '*size': 'int' } }
+  'data': { '*offset': 'int', '*size': 'int' ,
+            '*dirty-bitmaps': ['BlockdevOptionsRawDirtyBitmap'] } }
 
 ##
 # @BlockdevOptionsThrottle:
-- 
2.31.0


Re: [PATCH 2/2] qapi: implementation of the block-dirty-bitmap-dump command
Posted by Vladimir Sementsov-Ogievskiy 3 years, 7 months ago
20.03.2021 12:32, Patrik Janoušek wrote:
> Currently, dirty bitmaps are for internal use only

As I said, that's not correct: for external use we have bitmap export through NBD protocol.

Protocol description is here https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md  (you need "Metadata querying" section)

And qemu bitmap export through NBD "metadata querying" feature is described here: docs/interop/nbd.txt


-- 
Best regards,
Vladimir

Re: [PATCH 2/2] qapi: implementation of the block-dirty-bitmap-dump command
Posted by Kevin Wolf 3 years, 7 months ago
Am 20.03.2021 um 10:32 hat Patrik Janoušek geschrieben:
> Currently, dirty bitmaps are for internal use only and there is
> no support for accessing their content from third party-apps.
> This patch implements new command block-dirty-bitmap-dump, which
> returns content of the dirty bitmap encoded in base64. This is
> very useful especially in combination with a drive that uses raw
> format because third-party apps can easily use it to create
> incremental or differential backup.
> 
> Signed-off-by: Patrik Janoušek <pj@patrikjanousek.cz>

People have already pointed you to NBD to get the block dirty status
(and they are right), but I think nobody has explained yet why we
decided against a QMP command to return bitmaps.

The problem is that disk images can be huge, and this means that dirty
bitmaps describing them get pretty large, too. So your new QMP command
ends up reading many megabytes from disk and sending it to the socket.
While it's doing all of this I/O, QEMU is blocked and the guest may
appear to be hanging until it completes.

The client would also have to download the whole bitmap even if it is
only interested in specific blocks. (This may or may not be relevant for
you specific use case.)

With the NBD export, the client can query a specific block range and its
request is processed asynchronously in the background while the guest
and the QMP monitor are still available.

Kevin