* [Qemu-devel] [PATCH v12 01/17] qapi: Add optional field "name" to block dirty bitmap
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove John Snow
` (16 subsequent siblings)
17 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
From: Fam Zheng <famz@redhat.com>
This field will be set for user created dirty bitmap. Also pass in an
error pointer to bdrv_create_dirty_bitmap, so when a name is already
taken on this BDS, it can report an error message. This is not global
check, two BDSes can have dirty bitmap with a common name.
Implemented bdrv_find_dirty_bitmap to find a dirty bitmap by name, will
be used later when other QMP commands want to reference dirty bitmap by
name.
Add bdrv_dirty_bitmap_make_anon. This unsets the name of dirty bitmap.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
---
block.c | 32 +++++++++++++++++++++++++++++++-
block/mirror.c | 2 +-
include/block/block.h | 7 ++++++-
migration/block.c | 2 +-
qapi/block-core.json | 4 +++-
5 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/block.c b/block.c
index 210fd5f..c9c2359 100644
--- a/block.c
+++ b/block.c
@@ -53,6 +53,7 @@
struct BdrvDirtyBitmap {
HBitmap *bitmap;
+ char *name;
QLIST_ENTRY(BdrvDirtyBitmap) list;
};
@@ -5366,7 +5367,28 @@ bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov)
return true;
}
-BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs, int granularity,
+BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs, const char *name)
+{
+ BdrvDirtyBitmap *bm;
+
+ assert(name);
+ QLIST_FOREACH(bm, &bs->dirty_bitmaps, list) {
+ if (bm->name && !strcmp(name, bm->name)) {
+ return bm;
+ }
+ }
+ return NULL;
+}
+
+void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
+{
+ g_free(bitmap->name);
+ bitmap->name = NULL;
+}
+
+BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
+ int granularity,
+ const char *name,
Error **errp)
{
int64_t bitmap_size;
@@ -5374,6 +5396,10 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs, int granularity,
assert((granularity & (granularity - 1)) == 0);
+ if (name && bdrv_find_dirty_bitmap(bs, name)) {
+ error_setg(errp, "Bitmap already exists: %s", name);
+ return NULL;
+ }
granularity >>= BDRV_SECTOR_BITS;
assert(granularity);
bitmap_size = bdrv_nb_sectors(bs);
@@ -5384,6 +5410,7 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs, int granularity,
}
bitmap = g_new0(BdrvDirtyBitmap, 1);
bitmap->bitmap = hbitmap_alloc(bitmap_size, ffs(granularity) - 1);
+ bitmap->name = g_strdup(name);
QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
return bitmap;
}
@@ -5395,6 +5422,7 @@ void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
if (bm == bitmap) {
QLIST_REMOVE(bitmap, list);
hbitmap_free(bitmap->bitmap);
+ g_free(bitmap->name);
g_free(bitmap);
return;
}
@@ -5413,6 +5441,8 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
info->count = bdrv_get_dirty_count(bs, bm);
info->granularity =
((int64_t) BDRV_SECTOR_SIZE << hbitmap_granularity(bm->bitmap));
+ info->has_name = !!bm->name;
+ info->name = g_strdup(bm->name);
entry->value = info;
*plist = entry;
plist = &entry->next;
diff --git a/block/mirror.c b/block/mirror.c
index 4056164..f073ad7 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -703,7 +703,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
s->granularity = granularity;
s->buf_size = MAX(buf_size, granularity);
- s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, errp);
+ s->dirty_bitmap = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
if (!s->dirty_bitmap) {
return;
}
diff --git a/include/block/block.h b/include/block/block.h
index 321295e..0988b77 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -438,8 +438,13 @@ bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov);
struct HBitmapIter;
typedef struct BdrvDirtyBitmap BdrvDirtyBitmap;
-BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs, int granularity,
+BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
+ int granularity,
+ const char *name,
Error **errp);
+BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
+ const char *name);
+void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
diff --git a/migration/block.c b/migration/block.c
index 0c76106..5f9b3e5 100644
--- a/migration/block.c
+++ b/migration/block.c
@@ -319,7 +319,7 @@ static int set_dirty_tracking(void)
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
bmds->dirty_bitmap = bdrv_create_dirty_bitmap(bmds->bs, BLOCK_SIZE,
- NULL);
+ NULL, NULL);
if (!bmds->dirty_bitmap) {
ret = -errno;
goto fail;
diff --git a/qapi/block-core.json b/qapi/block-core.json
index a3fdaf0..829ba1c 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -326,6 +326,8 @@
#
# Block dirty bitmap information.
#
+# @name: #optional the name of the dirty bitmap (Since 2.3)
+#
# @count: number of dirty bytes according to the dirty bitmap
#
# @granularity: granularity of the dirty bitmap in bytes (since 1.4)
@@ -333,7 +335,7 @@
# Since: 1.3
##
{ 'type': 'BlockDirtyInfo',
- 'data': {'count': 'int', 'granularity': 'int'} }
+ 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int'} }
##
# @BlockInfo:
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 01/17] qapi: Add optional field "name" to block dirty bitmap John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-10 21:56 ` Max Reitz
2015-02-13 22:24 ` Eric Blake
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity() John Snow
` (15 subsequent siblings)
17 siblings, 2 replies; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
The new command pair is added to manage user created dirty bitmap. The
dirty bitmap's name is mandatory and must be unique for the same device,
but different devices can have bitmaps with the same names.
The granularity is an optional field. If it is not specified, we will
choose a default granularity based on the cluster size if available,
clamped to between 4K and 64K to mirror how the 'mirror' code was
already choosing granularity. If we do not have cluster size info
available, we choose 64K. This code has been factored out into a helper
shared with block/mirror.
This patch also introduces the 'block_dirty_bitmap_lookup' helper,
which takes a device name and a dirty bitmap name and validates the
lookup, returning NULL and setting errp if there is a problem with
either field. This helper will be re-used in future patches in this
series.
The types added to block-core.json will be re-used in future patches
in this series, see:
'qapi: Add transaction support to block-dirty-bitmap-{add, enable, disable}'
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 20 ++++++++++
block/mirror.c | 10 +----
blockdev.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++
include/block/block.h | 1 +
qapi/block-core.json | 55 +++++++++++++++++++++++++++
qmp-commands.hx | 51 +++++++++++++++++++++++++
6 files changed, 228 insertions(+), 9 deletions(-)
diff --git a/block.c b/block.c
index c9c2359..1661ff9 100644
--- a/block.c
+++ b/block.c
@@ -5460,6 +5460,26 @@ int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector
}
}
+/**
+ * Chooses a default granularity based on the existing cluster size,
+ * but clamped between [4K, 64K]. Defaults to 64K in the case that there
+ * is no cluster size information available.
+ */
+uint64_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs)
+{
+ BlockDriverInfo bdi;
+ uint64_t granularity;
+
+ if (bdrv_get_info(bs, &bdi) >= 0 && bdi.cluster_size != 0) {
+ granularity = MAX(4096, bdi.cluster_size);
+ granularity = MIN(65536, granularity);
+ } else {
+ granularity = 65536;
+ }
+
+ return granularity;
+}
+
void bdrv_dirty_iter_init(BlockDriverState *bs,
BdrvDirtyBitmap *bitmap, HBitmapIter *hbi)
{
diff --git a/block/mirror.c b/block/mirror.c
index f073ad7..77bd1ed 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -668,15 +668,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
MirrorBlockJob *s;
if (granularity == 0) {
- /* Choose the default granularity based on the target file's cluster
- * size, clamped between 4k and 64k. */
- BlockDriverInfo bdi;
- if (bdrv_get_info(target, &bdi) >= 0 && bdi.cluster_size != 0) {
- granularity = MAX(4096, bdi.cluster_size);
- granularity = MIN(65536, granularity);
- } else {
- granularity = 65536;
- }
+ granularity = bdrv_get_default_bitmap_granularity(target);
}
assert ((granularity & (granularity - 1)) == 0);
diff --git a/blockdev.c b/blockdev.c
index 7d34960..eccdd17 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1173,6 +1173,48 @@ out_aio_context:
return NULL;
}
+/**
+ * Return a dirty bitmap (if present), after validating
+ * the node reference and bitmap names. Returns NULL on error,
+ * including when the BDS and/or bitmap is not found.
+ */
+static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,
+ const char *name,
+ BlockDriverState **pbs,
+ Error **errp)
+{
+ BlockDriverState *bs;
+ BdrvDirtyBitmap *bitmap;
+
+ if (!node) {
+ error_setg(errp, "Node cannot be NULL");
+ return NULL;
+ }
+ if (!name) {
+ error_setg(errp, "Bitmap name cannot be NULL");
+ return NULL;
+ }
+
+ bs = bdrv_lookup_bs(node, node, NULL);
+ if (!bs) {
+ error_setg(errp, "Node '%s' not found", node);
+ return NULL;
+ }
+
+ /* If caller provided a BDS*, provide the result of that lookup, too. */
+ if (pbs) {
+ *pbs = bs;
+ }
+
+ bitmap = bdrv_find_dirty_bitmap(bs, name);
+ if (!bitmap) {
+ error_setg(errp, "Dirty bitmap '%s' not found", name);
+ return NULL;
+ }
+
+ return bitmap;
+}
+
/* New and old BlockDriverState structs for atomic group operations */
typedef struct BlkTransactionState BlkTransactionState;
@@ -1953,6 +1995,64 @@ void qmp_block_set_io_throttle(const char *device, int64_t bps, int64_t bps_rd,
aio_context_release(aio_context);
}
+void qmp_block_dirty_bitmap_add(const char *node, const char *name,
+ bool has_granularity, int64_t granularity,
+ Error **errp)
+{
+ AioContext *aio_context;
+ BlockDriverState *bs;
+
+ if (!name || name[0] == '\0') {
+ error_setg(errp, "Bitmap name cannot be empty");
+ return;
+ }
+
+ bs = bdrv_lookup_bs(node, node, errp);
+ if (!bs) {
+ return;
+ }
+
+ aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(aio_context);
+
+ if (has_granularity) {
+ if (granularity < 512 || !is_power_of_2(granularity)) {
+ error_setg(errp, "Granularity must be power of 2 "
+ "and at least 512");
+ goto out;
+ }
+ } else {
+ /* Default to cluster size, if available: */
+ granularity = bdrv_get_default_bitmap_granularity(bs);
+ }
+
+ bdrv_create_dirty_bitmap(bs, granularity, name, errp);
+
+ out:
+ aio_context_release(aio_context);
+}
+
+void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
+ Error **errp)
+{
+ AioContext *aio_context;
+ BlockDriverState *bs;
+ BdrvDirtyBitmap *bitmap;
+
+ bitmap = block_dirty_bitmap_lookup(node, name, &bs, errp);
+ if (!bitmap || !bs) {
+ return;
+ }
+
+ aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(aio_context);
+
+ bdrv_dirty_bitmap_make_anon(bs, bitmap);
+ bdrv_release_dirty_bitmap(bs, bitmap);
+
+ aio_context_release(aio_context);
+}
+
int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
{
const char *id = qdict_get_str(qdict, "id");
diff --git a/include/block/block.h b/include/block/block.h
index 0988b77..eb58002 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -447,6 +447,7 @@ BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
+uint64_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 829ba1c..de14b24 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -959,6 +959,61 @@
'*on-target-error': 'BlockdevOnError' } }
##
+# @BlockDirtyBitmap
+#
+# @node: name of device/node which the bitmap is tracking
+#
+# @name: name of the dirty bitmap
+#
+# Since 2.3
+##
+{ 'type': 'BlockDirtyBitmap',
+ 'data': { 'node': 'str', 'name': 'str' } }
+
+##
+# @BlockDirtyBitmapAdd
+#
+# @node: name of device/node which the bitmap is tracking
+#
+# @name: name of the dirty bitmap
+#
+# @granularity: #optional the bitmap granularity, default is 64k for
+# block-dirty-bitmap-add
+#
+# Since 2.3
+##
+{ 'type': 'BlockDirtyBitmapAdd',
+ 'data': { 'node': 'str', 'name': 'str', '*granularity': 'int' } }
+
+##
+# @block-dirty-bitmap-add
+#
+# Create 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
+#
+# Since 2.3
+##
+{ 'command': 'block-dirty-bitmap-add',
+ 'data': 'BlockDirtyBitmapAdd' }
+
+##
+# @block-dirty-bitmap-remove
+#
+# Remove a dirty bitmap on the node
+#
+# Returns: nothing on success
+# If @node is not a valid block device or node, DeviceNotFound
+# If @name is not found, GenericError with an explanation
+#
+# Since 2.3
+##
+{ 'command': 'block-dirty-bitmap-remove',
+ 'data': 'BlockDirtyBitmap' }
+
+##
# @block_set_io_throttle:
#
# Change I/O throttle limits for a block drive.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index a85d847..881a810 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1244,6 +1244,57 @@ Example:
EQMP
{
+ .name = "block-dirty-bitmap-add",
+ .args_type = "node:B,name:s,granularity:i?",
+ .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_add,
+ },
+ {
+ .name = "block-dirty-bitmap-remove",
+ .args_type = "node:B,name:s",
+ .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_remove,
+ },
+
+SQMP
+
+block-dirty-bitmap-add
+----------------------
+Since 2.3
+
+Create a dirty bitmap with a name on the device, and start tracking the writes.
+
+Arguments:
+
+- "node": device/node on which to create dirty bitmap (json-string)
+- "name": name of the new dirty bitmap (json-string)
+- "granularity": granularity to track writes with (int, optional)
+
+Example:
+
+-> { "execute": "block-dirty-bitmap-add", "arguments": { "node": "drive0",
+ "name": "bitmap0" } }
+<- { "return": {} }
+
+block-dirty-bitmap-remove
+-------------------------
+Since 2.3
+
+Stop write tracking and remove the dirty bitmap that was created with
+block-dirty-bitmap-add.
+
+Arguments:
+
+- "node": device/node on which to remove dirty bitmap (json-string)
+- "name": name of the dirty bitmap to remove (json-string)
+
+Example:
+
+-> { "execute": "block-dirty-bitmap-remove", "arguments": { "node": "drive0",
+ "name": "bitmap0" } }
+<- { "return": {} }
+
+EQMP
+
+ {
.name = "blockdev-snapshot-sync",
.args_type = "device:s?,node-name:s?,snapshot-file:s,snapshot-node-name:s?,format:s?,mode:s?",
.mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync,
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove John Snow
@ 2015-02-10 21:56 ` Max Reitz
2015-02-13 22:24 ` Eric Blake
1 sibling, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-10 21:56 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> The new command pair is added to manage user created dirty bitmap. The
> dirty bitmap's name is mandatory and must be unique for the same device,
> but different devices can have bitmaps with the same names.
>
> The granularity is an optional field. If it is not specified, we will
> choose a default granularity based on the cluster size if available,
> clamped to between 4K and 64K to mirror how the 'mirror' code was
> already choosing granularity. If we do not have cluster size info
> available, we choose 64K. This code has been factored out into a helper
> shared with block/mirror.
>
> This patch also introduces the 'block_dirty_bitmap_lookup' helper,
> which takes a device name and a dirty bitmap name and validates the
> lookup, returning NULL and setting errp if there is a problem with
> either field. This helper will be re-used in future patches in this
> series.
>
> The types added to block-core.json will be re-used in future patches
> in this series, see:
> 'qapi: Add transaction support to block-dirty-bitmap-{add, enable, disable}'
>
> Signed-off-by: Fam Zheng <famz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 20 ++++++++++
> block/mirror.c | 10 +----
> blockdev.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++
> include/block/block.h | 1 +
> qapi/block-core.json | 55 +++++++++++++++++++++++++++
> qmp-commands.hx | 51 +++++++++++++++++++++++++
> 6 files changed, 228 insertions(+), 9 deletions(-)
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove John Snow
2015-02-10 21:56 ` Max Reitz
@ 2015-02-13 22:24 ` Eric Blake
2015-02-13 22:39 ` John Snow
1 sibling, 1 reply; 55+ messages in thread
From: Eric Blake @ 2015-02-13 22:24 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, mreitz, vsementsov, stefanha
[-- Attachment #1: Type: text/plain, Size: 7671 bytes --]
On 02/09/2015 06:35 PM, John Snow wrote:
> The new command pair is added to manage user created dirty bitmap. The
> dirty bitmap's name is mandatory and must be unique for the same device,
> but different devices can have bitmaps with the same names.
I haven't been following this series closely (I know I should be doing
that, though). Is the bitmap associated with the BDS (a host resource,
independent of which device(s) are currently viewing that content) or
with the BlockBackend (only one bitmap namespace per device)? I'm a bit
worried that we will WANT to have bitmaps associated with BDS (if we
don't already) because of image fleecing. That is, if we start with:
base <- mid <- active
and request an image fleecing operation, we want:
base <- mid <- active
\- overlay
where overlay serves the NBD that sees the point in time. If we then
allow a block-commit, then writes to 'mid' containing the content from
'active' will trigger another write to 'overlay' with the pre-modified
contents, so that the NBD fleecing operation doesn't see any changes.
If we then migrate, it means we need multiple bitmaps: the map for the
commit of active into mid (how much remains to be committed), and the
map for mid to overlay (how much of mid has been changed since the
point-in-time overlay was created).
By associating bitmaps with a device (a BB), rather than a BDS, you may
be artificially limiting which operations can be performed. On the
other hand, if you associate with a BDS, and we improve things to allow
arbitrary refactoring relationships where a BDS can be in more than one
tree at once, it starts to be hard to prove that bitmap names won't be
duplicated.
Am I overthinking something here, or are we okay limiting bitmap names
to just the BB device, rather than a BDS?
>
> The granularity is an optional field. If it is not specified, we will
> choose a default granularity based on the cluster size if available,
> clamped to between 4K and 64K to mirror how the 'mirror' code was
> already choosing granularity. If we do not have cluster size info
> available, we choose 64K. This code has been factored out into a helper
> shared with block/mirror.
>
> This patch also introduces the 'block_dirty_bitmap_lookup' helper,
> which takes a device name and a dirty bitmap name and validates the
> lookup, returning NULL and setting errp if there is a problem with
> either field. This helper will be re-used in future patches in this
> series.
>
> The types added to block-core.json will be re-used in future patches
> in this series, see:
> 'qapi: Add transaction support to block-dirty-bitmap-{add, enable, disable}'
>
> Signed-off-by: Fam Zheng <famz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 20 ++++++++++
> block/mirror.c | 10 +----
> blockdev.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++
> include/block/block.h | 1 +
> qapi/block-core.json | 55 +++++++++++++++++++++++++++
> qmp-commands.hx | 51 +++++++++++++++++++++++++
> 6 files changed, 228 insertions(+), 9 deletions(-)
>
> +++ b/blockdev.c
> @@ -1173,6 +1173,48 @@ out_aio_context:
> return NULL;
> }
>
> +/**
> + * Return a dirty bitmap (if present), after validating
> + * the node reference and bitmap names. Returns NULL on error,
> + * including when the BDS and/or bitmap is not found.
> + */
> +static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,
> + const char *name,
> + BlockDriverState **pbs,
> + Error **errp)
> +{
> + BlockDriverState *bs;
> + BdrvDirtyBitmap *bitmap;
> +
> + if (!node) {
> + error_setg(errp, "Node cannot be NULL");
> + return NULL;
> + }
Node tends to be the term we use for BDS, rather than for device BB.
> + if (!name) {
> + error_setg(errp, "Bitmap name cannot be NULL");
> + return NULL;
> + }
> +
> + bs = bdrv_lookup_bs(node, node, NULL);
> + if (!bs) {
> + error_setg(errp, "Node '%s' not found", node);
> + return NULL;
> + }
> +
> + /* If caller provided a BDS*, provide the result of that lookup, too. */
> + if (pbs) {
> + *pbs = bs;
> + }
> +
> + bitmap = bdrv_find_dirty_bitmap(bs, name);
and this seems to say that bitmap names are per-BDS, not per-device.
> + if (!bitmap) {
> + error_setg(errp, "Dirty bitmap '%s' not found", name);
> + return NULL;
> + }
> +
> + return bitmap;
> +}
> +
> /* New and old BlockDriverState structs for atomic group operations */
> +++ b/qapi/block-core.json
> @@ -959,6 +959,61 @@
> '*on-target-error': 'BlockdevOnError' } }
>
> ##
> +# @BlockDirtyBitmap
> +#
> +# @node: name of device/node which the bitmap is tracking
> +#
> +# @name: name of the dirty bitmap
> +#
> +# Since 2.3
> +##
> +{ 'type': 'BlockDirtyBitmap',
> + 'data': { 'node': 'str', 'name': 'str' } }
This naming implies that bitmap is a per-BDS option, but that as a
convenience, we allow a device name (BB) as shorthand for the top-level
BDS associated with the BB. I can live with that.
> +++ b/qmp-commands.hx
> @@ -1244,6 +1244,57 @@ Example:
> EQMP
>
> {
> + .name = "block-dirty-bitmap-add",
> + .args_type = "node:B,name:s,granularity:i?",
> + .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_add,
> + },
> + {
> + .name = "block-dirty-bitmap-remove",
> + .args_type = "node:B,name:s",
> + .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_remove,
> + },
I don't know if it is worth interleaving these declarations...
> +
> +SQMP
> +
> +block-dirty-bitmap-add
> +----------------------
> +Since 2.3
> +
> +Create a dirty bitmap with a name on the device, and start tracking the writes.
> +
> +Arguments:
> +
> +- "node": device/node on which to create dirty bitmap (json-string)
> +- "name": name of the new dirty bitmap (json-string)
> +- "granularity": granularity to track writes with (int, optional)
> +
> +Example:
> +
> +-> { "execute": "block-dirty-bitmap-add", "arguments": { "node": "drive0",
> + "name": "bitmap0" } }
> +<- { "return": {} }
> +
> +block-dirty-bitmap-remove
> +-------------------------
> +Since 2.3
...to align with their examples. I don't see much else grouping in this
file.
> +
> +Stop write tracking and remove the dirty bitmap that was created with
> +block-dirty-bitmap-add.
> +
> +Arguments:
> +
> +- "node": device/node on which to remove dirty bitmap (json-string)
> +- "name": name of the dirty bitmap to remove (json-string)
> +
> +Example:
> +
> +-> { "execute": "block-dirty-bitmap-remove", "arguments": { "node": "drive0",
> + "name": "bitmap0" } }
> +<- { "return": {} }
> +
> +EQMP
> +
> + {
> .name = "blockdev-snapshot-sync",
> .args_type = "device:s?,node-name:s?,snapshot-file:s,snapshot-node-name:s?,format:s?,mode:s?",
> .mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync,
>
The interface seems okay; I'm not sure if there are any tweaks we need
to the commit message or documentation.
--
Eric Blake eblake redhat com +1-919-301-3266
Libvirt virtualization library http://libvirt.org
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove
2015-02-13 22:24 ` Eric Blake
@ 2015-02-13 22:39 ` John Snow
0 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-13 22:39 UTC (permalink / raw)
To: Eric Blake, qemu-devel; +Cc: kwolf, famz, armbru, mreitz, vsementsov, stefanha
Missed you by several seconds, I submitted a v13 fixup to cover Max's
comments and had wrongly assumed I wouldn't be hearing anything else at
5PM on a Friday :)
On 02/13/2015 05:24 PM, Eric Blake wrote:
> On 02/09/2015 06:35 PM, John Snow wrote:
>> The new command pair is added to manage user created dirty bitmap. The
>> dirty bitmap's name is mandatory and must be unique for the same device,
>> but different devices can have bitmaps with the same names.
>
> I haven't been following this series closely (I know I should be doing
> that, though). Is the bitmap associated with the BDS (a host resource,
> independent of which device(s) are currently viewing that content) or
> with the BlockBackend (only one bitmap namespace per device)? I'm a bit
> worried that we will WANT to have bitmaps associated with BDS (if we
> don't already) because of image fleecing. That is, if we start with:
>
> base <- mid <- active
>
> and request an image fleecing operation, we want:
>
> base <- mid <- active
> \- overlay
>
Currently: I am intending to allow users to attach them to any old BDS,
per-node. However, they are currently only /useful/ if you attach them
to the root, since that's what Drive Backup is going to operate on.
> where overlay serves the NBD that sees the point in time. If we then
> allow a block-commit, then writes to 'mid' containing the content from
> 'active' will trigger another write to 'overlay' with the pre-modified
> contents, so that the NBD fleecing operation doesn't see any changes.
> If we then migrate, it means we need multiple bitmaps: the map for the
> commit of active into mid (how much remains to be committed), and the
> map for mid to overlay (how much of mid has been changed since the
> point-in-time overlay was created).
>
> By associating bitmaps with a device (a BB), rather than a BDS, you may
> be artificially limiting which operations can be performed. On the
> other hand, if you associate with a BDS, and we improve things to allow
> arbitrary refactoring relationships where a BDS can be in more than one
> tree at once, it starts to be hard to prove that bitmap names won't be
> duplicated.
>
> Am I overthinking something here, or are we okay limiting bitmap names
> to just the BB device, rather than a BDS?
>
Not my intention to create an artificial limitation, just an implicit
one: There are currently no users of named BdrvDirtyBitmaps that act
per-node.
You may review this series under this premise and yell at me if I have
diverged from this assumption.
>>
>> The granularity is an optional field. If it is not specified, we will
>> choose a default granularity based on the cluster size if available,
>> clamped to between 4K and 64K to mirror how the 'mirror' code was
>> already choosing granularity. If we do not have cluster size info
>> available, we choose 64K. This code has been factored out into a helper
>> shared with block/mirror.
>>
>> This patch also introduces the 'block_dirty_bitmap_lookup' helper,
>> which takes a device name and a dirty bitmap name and validates the
>> lookup, returning NULL and setting errp if there is a problem with
>> either field. This helper will be re-used in future patches in this
>> series.
>>
>> The types added to block-core.json will be re-used in future patches
>> in this series, see:
>> 'qapi: Add transaction support to block-dirty-bitmap-{add, enable, disable}'
>>
>> Signed-off-by: Fam Zheng <famz@redhat.com>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 20 ++++++++++
>> block/mirror.c | 10 +----
>> blockdev.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++
>> include/block/block.h | 1 +
>> qapi/block-core.json | 55 +++++++++++++++++++++++++++
>> qmp-commands.hx | 51 +++++++++++++++++++++++++
>> 6 files changed, 228 insertions(+), 9 deletions(-)
>>
>
>> +++ b/blockdev.c
>> @@ -1173,6 +1173,48 @@ out_aio_context:
>> return NULL;
>> }
>>
>> +/**
>> + * Return a dirty bitmap (if present), after validating
>> + * the node reference and bitmap names. Returns NULL on error,
>> + * including when the BDS and/or bitmap is not found.
>> + */
>> +static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,
>> + const char *name,
>> + BlockDriverState **pbs,
>> + Error **errp)
>> +{
>> + BlockDriverState *bs;
>> + BdrvDirtyBitmap *bitmap;
>> +
>> + if (!node) {
>> + error_setg(errp, "Node cannot be NULL");
>> + return NULL;
>> + }
>
> Node tends to be the term we use for BDS, rather than for device BB.
>
So far so good.
>> + if (!name) {
>> + error_setg(errp, "Bitmap name cannot be NULL");
>> + return NULL;
>> + }
>> +
>> + bs = bdrv_lookup_bs(node, node, NULL);
>> + if (!bs) {
>> + error_setg(errp, "Node '%s' not found", node);
>> + return NULL;
>> + }
>> +
>> + /* If caller provided a BDS*, provide the result of that lookup, too. */
>> + if (pbs) {
>> + *pbs = bs;
>> + }
>> +
>> + bitmap = bdrv_find_dirty_bitmap(bs, name);
>
> and this seems to say that bitmap names are per-BDS, not per-device.
>
We're still on the same page.
>> + if (!bitmap) {
>> + error_setg(errp, "Dirty bitmap '%s' not found", name);
>> + return NULL;
>> + }
>> +
>> + return bitmap;
>> +}
>> +
>> /* New and old BlockDriverState structs for atomic group operations */
>> +++ b/qapi/block-core.json
>> @@ -959,6 +959,61 @@
>> '*on-target-error': 'BlockdevOnError' } }
>>
>> ##
>> +# @BlockDirtyBitmap
>> +#
>> +# @node: name of device/node which the bitmap is tracking
>> +#
>> +# @name: name of the dirty bitmap
>> +#
>> +# Since 2.3
>> +##
>> +{ 'type': 'BlockDirtyBitmap',
>> + 'data': { 'node': 'str', 'name': 'str' } }
>
> This naming implies that bitmap is a per-BDS option, but that as a
> convenience, we allow a device name (BB) as shorthand for the top-level
> BDS associated with the BB. I can live with that.
>
It sounds like I am not going to the mental facility after all. This is
what I wanted.
>> +++ b/qmp-commands.hx
>> @@ -1244,6 +1244,57 @@ Example:
>> EQMP
>>
>> {
>> + .name = "block-dirty-bitmap-add",
>> + .args_type = "node:B,name:s,granularity:i?",
>> + .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_add,
>> + },
>> + {
>> + .name = "block-dirty-bitmap-remove",
>> + .args_type = "node:B,name:s",
>> + .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_remove,
>> + },
>
> I don't know if it is worth interleaving these declarations...
>
I can demingle them if I must; I just carried forth the convention in
the original patches I received and nobody has mentioned it yet.
>> +
>> +SQMP
>> +
>> +block-dirty-bitmap-add
>> +----------------------
>> +Since 2.3
>> +
>> +Create a dirty bitmap with a name on the device, and start tracking the writes.
>> +
>> +Arguments:
>> +
>> +- "node": device/node on which to create dirty bitmap (json-string)
>> +- "name": name of the new dirty bitmap (json-string)
>> +- "granularity": granularity to track writes with (int, optional)
>> +
>> +Example:
>> +
>> +-> { "execute": "block-dirty-bitmap-add", "arguments": { "node": "drive0",
>> + "name": "bitmap0" } }
>> +<- { "return": {} }
>> +
>> +block-dirty-bitmap-remove
>> +-------------------------
>> +Since 2.3
>
> ...to align with their examples. I don't see much else grouping in this
> file.
>
OK, if a v14 is needed for other reasons I will decouple the examples in
this file.
>> +
>> +Stop write tracking and remove the dirty bitmap that was created with
>> +block-dirty-bitmap-add.
>> +
>> +Arguments:
>> +
>> +- "node": device/node on which to remove dirty bitmap (json-string)
>> +- "name": name of the dirty bitmap to remove (json-string)
>> +
>> +Example:
>> +
>> +-> { "execute": "block-dirty-bitmap-remove", "arguments": { "node": "drive0",
>> + "name": "bitmap0" } }
>> +<- { "return": {} }
>> +
>> +EQMP
>> +
>> + {
>> .name = "blockdev-snapshot-sync",
>> .args_type = "device:s?,node-name:s?,snapshot-file:s,snapshot-node-name:s?,format:s?,mode:s?",
>> .mhandler.cmd_new = qmp_marshal_input_blockdev_snapshot_sync,
>>
>
> The interface seems okay; I'm not sure if there are any tweaks we need
> to the commit message or documentation.
>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity()
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 01/17] qapi: Add optional field "name" to block dirty bitmap John Snow
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 02/17] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-10 22:03 ` Max Reitz
2015-02-10 22:13 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 04/17] hbitmap: add hbitmap_merge John Snow
` (14 subsequent siblings)
17 siblings, 2 replies; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
This returns the granularity (in bytes) of dirty bitmap,
which matches the QMP interface and the existing query
interface.
Small adjustments are made to ensure that granularity-- in bytes--
is handled consistently as a uint64_t throughout the code.
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 17 +++++++++++------
include/block/block.h | 3 ++-
2 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/block.c b/block.c
index 1661ff9..83f411f 100644
--- a/block.c
+++ b/block.c
@@ -5387,12 +5387,13 @@ void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
}
BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
- int granularity,
+ uint64_t granularity,
const char *name,
Error **errp)
{
int64_t bitmap_size;
BdrvDirtyBitmap *bitmap;
+ int sector_granularity;
assert((granularity & (granularity - 1)) == 0);
@@ -5400,8 +5401,8 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
error_setg(errp, "Bitmap already exists: %s", name);
return NULL;
}
- granularity >>= BDRV_SECTOR_BITS;
- assert(granularity);
+ sector_granularity = granularity >> BDRV_SECTOR_BITS;
+ assert(sector_granularity);
bitmap_size = bdrv_nb_sectors(bs);
if (bitmap_size < 0) {
error_setg_errno(errp, -bitmap_size, "could not get length of device");
@@ -5409,7 +5410,7 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
return NULL;
}
bitmap = g_new0(BdrvDirtyBitmap, 1);
- bitmap->bitmap = hbitmap_alloc(bitmap_size, ffs(granularity) - 1);
+ bitmap->bitmap = hbitmap_alloc(bitmap_size, ffs(sector_granularity) - 1);
bitmap->name = g_strdup(name);
QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
return bitmap;
@@ -5439,8 +5440,7 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
BlockDirtyInfo *info = g_new0(BlockDirtyInfo, 1);
BlockDirtyInfoList *entry = g_new0(BlockDirtyInfoList, 1);
info->count = bdrv_get_dirty_count(bs, bm);
- info->granularity =
- ((int64_t) BDRV_SECTOR_SIZE << hbitmap_granularity(bm->bitmap));
+ info->granularity = bdrv_dirty_bitmap_granularity(bm);
info->has_name = !!bm->name;
info->name = g_strdup(bm->name);
entry->value = info;
@@ -5480,6 +5480,11 @@ uint64_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs)
return granularity;
}
+uint64_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap)
+{
+ return BDRV_SECTOR_SIZE << hbitmap_granularity(bitmap->bitmap);
+}
+
void bdrv_dirty_iter_init(BlockDriverState *bs,
BdrvDirtyBitmap *bitmap, HBitmapIter *hbi)
{
diff --git a/include/block/block.h b/include/block/block.h
index eb58002..6366cbf 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -439,7 +439,7 @@ bool bdrv_qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov);
struct HBitmapIter;
typedef struct BdrvDirtyBitmap BdrvDirtyBitmap;
BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
- int granularity,
+ uint64_t granularity,
const char *name,
Error **errp);
BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
@@ -448,6 +448,7 @@ void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
uint64_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
+uint64_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap);
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity()
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity() John Snow
@ 2015-02-10 22:03 ` Max Reitz
2015-02-11 18:57 ` John Snow
2015-02-10 22:13 ` Max Reitz
1 sibling, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-10 22:03 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> This returns the granularity (in bytes) of dirty bitmap,
> which matches the QMP interface and the existing query
> interface.
>
> Small adjustments are made to ensure that granularity-- in bytes--
I guess these should be ASCII replacements of an em dash? But they kind
of look like decrement operators to me...
> is handled consistently as a uint64_t throughout the code.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 17 +++++++++++------
> include/block/block.h | 3 ++-
> 2 files changed, 13 insertions(+), 7 deletions(-)
>
> diff --git a/block.c b/block.c
> index 1661ff9..83f411f 100644
> --- a/block.c
> +++ b/block.c
> @@ -5387,12 +5387,13 @@ void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
> }
>
> BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
> - int granularity,
> + uint64_t granularity,
> const char *name,
> Error **errp)
> {
> int64_t bitmap_size;
> BdrvDirtyBitmap *bitmap;
> + int sector_granularity;
>
> assert((granularity & (granularity - 1)) == 0);
>
> @@ -5400,8 +5401,8 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
> error_setg(errp, "Bitmap already exists: %s", name);
> return NULL;
> }
> - granularity >>= BDRV_SECTOR_BITS;
> - assert(granularity);
> + sector_granularity = granularity >> BDRV_SECTOR_BITS;
I can see Coverity's screams regarding a possible overflow already...
(but maybe it doesn't even scream and I'm just overcautious)
Whether you add an assert((granularity >> BDRV_SECTOR_BITS) <= INT_MAX)
or not here (it does look pretty ugly and it is pretty unnecessary, I
know) or not, and whether you do something about the decrement operators
in the commit message or not:
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity()
2015-02-10 22:03 ` Max Reitz
@ 2015-02-11 18:57 ` John Snow
2015-02-11 18:58 ` Max Reitz
0 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-11 18:57 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/10/2015 05:03 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> This returns the granularity (in bytes) of dirty bitmap,
>> which matches the QMP interface and the existing query
>> interface.
>>
>> Small adjustments are made to ensure that granularity-- in bytes--
>
> I guess these should be ASCII replacements of an em dash? But they kind
> of look like decrement operators to me...
>
>> is handled consistently as a uint64_t throughout the code.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 17 +++++++++++------
>> include/block/block.h | 3 ++-
>> 2 files changed, 13 insertions(+), 7 deletions(-)
>>
>> diff --git a/block.c b/block.c
>> index 1661ff9..83f411f 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -5387,12 +5387,13 @@ void
>> bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap
>> *bitmap)
>> }
>> BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
>> - int granularity,
>> + uint64_t granularity,
>> const char *name,
>> Error **errp)
>> {
>> int64_t bitmap_size;
>> BdrvDirtyBitmap *bitmap;
>> + int sector_granularity;
>> assert((granularity & (granularity - 1)) == 0);
>> @@ -5400,8 +5401,8 @@ BdrvDirtyBitmap
>> *bdrv_create_dirty_bitmap(BlockDriverState *bs,
>> error_setg(errp, "Bitmap already exists: %s", name);
>> return NULL;
>> }
>> - granularity >>= BDRV_SECTOR_BITS;
>> - assert(granularity);
>> + sector_granularity = granularity >> BDRV_SECTOR_BITS;
>
> I can see Coverity's screams regarding a possible overflow already...
> (but maybe it doesn't even scream and I'm just overcautious)
>
> Whether you add an assert((granularity >> BDRV_SECTOR_BITS) <= INT_MAX)
> or not here (it does look pretty ugly and it is pretty unnecessary, I
> know) or not, and whether you do something about the decrement operators
> in the commit message or not:
>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
Coverity would whine about right-shifting a value?
In this case, right-shifting an unsigned value should be fine for all
cases from 0 through UINT64_MAX. It won't underflow and loop around to
something too big; this is safe as an integral division operation.
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity()
2015-02-11 18:57 ` John Snow
@ 2015-02-11 18:58 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 18:58 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-11 at 13:57, John Snow wrote:
>
>
> On 02/10/2015 05:03 PM, Max Reitz wrote:
>> On 2015-02-09 at 20:35, John Snow wrote:
>>> This returns the granularity (in bytes) of dirty bitmap,
>>> which matches the QMP interface and the existing query
>>> interface.
>>>
>>> Small adjustments are made to ensure that granularity-- in bytes--
>>
>> I guess these should be ASCII replacements of an em dash? But they kind
>> of look like decrement operators to me...
>>
>>> is handled consistently as a uint64_t throughout the code.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>> block.c | 17 +++++++++++------
>>> include/block/block.h | 3 ++-
>>> 2 files changed, 13 insertions(+), 7 deletions(-)
>>>
>>> diff --git a/block.c b/block.c
>>> index 1661ff9..83f411f 100644
>>> --- a/block.c
>>> +++ b/block.c
>>> @@ -5387,12 +5387,13 @@ void
>>> bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap
>>> *bitmap)
>>> }
>>> BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
>>> - int granularity,
>>> + uint64_t granularity,
>>> const char *name,
>>> Error **errp)
>>> {
>>> int64_t bitmap_size;
>>> BdrvDirtyBitmap *bitmap;
>>> + int sector_granularity;
>>> assert((granularity & (granularity - 1)) == 0);
>>> @@ -5400,8 +5401,8 @@ BdrvDirtyBitmap
>>> *bdrv_create_dirty_bitmap(BlockDriverState *bs,
>>> error_setg(errp, "Bitmap already exists: %s", name);
>>> return NULL;
>>> }
>>> - granularity >>= BDRV_SECTOR_BITS;
>>> - assert(granularity);
>>> + sector_granularity = granularity >> BDRV_SECTOR_BITS;
>>
>> I can see Coverity's screams regarding a possible overflow already...
>> (but maybe it doesn't even scream and I'm just overcautious)
>>
>> Whether you add an assert((granularity >> BDRV_SECTOR_BITS) <= INT_MAX)
>> or not here (it does look pretty ugly and it is pretty unnecessary, I
>> know) or not, and whether you do something about the decrement operators
>> in the commit message or not:
>>
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>
> Coverity would whine about right-shifting a value?
Not about right-shifting, but about right-shifting a uint64_t by less
than 32 and storing it in an int.
Max
>
> In this case, right-shifting an unsigned value should be fine for all
> cases from 0 through UINT64_MAX. It won't underflow and loop around to
> something too big; this is safe as an integral division operation.
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity()
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity() John Snow
2015-02-10 22:03 ` Max Reitz
@ 2015-02-10 22:13 ` Max Reitz
2015-02-10 22:15 ` John Snow
1 sibling, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-10 22:13 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> This returns the granularity (in bytes) of dirty bitmap,
> which matches the QMP interface and the existing query
> interface.
>
> Small adjustments are made to ensure that granularity-- in bytes--
> is handled consistently as a uint64_t throughout the code.
Just asking whether you want to adjust the following, too: "int
granularity" in mirror_free_init() (the granularity is stored as an 64
bit integer, but the value is limited by qmp_drive_mirror() to stay
between 512 and 64M, and is defined to be a uint32_t by
qapi/block-core.json).
It's fine from my perspective, so my R-b stands, but it looked like it
might conflict with "is handled consistently". :-)
Max
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 17 +++++++++++------
> include/block/block.h | 3 ++-
> 2 files changed, 13 insertions(+), 7 deletions(-)
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity()
2015-02-10 22:13 ` Max Reitz
@ 2015-02-10 22:15 ` John Snow
0 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-10 22:15 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/10/2015 05:13 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> This returns the granularity (in bytes) of dirty bitmap,
>> which matches the QMP interface and the existing query
>> interface.
>>
>> Small adjustments are made to ensure that granularity-- in bytes--
>> is handled consistently as a uint64_t throughout the code.
>
> Just asking whether you want to adjust the following, too: "int
> granularity" in mirror_free_init() (the granularity is stored as an 64
> bit integer, but the value is limited by qmp_drive_mirror() to stay
> between 512 and 64M, and is defined to be a uint32_t by
> qapi/block-core.json).
>
> It's fine from my perspective, so my R-b stands, but it looked like it
> might conflict with "is handled consistently". :-)
>
> Max
>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 17 +++++++++++------
>> include/block/block.h | 3 ++-
>> 2 files changed, 13 insertions(+), 7 deletions(-)
I'll take a look! Every time I change a type I have to change ten more.
It is a ceaseless game of whackamole.
When I inevitably need to change the QMP parameter name again, I'll fix
this while I'm at it.
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 04/17] hbitmap: add hbitmap_merge
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (2 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 03/17] block: Introduce bdrv_dirty_bitmap_granularity() John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-10 22:16 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 05/17] qmp: Add block-dirty-bitmap-enable and block-dirty-bitmap-disable John Snow
` (13 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
We add a bitmap merge operation to assist in error cases
where we wish to combine two bitmaps together.
This is algorithmically O(bits) provided HBITMAP_LEVELS remains
constant. For a full bitmap on a 64bit machine:
sum(bits/64^k, k, 0, HBITMAP_LEVELS) ~= 1.01587 * bits
We may be able to improve running speed for particularly sparse
bitmaps by using iterators, but the running time for dense maps
will be worse.
We present the simpler solution first, and we can refine it later
if needed.
Signed-off-by: John Snow <jsnow@redhat.com>
---
include/qemu/hbitmap.h | 11 +++++++++++
util/hbitmap.c | 32 ++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+)
diff --git a/include/qemu/hbitmap.h b/include/qemu/hbitmap.h
index 550d7ce..c19c1cb 100644
--- a/include/qemu/hbitmap.h
+++ b/include/qemu/hbitmap.h
@@ -65,6 +65,17 @@ struct HBitmapIter {
HBitmap *hbitmap_alloc(uint64_t size, int granularity);
/**
+ * hbitmap_merge:
+ * @a: The bitmap to store the result in.
+ * @b: The bitmap to merge into @a.
+ *
+ * Merge two bitmaps together.
+ * A := A (BITOR) B.
+ * B is left unmodified.
+ */
+bool hbitmap_merge(HBitmap *a, const HBitmap *b);
+
+/**
* hbitmap_empty:
* @hb: HBitmap to operate on.
*
diff --git a/util/hbitmap.c b/util/hbitmap.c
index ab13971..962ff29 100644
--- a/util/hbitmap.c
+++ b/util/hbitmap.c
@@ -395,3 +395,35 @@ HBitmap *hbitmap_alloc(uint64_t size, int granularity)
hb->levels[0][0] |= 1UL << (BITS_PER_LONG - 1);
return hb;
}
+
+/**
+ * Given HBitmaps A and B, let A := A (BITOR) B.
+ * Bitmap B will not be modified.
+ */
+bool hbitmap_merge(HBitmap *a, const HBitmap *b)
+{
+ int i, j;
+ uint64_t size;
+
+ if ((a->size != b->size) || (a->granularity != b->granularity)) {
+ return false;
+ }
+
+ if (hbitmap_count(b) == 0) {
+ return true;
+ }
+
+ /* This merge is O(size), as BITS_PER_LONG and HBITMAP_LEVELS are constant.
+ * It may be possible to improve running times for sparsely populated maps
+ * by using hbitmap_iter_next, but this is suboptimal for dense maps.
+ */
+ size = a->size;
+ for (i = HBITMAP_LEVELS - 1; i >= 0; i--) {
+ size = MAX((size + BITS_PER_LONG - 1) >> BITS_PER_LEVEL, 1);
+ for (j = 0; j < size; j++) {
+ a->levels[i][j] |= b->levels[i][j];
+ }
+ }
+
+ return true;
+}
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 04/17] hbitmap: add hbitmap_merge
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 04/17] hbitmap: add hbitmap_merge John Snow
@ 2015-02-10 22:16 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-10 22:16 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> We add a bitmap merge operation to assist in error cases
> where we wish to combine two bitmaps together.
>
> This is algorithmically O(bits) provided HBITMAP_LEVELS remains
> constant. For a full bitmap on a 64bit machine:
> sum(bits/64^k, k, 0, HBITMAP_LEVELS) ~= 1.01587 * bits
>
> We may be able to improve running speed for particularly sparse
> bitmaps by using iterators, but the running time for dense maps
> will be worse.
>
> We present the simpler solution first, and we can refine it later
> if needed.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> include/qemu/hbitmap.h | 11 +++++++++++
> util/hbitmap.c | 32 ++++++++++++++++++++++++++++++++
> 2 files changed, 43 insertions(+)
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 05/17] qmp: Add block-dirty-bitmap-enable and block-dirty-bitmap-disable
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (3 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 04/17] hbitmap: add hbitmap_merge John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 16:26 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 06/17] block: Add bitmap successors John Snow
` (12 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
This allows to put the dirty bitmap into a disabled state where it is
read only. A disabled bitmap will ignore any attempts to set or reset
any of its bits, but can otherwise be renamed, deleted, or re-enabled.
It will be used before backup or writing to persistent file.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 25 +++++++++++++++++++++++++
blockdev.c | 40 ++++++++++++++++++++++++++++++++++++++++
include/block/block.h | 3 +++
qapi/block-core.json | 28 ++++++++++++++++++++++++++++
qmp-commands.hx | 10 ++++++++++
5 files changed, 106 insertions(+)
diff --git a/block.c b/block.c
index 83f411f..8d63375 100644
--- a/block.c
+++ b/block.c
@@ -54,6 +54,7 @@
struct BdrvDirtyBitmap {
HBitmap *bitmap;
char *name;
+ bool disabled;
QLIST_ENTRY(BdrvDirtyBitmap) list;
};
@@ -5412,10 +5413,16 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
bitmap = g_new0(BdrvDirtyBitmap, 1);
bitmap->bitmap = hbitmap_alloc(bitmap_size, ffs(sector_granularity) - 1);
bitmap->name = g_strdup(name);
+ bitmap->disabled = false;
QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
return bitmap;
}
+bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap)
+{
+ return !bitmap->disabled;
+}
+
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
{
BdrvDirtyBitmap *bm, *next;
@@ -5430,6 +5437,16 @@ void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
}
}
+void bdrv_disable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
+{
+ bitmap->disabled = true;
+}
+
+void bdrv_enable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
+{
+ bitmap->disabled = false;
+}
+
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
{
BdrvDirtyBitmap *bm;
@@ -5494,12 +5511,14 @@ void bdrv_dirty_iter_init(BlockDriverState *bs,
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors)
{
+ assert(bdrv_dirty_bitmap_enabled(bitmap));
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
}
void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors)
{
+ assert(bdrv_dirty_bitmap_enabled(bitmap));
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
}
@@ -5508,6 +5527,9 @@ static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
{
BdrvDirtyBitmap *bitmap;
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
+ if (!bdrv_dirty_bitmap_enabled(bitmap)) {
+ continue;
+ }
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
}
}
@@ -5517,6 +5539,9 @@ static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
{
BdrvDirtyBitmap *bitmap;
QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
+ if (!bdrv_dirty_bitmap_enabled(bitmap)) {
+ continue;
+ }
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
}
}
diff --git a/blockdev.c b/blockdev.c
index eccdd17..3494a46 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2053,6 +2053,46 @@ void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
aio_context_release(aio_context);
}
+void qmp_block_dirty_bitmap_enable(const char *node, const char *name,
+ Error **errp)
+{
+ AioContext *aio_context;
+ BdrvDirtyBitmap *bitmap;
+ BlockDriverState *bs;
+
+ bitmap = block_dirty_bitmap_lookup(node, name, &bs, errp);
+ if (!bitmap) {
+ return;
+ }
+
+ aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(aio_context);
+
+ bdrv_enable_dirty_bitmap(bitmap);
+
+ aio_context_release(aio_context);
+}
+
+void qmp_block_dirty_bitmap_disable(const char *node, const char *name,
+ Error **errp)
+{
+ AioContext *aio_context;
+ BdrvDirtyBitmap *bitmap;
+ BlockDriverState *bs;
+
+ bitmap = block_dirty_bitmap_lookup(node, name, &bs, errp);
+ if (!bitmap) {
+ return;
+ }
+
+ aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(aio_context);
+
+ bdrv_disable_dirty_bitmap(bitmap);
+
+ aio_context_release(aio_context);
+}
+
int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
{
const char *id = qdict_get_str(qdict, "id");
diff --git a/include/block/block.h b/include/block/block.h
index 6366cbf..8847d3e 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -446,9 +446,12 @@ BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
const char *name);
void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
+void bdrv_disable_dirty_bitmap(BdrvDirtyBitmap *bitmap);
+void bdrv_enable_dirty_bitmap(BdrvDirtyBitmap *bitmap);
BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
uint64_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
uint64_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap);
+bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap);
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
diff --git a/qapi/block-core.json b/qapi/block-core.json
index de14b24..324f836 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1014,6 +1014,34 @@
'data': 'BlockDirtyBitmap' }
##
+# @block-dirty-bitmap-enable
+#
+# Enable a dirty bitmap on the device
+#
+# Returns: nothing on success
+# If @node is not a valid block device, DeviceNotFound
+# If @name is not found, GenericError with an explanation
+#
+# Since 2.3
+##
+{ 'command': 'block-dirty-bitmap-enable',
+ 'data': 'BlockDirtyBitmap' }
+
+##
+# @block-dirty-bitmap-disable
+#
+# Disable a dirty bitmap on the device
+#
+# Returns: nothing on success
+# If @node is not a valid block device, DeviceNotFound
+# If @name is not found, GenericError with an explanation
+#
+# Since 2.3
+##
+{ 'command': 'block-dirty-bitmap-disable',
+ 'data': 'BlockDirtyBitmap' }
+
+##
# @block_set_io_throttle:
#
# Change I/O throttle limits for a block drive.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 881a810..ce7782f 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1253,6 +1253,16 @@ EQMP
.args_type = "node:B,name:s",
.mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_remove,
},
+ {
+ .name = "block-dirty-bitmap-enable",
+ .args_type = "node:B,name:s",
+ .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_enable,
+ },
+ {
+ .name = "block-dirty-bitmap-disable",
+ .args_type = "node:B,name:s",
+ .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_disable,
+ },
SQMP
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 05/17] qmp: Add block-dirty-bitmap-enable and block-dirty-bitmap-disable
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 05/17] qmp: Add block-dirty-bitmap-enable and block-dirty-bitmap-disable John Snow
@ 2015-02-11 16:26 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 16:26 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> This allows to put the dirty bitmap into a disabled state where it is
> read only. A disabled bitmap will ignore any attempts to set or reset
> any of its bits, but can otherwise be renamed, deleted, or re-enabled.
>
> It will be used before backup or writing to persistent file.
>
> Signed-off-by: Fam Zheng<famz@redhat.com>
> Signed-off-by: John Snow<jsnow@redhat.com>
> ---
> block.c | 25 +++++++++++++++++++++++++
> blockdev.c | 40 ++++++++++++++++++++++++++++++++++++++++
> include/block/block.h | 3 +++
> qapi/block-core.json | 28 ++++++++++++++++++++++++++++
> qmp-commands.hx | 10 ++++++++++
> 5 files changed, 106 insertions(+)
>
> diff --git a/block.c b/block.c
> index 83f411f..8d63375 100644
> --- a/block.c
> +++ b/block.c
> @@ -54,6 +54,7 @@
> struct BdrvDirtyBitmap {
> HBitmap *bitmap;
> char *name;
> + bool disabled;
> QLIST_ENTRY(BdrvDirtyBitmap) list;
> };
>
> @@ -5412,10 +5413,16 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
> bitmap = g_new0(BdrvDirtyBitmap, 1);
> bitmap->bitmap = hbitmap_alloc(bitmap_size, ffs(sector_granularity) - 1);
> bitmap->name = g_strdup(name);
> + bitmap->disabled = false;
Unnecessary thanks to the g_new0(), but won't hurt.
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 06/17] block: Add bitmap successors
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (4 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 05/17] qmp: Add block-dirty-bitmap-enable and block-dirty-bitmap-disable John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 16:50 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
` (11 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
A bitmap successor is an anonymous BdrvDirtyBitmap that is intended to
be created just prior to a sensitive operation (e.g. Incremental Backup)
that can either succeed or fail, but during the course of which we still
want a bitmap tracking writes.
On creating a successor, we "freeze" the parent bitmap which prevents
its deletion, enabling, anonymization, or creating a bitmap with the
same name.
On success, the parent bitmap can "abdicate" responsibility to the
successor, which will inherit its name. The successor will have been
tracking writes during the course of the backup operation. The parent
will be safely deleted.
On failure, we can "reclaim" the successor from the parent, unifying
them such that the resulting bitmap describes all writes occurring since
the last successful backup, for instance. Reclamation will thaw the
parent, but not explicitly re-enable it.
BdrvDirtyBitmap operations that target a single bitmap are protected
by assertions that the bitmap is not frozen and/or disabled.
BdrvDirtyBitmap operations that target a group of bitmaps, such as
bdrv_{set,reset}_dirty will ignore frozen/disabled drives with a
conditional instead.
QMP transactions that enable/disable bitmaps have extra error checking
surrounding them that prevent modifying bitmaps that are frozen.
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++-
blockdev.c | 22 +++++++++++
include/block/block.h | 10 +++++
qapi/block-core.json | 3 ++
4 files changed, 138 insertions(+), 1 deletion(-)
diff --git a/block.c b/block.c
index 8d63375..8d84ace 100644
--- a/block.c
+++ b/block.c
@@ -51,8 +51,17 @@
#include <windows.h>
#endif
+/**
+ * A BdrvDirtyBitmap can be in three possible states:
+ * (1) successor is false and disabled is false: full r/w mode
+ * (2) successor is false and disabled is true: read only mode ("disabled")
+ * (3) successor is set: frozen mode.
+ * A frozen bitmap cannot be renamed, deleted, anonymized, cleared, set,
+ * or enabled. A frozen bitmap can only abdicate() or reclaim().
+ */
struct BdrvDirtyBitmap {
HBitmap *bitmap;
+ BdrvDirtyBitmap *successor;
char *name;
bool disabled;
QLIST_ENTRY(BdrvDirtyBitmap) list;
@@ -5383,6 +5392,7 @@ BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs, const char *name)
void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
{
+ assert(!bdrv_dirty_bitmap_frozen(bitmap));
g_free(bitmap->name);
bitmap->name = NULL;
}
@@ -5418,9 +5428,98 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
return bitmap;
}
+bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap)
+{
+ return bitmap->successor;
+}
+
bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap)
{
- return !bitmap->disabled;
+ return !(bitmap->disabled || bitmap->successor);
+}
+
+/**
+ * Create a successor bitmap destined to replace this bitmap after an operation.
+ * Requires that the bitmap is not frozen and has no successor.
+ */
+int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
+ BdrvDirtyBitmap *bitmap, Error **errp)
+{
+ uint64_t granularity;
+ BdrvDirtyBitmap *child;
+
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ error_setg(errp, "Cannot create a successor for a bitmap that is "
+ "currently frozen.");
+ return -1;
+ }
+ assert(!bitmap->successor);
+
+ /* Create an anonymous successor */
+ granularity = bdrv_dirty_bitmap_granularity(bitmap);
+ child = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
+ if (!child) {
+ return -1;
+ }
+
+ /* Successor will be on or off based on our current state. */
+ child->disabled = bitmap->disabled;
+
+ /* Install the successor and freeze the parent */
+ bitmap->successor = child;
+ return 0;
+}
+
+/**
+ * For a bitmap with a successor, yield our name to the successor,
+ * Delete the old bitmap, and return a handle to the new bitmap.
+ */
+BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
+ BdrvDirtyBitmap *bitmap,
+ Error **errp)
+{
+ char *name;
+ BdrvDirtyBitmap *successor = bitmap->successor;
+
+ if (successor == NULL) {
+ error_setg(errp, "Cannot relinquish control if "
+ "there's no successor present.\n");
+ return NULL;
+ }
+
+ name = bitmap->name;
+ bitmap->name = NULL;
+ successor->name = name;
+ bitmap->successor = NULL;
+ bdrv_release_dirty_bitmap(bs, bitmap);
+
+ return successor;
+}
+
+/**
+ * In cases of failure where we can no longer safely delete the parent,
+ * We may wish to re-join the parent and child/successor.
+ * The merged parent will be un-frozen, but not explicitly re-enabled.
+ */
+BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
+ BdrvDirtyBitmap *parent,
+ Error **errp)
+{
+ BdrvDirtyBitmap *successor = parent->successor;
+
+ if (!successor) {
+ error_setg(errp, "Cannot reclaim a successor when none is present.\n");
+ return NULL;
+ }
+
+ if (!hbitmap_merge(parent->bitmap, successor->bitmap)) {
+ error_setg(errp, "Merging of parent and successor bitmap failed.\n");
+ return NULL;
+ }
+ bdrv_release_dirty_bitmap(bs, successor);
+ parent->successor = NULL;
+
+ return parent;
}
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
@@ -5428,6 +5527,7 @@ void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
BdrvDirtyBitmap *bm, *next;
QLIST_FOREACH_SAFE(bm, &bs->dirty_bitmaps, list, next) {
if (bm == bitmap) {
+ assert(!bdrv_dirty_bitmap_frozen(bm));
QLIST_REMOVE(bitmap, list);
hbitmap_free(bitmap->bitmap);
g_free(bitmap->name);
@@ -5439,11 +5539,13 @@ void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
void bdrv_disable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
{
+ assert(!bdrv_dirty_bitmap_frozen(bitmap));
bitmap->disabled = true;
}
void bdrv_enable_dirty_bitmap(BdrvDirtyBitmap *bitmap)
{
+ assert(!bdrv_dirty_bitmap_frozen(bitmap));
bitmap->disabled = false;
}
diff --git a/blockdev.c b/blockdev.c
index 3494a46..826d0c1 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2047,9 +2047,16 @@ void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ error_setg(errp,
+ "Bitmap '%s' is currently frozen and cannot be removed.\n",
+ name);
+ goto out;
+ }
bdrv_dirty_bitmap_make_anon(bs, bitmap);
bdrv_release_dirty_bitmap(bs, bitmap);
+ out:
aio_context_release(aio_context);
}
@@ -2068,8 +2075,16 @@ void qmp_block_dirty_bitmap_enable(const char *node, const char *name,
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ error_setg(errp,
+ "Bitmap '%s' is currently frozen and cannot be modified.\n",
+ name);
+ goto out;
+ }
+
bdrv_enable_dirty_bitmap(bitmap);
+ out:
aio_context_release(aio_context);
}
@@ -2088,8 +2103,15 @@ void qmp_block_dirty_bitmap_disable(const char *node, const char *name,
aio_context = bdrv_get_aio_context(bs);
aio_context_acquire(aio_context);
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ error_setg(errp,
+ "Bitmap '%s' is currently frozen and cannot be modified.\n",
+ name);
+ goto out;
+ }
bdrv_disable_dirty_bitmap(bitmap);
+ out:
aio_context_release(aio_context);
}
diff --git a/include/block/block.h b/include/block/block.h
index 8847d3e..b2d84d6 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -442,6 +442,15 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
uint64_t granularity,
const char *name,
Error **errp);
+int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
+ BdrvDirtyBitmap *bitmap,
+ Error **errp);
+BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
+ BdrvDirtyBitmap *bitmap,
+ Error **errp);
+BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
+ BdrvDirtyBitmap *bitmap,
+ Error **errp);
BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
const char *name);
void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
@@ -452,6 +461,7 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
uint64_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
uint64_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap);
bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap);
+bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap);
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 324f836..940eff7 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1007,6 +1007,7 @@
# Returns: nothing on success
# If @node is not a valid block device or node, DeviceNotFound
# If @name is not found, GenericError with an explanation
+# if @name is frozen by an operation, GenericError
#
# Since 2.3
##
@@ -1021,6 +1022,7 @@
# Returns: nothing on success
# If @node is not a valid block device, DeviceNotFound
# If @name is not found, GenericError with an explanation
+# if @name is frozen by an operation, GenericError
#
# Since 2.3
##
@@ -1035,6 +1037,7 @@
# Returns: nothing on success
# If @node is not a valid block device, DeviceNotFound
# If @name is not found, GenericError with an explanation
+# if @name is frozen by an operation, GenericError
#
# Since 2.3
##
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 06/17] block: Add bitmap successors
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 06/17] block: Add bitmap successors John Snow
@ 2015-02-11 16:50 ` Max Reitz
2015-02-11 16:51 ` John Snow
0 siblings, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-11 16:50 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> A bitmap successor is an anonymous BdrvDirtyBitmap that is intended to
> be created just prior to a sensitive operation (e.g. Incremental Backup)
> that can either succeed or fail, but during the course of which we still
> want a bitmap tracking writes.
>
> On creating a successor, we "freeze" the parent bitmap which prevents
> its deletion, enabling, anonymization, or creating a bitmap with the
> same name.
>
> On success, the parent bitmap can "abdicate" responsibility to the
> successor, which will inherit its name. The successor will have been
> tracking writes during the course of the backup operation. The parent
> will be safely deleted.
>
> On failure, we can "reclaim" the successor from the parent, unifying
> them such that the resulting bitmap describes all writes occurring since
> the last successful backup, for instance. Reclamation will thaw the
> parent, but not explicitly re-enable it.
>
> BdrvDirtyBitmap operations that target a single bitmap are protected
> by assertions that the bitmap is not frozen and/or disabled.
>
> BdrvDirtyBitmap operations that target a group of bitmaps, such as
> bdrv_{set,reset}_dirty will ignore frozen/disabled drives with a
> conditional instead.
>
> QMP transactions that enable/disable bitmaps have extra error checking
> surrounding them that prevent modifying bitmaps that are frozen.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++-
> blockdev.c | 22 +++++++++++
> include/block/block.h | 10 +++++
> qapi/block-core.json | 3 ++
> 4 files changed, 138 insertions(+), 1 deletion(-)
>
> diff --git a/block.c b/block.c
> index 8d63375..8d84ace 100644
> --- a/block.c
> +++ b/block.c
> @@ -51,8 +51,17 @@
> #include <windows.h>
> #endif
>
> +/**
> + * A BdrvDirtyBitmap can be in three possible states:
> + * (1) successor is false and disabled is false: full r/w mode
> + * (2) successor is false and disabled is true: read only mode ("disabled")
> + * (3) successor is set: frozen mode.
> + * A frozen bitmap cannot be renamed, deleted, anonymized, cleared, set,
> + * or enabled. A frozen bitmap can only abdicate() or reclaim().
> + */
> struct BdrvDirtyBitmap {
> HBitmap *bitmap;
> + BdrvDirtyBitmap *successor;
> char *name;
> bool disabled;
> QLIST_ENTRY(BdrvDirtyBitmap) list;
> @@ -5383,6 +5392,7 @@ BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs, const char *name)
>
> void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
> {
> + assert(!bdrv_dirty_bitmap_frozen(bitmap));
> g_free(bitmap->name);
> bitmap->name = NULL;
> }
> @@ -5418,9 +5428,98 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
> return bitmap;
> }
>
> +bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap)
> +{
> + return bitmap->successor;
> +}
> +
> bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap)
> {
> - return !bitmap->disabled;
> + return !(bitmap->disabled || bitmap->successor);
> +}
> +
> +/**
> + * Create a successor bitmap destined to replace this bitmap after an operation.
> + * Requires that the bitmap is not frozen and has no successor.
> + */
> +int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,
> + BdrvDirtyBitmap *bitmap, Error **errp)
> +{
> + uint64_t granularity;
> + BdrvDirtyBitmap *child;
> +
> + if (bdrv_dirty_bitmap_frozen(bitmap)) {
> + error_setg(errp, "Cannot create a successor for a bitmap that is "
> + "currently frozen.");
Hm, I seem to remember Markus saying something about full stops at the
end of error messages...
> + return -1;
> + }
> + assert(!bitmap->successor);
> +
> + /* Create an anonymous successor */
> + granularity = bdrv_dirty_bitmap_granularity(bitmap);
> + child = bdrv_create_dirty_bitmap(bs, granularity, NULL, errp);
> + if (!child) {
> + return -1;
> + }
> +
> + /* Successor will be on or off based on our current state. */
> + child->disabled = bitmap->disabled;
> +
> + /* Install the successor and freeze the parent */
> + bitmap->successor = child;
> + return 0;
> +}
> +
> +/**
> + * For a bitmap with a successor, yield our name to the successor,
> + * Delete the old bitmap, and return a handle to the new bitmap.
> + */
> +BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,
> + BdrvDirtyBitmap *bitmap,
> + Error **errp)
> +{
> + char *name;
> + BdrvDirtyBitmap *successor = bitmap->successor;
> +
> + if (successor == NULL) {
> + error_setg(errp, "Cannot relinquish control if "
> + "there's no successor present.\n");
So while I'm unsure about full stops, I am pretty sure that we don't
really want newlines.
With the full stops and newlines removed (in all error_setg() calls in
this patch):
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (5 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 06/17] block: Add bitmap successors John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 17:47 ` Max Reitz
2015-02-13 17:33 ` Vladimir Sementsov-Ogievskiy
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 08/17] qmp: add block-dirty-bitmap-clear John Snow
` (10 subsequent siblings)
17 siblings, 2 replies; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
For "dirty-bitmap" sync mode, the block job will iterate through the
given dirty bitmap to decide if a sector needs backup (backup all the
dirty clusters and skip clean ones), just as allocation conditions of
"top" sync mode.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 9 +++
block/backup.c | 149 +++++++++++++++++++++++++++++++++++++++-------
block/mirror.c | 4 ++
blockdev.c | 18 +++++-
hmp.c | 3 +-
include/block/block.h | 1 +
include/block/block_int.h | 2 +
qapi/block-core.json | 13 ++--
qmp-commands.hx | 7 ++-
9 files changed, 172 insertions(+), 34 deletions(-)
diff --git a/block.c b/block.c
index 8d84ace..e93fceb 100644
--- a/block.c
+++ b/block.c
@@ -5648,6 +5648,15 @@ static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
}
}
+/**
+ * Advance an HBitmapIter to an arbitrary offset.
+ */
+void bdrv_set_dirty_iter(HBitmapIter *hbi, int64_t offset)
+{
+ assert(hbi->hb);
+ hbitmap_iter_init(hbi, hbi->hb, offset);
+}
+
int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
{
return hbitmap_count(bitmap->bitmap);
diff --git a/block/backup.c b/block/backup.c
index 1c535b1..bf8c0e7 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -37,6 +37,8 @@ typedef struct CowRequest {
typedef struct BackupBlockJob {
BlockJob common;
BlockDriverState *target;
+ /* bitmap for sync=dirty-bitmap */
+ BdrvDirtyBitmap *sync_bitmap;
MirrorSyncMode sync_mode;
RateLimit limit;
BlockdevOnError on_source_error;
@@ -242,6 +244,31 @@ static void backup_complete(BlockJob *job, void *opaque)
g_free(data);
}
+static bool coroutine_fn yield_and_check(BackupBlockJob *job)
+{
+ if (block_job_is_cancelled(&job->common)) {
+ return true;
+ }
+
+ /* we need to yield so that qemu_aio_flush() returns.
+ * (without, VM does not reboot)
+ */
+ if (job->common.speed) {
+ uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
+ job->sectors_read);
+ job->sectors_read = 0;
+ block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
+ } else {
+ block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
+ }
+
+ if (block_job_is_cancelled(&job->common)) {
+ return true;
+ }
+
+ return false;
+}
+
static void coroutine_fn backup_run(void *opaque)
{
BackupBlockJob *job = opaque;
@@ -254,13 +281,13 @@ static void coroutine_fn backup_run(void *opaque)
};
int64_t start, end;
int ret = 0;
+ bool error_is_read;
QLIST_INIT(&job->inflight_reqs);
qemu_co_rwlock_init(&job->flush_rwlock);
start = 0;
- end = DIV_ROUND_UP(job->common.len / BDRV_SECTOR_SIZE,
- BACKUP_SECTORS_PER_CLUSTER);
+ end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
job->bitmap = hbitmap_alloc(end, 0);
@@ -278,28 +305,61 @@ static void coroutine_fn backup_run(void *opaque)
qemu_coroutine_yield();
job->common.busy = true;
}
+ } else if (job->sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
+ /* Dirty Bitmap sync has a slightly different iteration method */
+ HBitmapIter hbi;
+ int64_t sector;
+ int64_t cluster;
+ int64_t last_cluster = -1;
+ bool polyrhythmic;
+
+ bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
+ /* Does the granularity happen to match our backup cluster size? */
+ polyrhythmic = (bdrv_dirty_bitmap_granularity(job->sync_bitmap) !=
+ BACKUP_CLUSTER_SIZE);
+
+ /* Find the next dirty /sector/ and copy that /cluster/ */
+ while ((sector = hbitmap_iter_next(&hbi)) != -1) {
+ if (yield_and_check(job)) {
+ goto leave;
+ }
+ cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
+
+ /* Play some catchup with the progress meter */
+ if (cluster != last_cluster + 1) {
+ job->common.offset += ((cluster - last_cluster - 1) *
+ BACKUP_CLUSTER_SIZE);
+ }
+
+ do {
+ ret = backup_do_cow(bs, cluster * BACKUP_SECTORS_PER_CLUSTER,
+ BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
+ if ((ret < 0) &&
+ backup_error_action(job, error_is_read, -ret) ==
+ BLOCK_ERROR_ACTION_REPORT) {
+ goto leave;
+ }
+ } while (ret < 0);
+
+ /* Advance (or rewind) our iterator if we need to. */
+ if (polyrhythmic) {
+ bdrv_set_dirty_iter(&hbi,
+ (cluster + 1) * BACKUP_SECTORS_PER_CLUSTER);
+ }
+
+ last_cluster = cluster;
+ }
+
+ /* Play some final catchup with the progress meter */
+ if (last_cluster + 1 < end) {
+ job->common.offset += ((end - last_cluster - 1) *
+ BACKUP_CLUSTER_SIZE);
+ }
+
} else {
/* Both FULL and TOP SYNC_MODE's require copying.. */
for (; start < end; start++) {
- bool error_is_read;
-
- if (block_job_is_cancelled(&job->common)) {
- break;
- }
-
- /* we need to yield so that qemu_aio_flush() returns.
- * (without, VM does not reboot)
- */
- if (job->common.speed) {
- uint64_t delay_ns = ratelimit_calculate_delay(
- &job->limit, job->sectors_read);
- job->sectors_read = 0;
- block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
- } else {
- block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
- }
-
- if (block_job_is_cancelled(&job->common)) {
+ if (yield_and_check(job)) {
break;
}
@@ -351,12 +411,26 @@ static void coroutine_fn backup_run(void *opaque)
}
}
+leave:
notifier_with_return_remove(&before_write);
/* wait until pending backup_do_cow() calls have completed */
qemu_co_rwlock_wrlock(&job->flush_rwlock);
qemu_co_rwlock_unlock(&job->flush_rwlock);
+ if (job->sync_bitmap) {
+ BdrvDirtyBitmap *bm;
+ if (ret < 0) {
+ /* Merge the successor back into the parent, delete nothing. */
+ bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
+ assert(bm);
+ bdrv_enable_dirty_bitmap(job->sync_bitmap);
+ } else {
+ /* Everything is fine, delete this bitmap and install the backup. */
+ bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
+ assert(bm);
+ }
+ }
hbitmap_free(job->bitmap);
bdrv_iostatus_disable(target);
@@ -369,6 +443,7 @@ static void coroutine_fn backup_run(void *opaque)
void backup_start(BlockDriverState *bs, BlockDriverState *target,
int64_t speed, MirrorSyncMode sync_mode,
+ BdrvDirtyBitmap *sync_bitmap,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
BlockCompletionFunc *cb, void *opaque,
@@ -412,17 +487,37 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
return;
}
+ if (sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
+ if (!sync_bitmap) {
+ error_setg(errp, "must provide a valid bitmap name for "
+ "\"dirty-bitmap\" sync mode");
+ return;
+ }
+
+ /* Create a new bitmap, and freeze/disable this one. */
+ if (bdrv_dirty_bitmap_create_successor(bs, sync_bitmap, errp) < 0) {
+ return;
+ }
+
+ } else if (sync_bitmap) {
+ error_setg(errp,
+ "a sync_bitmap was provided to backup_run, "
+ "but received an incompatible sync_mode (%s)",
+ MirrorSyncMode_lookup[sync_mode]);
+ return;
+ }
+
len = bdrv_getlength(bs);
if (len < 0) {
error_setg_errno(errp, -len, "unable to get length for '%s'",
bdrv_get_device_name(bs));
- return;
+ goto error;
}
BackupBlockJob *job = block_job_create(&backup_job_driver, bs, speed,
cb, opaque, errp);
if (!job) {
- return;
+ goto error;
}
bdrv_op_block_all(target, job->common.blocker);
@@ -431,7 +526,15 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
job->on_target_error = on_target_error;
job->target = target;
job->sync_mode = sync_mode;
+ job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP ?
+ sync_bitmap : NULL;
job->common.len = len;
job->common.co = qemu_coroutine_create(backup_run);
qemu_coroutine_enter(job->common.co, job);
+ return;
+
+ error:
+ if (sync_bitmap) {
+ bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
+ }
}
diff --git a/block/mirror.c b/block/mirror.c
index 77bd1ed..271dbf3 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -718,6 +718,10 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
bool is_none_mode;
BlockDriverState *base;
+ if (mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
+ error_setg(errp, "Sync mode 'dirty-bitmap' not supported");
+ return;
+ }
is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
base = mode == MIRROR_SYNC_MODE_TOP ? bs->backing_hd : NULL;
mirror_start_job(bs, target, replaces,
diff --git a/blockdev.c b/blockdev.c
index 826d0c1..3f41e82 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1569,6 +1569,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
backup->sync,
backup->has_mode, backup->mode,
backup->has_speed, backup->speed,
+ backup->has_bitmap, backup->bitmap,
backup->has_on_source_error, backup->on_source_error,
backup->has_on_target_error, backup->on_target_error,
&local_err);
@@ -2429,6 +2430,7 @@ void qmp_drive_backup(const char *device, const char *target,
enum MirrorSyncMode sync,
bool has_mode, enum NewImageMode mode,
bool has_speed, int64_t speed,
+ bool has_bitmap, const char *bitmap,
bool has_on_source_error, BlockdevOnError on_source_error,
bool has_on_target_error, BlockdevOnError on_target_error,
Error **errp)
@@ -2436,6 +2438,7 @@ void qmp_drive_backup(const char *device, const char *target,
BlockDriverState *bs;
BlockDriverState *target_bs;
BlockDriverState *source = NULL;
+ BdrvDirtyBitmap *bmap = NULL;
AioContext *aio_context;
BlockDriver *drv = NULL;
Error *local_err = NULL;
@@ -2534,7 +2537,16 @@ void qmp_drive_backup(const char *device, const char *target,
bdrv_set_aio_context(target_bs, aio_context);
- backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
+ if (has_bitmap) {
+ bmap = bdrv_find_dirty_bitmap(bs, bitmap);
+ if (!bmap) {
+ error_setg(errp, "Bitmap '%s' could not be found", bitmap);
+ goto out;
+ }
+ }
+
+ backup_start(bs, target_bs, speed, sync, bmap,
+ on_source_error, on_target_error,
block_job_cb, bs, &local_err);
if (local_err != NULL) {
bdrv_unref(target_bs);
@@ -2592,8 +2604,8 @@ void qmp_blockdev_backup(const char *device, const char *target,
bdrv_ref(target_bs);
bdrv_set_aio_context(target_bs, aio_context);
- backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
- block_job_cb, bs, &local_err);
+ backup_start(bs, target_bs, speed, sync, NULL, on_source_error,
+ on_target_error, block_job_cb, bs, &local_err);
if (local_err != NULL) {
bdrv_unref(target_bs);
error_propagate(errp, local_err);
diff --git a/hmp.c b/hmp.c
index b47f331..015499f 100644
--- a/hmp.c
+++ b/hmp.c
@@ -1027,7 +1027,8 @@ void hmp_drive_backup(Monitor *mon, const QDict *qdict)
qmp_drive_backup(device, filename, !!format, format,
full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
- true, mode, false, 0, false, 0, false, 0, &err);
+ true, mode, false, 0, false, NULL,
+ false, 0, false, 0, &err);
hmp_handle_error(mon, &err);
}
diff --git a/include/block/block.h b/include/block/block.h
index b2d84d6..8589e77 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -469,6 +469,7 @@ void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
void bdrv_dirty_iter_init(BlockDriverState *bs,
BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
+void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
void bdrv_enable_copy_on_read(BlockDriverState *bs);
diff --git a/include/block/block_int.h b/include/block/block_int.h
index 7ad1950..2233790 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -588,6 +588,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
* @target: Block device to write to.
* @speed: The maximum speed, in bytes per second, or 0 for unlimited.
* @sync_mode: What parts of the disk image should be copied to the destination.
+ * @sync_bitmap: The dirty bitmap if sync_mode is MIRROR_SYNC_MODE_DIRTY_BITMAP.
* @on_source_error: The action to take upon error reading from the source.
* @on_target_error: The action to take upon error writing to the target.
* @cb: Completion function for the job.
@@ -598,6 +599,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
*/
void backup_start(BlockDriverState *bs, BlockDriverState *target,
int64_t speed, MirrorSyncMode sync_mode,
+ BdrvDirtyBitmap *sync_bitmap,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
BlockCompletionFunc *cb, void *opaque,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 940eff7..9c5a99c 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -508,10 +508,12 @@
#
# @none: only copy data written from now on
#
+# @dirty-bitmap: only copy data described by the dirty bitmap. Since: 2.3
+#
# Since: 1.3
##
{ 'enum': 'MirrorSyncMode',
- 'data': ['top', 'full', 'none'] }
+ 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
##
# @BlockJobType:
@@ -686,14 +688,17 @@
# probe if @mode is 'existing', else the format of the source
#
# @sync: what parts of the disk image should be copied to the destination
-# (all the disk, only the sectors allocated in the topmost image, or
-# only new I/O).
+# (all the disk, only the sectors allocated in the topmost image, from a
+# dirty bitmap, or only new I/O).
#
# @mode: #optional whether and how QEMU should create a new image, default is
# 'absolute-paths'.
#
# @speed: #optional the maximum speed, in bytes per second
#
+# @bitmap: #optional the name of dirty bitmap if sync is "dirty-bitmap"
+# (Since 2.3)
+#
# @on-source-error: #optional the action to take on an error on the source,
# default 'report'. 'stop' and 'enospc' can only be used
# if the block device supports io-status (see BlockInfo).
@@ -711,7 +716,7 @@
{ 'type': 'DriveBackup',
'data': { 'device': 'str', 'target': 'str', '*format': 'str',
'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
- '*speed': 'int',
+ '*speed': 'int', '*bitmap': 'str',
'*on-source-error': 'BlockdevOnError',
'*on-target-error': 'BlockdevOnError' } }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index ce7782f..5aa3845 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1048,7 +1048,7 @@ EQMP
{
.name = "drive-backup",
.args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
- "on-source-error:s?,on-target-error:s?",
+ "bitmap:s?,on-source-error:s?,on-target-error:s?",
.mhandler.cmd_new = qmp_marshal_input_drive_backup,
},
@@ -1075,8 +1075,9 @@ Arguments:
(json-string, optional)
- "sync": what parts of the disk image should be copied to the destination;
possibilities include "full" for all the disk, "top" for only the sectors
- allocated in the topmost image, or "none" to only replicate new I/O
- (MirrorSyncMode).
+ allocated in the topmost image, "dirty-bitmap" for only the dirty sectors in
+ the bitmap, or "none" to only replicate new I/O (MirrorSyncMode).
+- "bitmap": dirty bitmap name for sync==dirty-bitmap
- "mode": whether and how QEMU should create a new image
(NewImageMode, optional, default 'absolute-paths')
- "speed": the maximum speed, in bytes per second (json-int, optional)
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
@ 2015-02-11 17:47 ` Max Reitz
2015-02-11 17:54 ` John Snow
2015-02-11 21:13 ` John Snow
2015-02-13 17:33 ` Vladimir Sementsov-Ogievskiy
1 sibling, 2 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 17:47 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> For "dirty-bitmap" sync mode, the block job will iterate through the
> given dirty bitmap to decide if a sector needs backup (backup all the
> dirty clusters and skip clean ones), just as allocation conditions of
> "top" sync mode.
>
> Signed-off-by: Fam Zheng <famz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 9 +++
> block/backup.c | 149 +++++++++++++++++++++++++++++++++++++++-------
> block/mirror.c | 4 ++
> blockdev.c | 18 +++++-
> hmp.c | 3 +-
> include/block/block.h | 1 +
> include/block/block_int.h | 2 +
> qapi/block-core.json | 13 ++--
> qmp-commands.hx | 7 ++-
> 9 files changed, 172 insertions(+), 34 deletions(-)
>
> diff --git a/block.c b/block.c
> index 8d84ace..e93fceb 100644
> --- a/block.c
> +++ b/block.c
> @@ -5648,6 +5648,15 @@ static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
> }
> }
>
> +/**
> + * Advance an HBitmapIter to an arbitrary offset.
> + */
> +void bdrv_set_dirty_iter(HBitmapIter *hbi, int64_t offset)
> +{
> + assert(hbi->hb);
> + hbitmap_iter_init(hbi, hbi->hb, offset);
> +}
> +
> int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
> {
> return hbitmap_count(bitmap->bitmap);
> diff --git a/block/backup.c b/block/backup.c
> index 1c535b1..bf8c0e7 100644
> --- a/block/backup.c
> +++ b/block/backup.c
> @@ -37,6 +37,8 @@ typedef struct CowRequest {
> typedef struct BackupBlockJob {
> BlockJob common;
> BlockDriverState *target;
> + /* bitmap for sync=dirty-bitmap */
> + BdrvDirtyBitmap *sync_bitmap;
> MirrorSyncMode sync_mode;
> RateLimit limit;
> BlockdevOnError on_source_error;
> @@ -242,6 +244,31 @@ static void backup_complete(BlockJob *job, void *opaque)
> g_free(data);
> }
>
> +static bool coroutine_fn yield_and_check(BackupBlockJob *job)
> +{
> + if (block_job_is_cancelled(&job->common)) {
> + return true;
> + }
> +
> + /* we need to yield so that qemu_aio_flush() returns.
> + * (without, VM does not reboot)
> + */
> + if (job->common.speed) {
> + uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
> + job->sectors_read);
> + job->sectors_read = 0;
> + block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
> + } else {
> + block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
> + }
> +
> + if (block_job_is_cancelled(&job->common)) {
> + return true;
> + }
> +
> + return false;
> +}
> +
> static void coroutine_fn backup_run(void *opaque)
> {
> BackupBlockJob *job = opaque;
> @@ -254,13 +281,13 @@ static void coroutine_fn backup_run(void *opaque)
> };
> int64_t start, end;
> int ret = 0;
> + bool error_is_read;
>
> QLIST_INIT(&job->inflight_reqs);
> qemu_co_rwlock_init(&job->flush_rwlock);
>
> start = 0;
> - end = DIV_ROUND_UP(job->common.len / BDRV_SECTOR_SIZE,
> - BACKUP_SECTORS_PER_CLUSTER);
> + end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
>
> job->bitmap = hbitmap_alloc(end, 0);
>
> @@ -278,28 +305,61 @@ static void coroutine_fn backup_run(void *opaque)
> qemu_coroutine_yield();
> job->common.busy = true;
> }
> + } else if (job->sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
> + /* Dirty Bitmap sync has a slightly different iteration method */
> + HBitmapIter hbi;
> + int64_t sector;
> + int64_t cluster;
> + int64_t last_cluster = -1;
> + bool polyrhythmic;
> +
> + bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
> + /* Does the granularity happen to match our backup cluster size? */
> + polyrhythmic = (bdrv_dirty_bitmap_granularity(job->sync_bitmap) !=
> + BACKUP_CLUSTER_SIZE);
> +
> + /* Find the next dirty /sector/ and copy that /cluster/ */
> + while ((sector = hbitmap_iter_next(&hbi)) != -1) {
> + if (yield_and_check(job)) {
> + goto leave;
> + }
> + cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
> +
> + /* Play some catchup with the progress meter */
> + if (cluster != last_cluster + 1) {
> + job->common.offset += ((cluster - last_cluster - 1) *
> + BACKUP_CLUSTER_SIZE);
> + }
I guess the "- 1" in the calculation comes from backup_do_cow()
modifying job->common.offset, too. How about simply overwriting it like
"job->common.offset = cluster * BACKUP_CLUSTER_SIZE"? And it may be a
good idea to move this update above yield_and_check() because that's the
progress we have actually done up to that point (and I imagine
yield_and_check() is the point where the caller might check the progress).
> +
> + do {
> + ret = backup_do_cow(bs, cluster * BACKUP_SECTORS_PER_CLUSTER,
> + BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
> + if ((ret < 0) &&
> + backup_error_action(job, error_is_read, -ret) ==
> + BLOCK_ERROR_ACTION_REPORT) {
> + goto leave;
> + }
> + } while (ret < 0);
> +
> + /* Advance (or rewind) our iterator if we need to. */
> + if (polyrhythmic) {
> + bdrv_set_dirty_iter(&hbi,
> + (cluster + 1) * BACKUP_SECTORS_PER_CLUSTER);
> + }
> +
> + last_cluster = cluster;
> + }
> +
> + /* Play some final catchup with the progress meter */
> + if (last_cluster + 1 < end) {
> + job->common.offset += ((end - last_cluster - 1) *
> + BACKUP_CLUSTER_SIZE);
> + }
> +
Superfluous empty line.
> } else {
> /* Both FULL and TOP SYNC_MODE's require copying.. */
> for (; start < end; start++) {
> - bool error_is_read;
> -
> - if (block_job_is_cancelled(&job->common)) {
> - break;
> - }
> -
> - /* we need to yield so that qemu_aio_flush() returns.
> - * (without, VM does not reboot)
> - */
> - if (job->common.speed) {
> - uint64_t delay_ns = ratelimit_calculate_delay(
> - &job->limit, job->sectors_read);
> - job->sectors_read = 0;
> - block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
> - } else {
> - block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
> - }
> -
> - if (block_job_is_cancelled(&job->common)) {
> + if (yield_and_check(job)) {
> break;
> }
>
> @@ -351,12 +411,26 @@ static void coroutine_fn backup_run(void *opaque)
> }
> }
>
> +leave:
> notifier_with_return_remove(&before_write);
>
> /* wait until pending backup_do_cow() calls have completed */
> qemu_co_rwlock_wrlock(&job->flush_rwlock);
> qemu_co_rwlock_unlock(&job->flush_rwlock);
>
> + if (job->sync_bitmap) {
> + BdrvDirtyBitmap *bm;
> + if (ret < 0) {
> + /* Merge the successor back into the parent, delete nothing. */
> + bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
> + assert(bm);
> + bdrv_enable_dirty_bitmap(job->sync_bitmap);
> + } else {
> + /* Everything is fine, delete this bitmap and install the backup. */
> + bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
> + assert(bm);
> + }
> + }
> hbitmap_free(job->bitmap);
>
> bdrv_iostatus_disable(target);
> @@ -369,6 +443,7 @@ static void coroutine_fn backup_run(void *opaque)
>
> void backup_start(BlockDriverState *bs, BlockDriverState *target,
> int64_t speed, MirrorSyncMode sync_mode,
> + BdrvDirtyBitmap *sync_bitmap,
> BlockdevOnError on_source_error,
> BlockdevOnError on_target_error,
> BlockCompletionFunc *cb, void *opaque,
> @@ -412,17 +487,37 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
> return;
> }
>
> + if (sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
> + if (!sync_bitmap) {
> + error_setg(errp, "must provide a valid bitmap name for "
> + "\"dirty-bitmap\" sync mode");
> + return;
> + }
> +
> + /* Create a new bitmap, and freeze/disable this one. */
> + if (bdrv_dirty_bitmap_create_successor(bs, sync_bitmap, errp) < 0) {
> + return;
> + }
> +
Another superfluous empty line.
> + } else if (sync_bitmap) {
> + error_setg(errp,
> + "a sync_bitmap was provided to backup_run, "
> + "but received an incompatible sync_mode (%s)",
> + MirrorSyncMode_lookup[sync_mode]);
> + return;
> + }
> +
> len = bdrv_getlength(bs);
> if (len < 0) {
> error_setg_errno(errp, -len, "unable to get length for '%s'",
> bdrv_get_device_name(bs));
> - return;
> + goto error;
> }
>
> BackupBlockJob *job = block_job_create(&backup_job_driver, bs, speed,
> cb, opaque, errp);
> if (!job) {
> - return;
> + goto error;
> }
>
> bdrv_op_block_all(target, job->common.blocker);
> @@ -431,7 +526,15 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target,
> job->on_target_error = on_target_error;
> job->target = target;
> job->sync_mode = sync_mode;
> + job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP ?
> + sync_bitmap : NULL;
> job->common.len = len;
> job->common.co = qemu_coroutine_create(backup_run);
> qemu_coroutine_enter(job->common.co, job);
> + return;
> +
> + error:
> + if (sync_bitmap) {
> + bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
> + }
> }
> diff --git a/block/mirror.c b/block/mirror.c
> index 77bd1ed..271dbf3 100644
> --- a/block/mirror.c
> +++ b/block/mirror.c
> @@ -718,6 +718,10 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
> bool is_none_mode;
> BlockDriverState *base;
>
> + if (mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
> + error_setg(errp, "Sync mode 'dirty-bitmap' not supported");
> + return;
> + }
> is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
> base = mode == MIRROR_SYNC_MODE_TOP ? bs->backing_hd : NULL;
> mirror_start_job(bs, target, replaces,
> diff --git a/blockdev.c b/blockdev.c
> index 826d0c1..3f41e82 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1569,6 +1569,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)
> backup->sync,
> backup->has_mode, backup->mode,
> backup->has_speed, backup->speed,
> + backup->has_bitmap, backup->bitmap,
> backup->has_on_source_error, backup->on_source_error,
> backup->has_on_target_error, backup->on_target_error,
> &local_err);
> @@ -2429,6 +2430,7 @@ void qmp_drive_backup(const char *device, const char *target,
> enum MirrorSyncMode sync,
> bool has_mode, enum NewImageMode mode,
> bool has_speed, int64_t speed,
> + bool has_bitmap, const char *bitmap,
> bool has_on_source_error, BlockdevOnError on_source_error,
> bool has_on_target_error, BlockdevOnError on_target_error,
> Error **errp)
> @@ -2436,6 +2438,7 @@ void qmp_drive_backup(const char *device, const char *target,
> BlockDriverState *bs;
> BlockDriverState *target_bs;
> BlockDriverState *source = NULL;
> + BdrvDirtyBitmap *bmap = NULL;
> AioContext *aio_context;
> BlockDriver *drv = NULL;
> Error *local_err = NULL;
> @@ -2534,7 +2537,16 @@ void qmp_drive_backup(const char *device, const char *target,
>
> bdrv_set_aio_context(target_bs, aio_context);
>
> - backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
> + if (has_bitmap) {
> + bmap = bdrv_find_dirty_bitmap(bs, bitmap);
> + if (!bmap) {
> + error_setg(errp, "Bitmap '%s' could not be found", bitmap);
> + goto out;
> + }
> + }
> +
> + backup_start(bs, target_bs, speed, sync, bmap,
> + on_source_error, on_target_error,
> block_job_cb, bs, &local_err);
> if (local_err != NULL) {
> bdrv_unref(target_bs);
> @@ -2592,8 +2604,8 @@ void qmp_blockdev_backup(const char *device, const char *target,
>
> bdrv_ref(target_bs);
> bdrv_set_aio_context(target_bs, aio_context);
> - backup_start(bs, target_bs, speed, sync, on_source_error, on_target_error,
> - block_job_cb, bs, &local_err);
> + backup_start(bs, target_bs, speed, sync, NULL, on_source_error,
> + on_target_error, block_job_cb, bs, &local_err);
> if (local_err != NULL) {
> bdrv_unref(target_bs);
> error_propagate(errp, local_err);
> diff --git a/hmp.c b/hmp.c
> index b47f331..015499f 100644
> --- a/hmp.c
> +++ b/hmp.c
> @@ -1027,7 +1027,8 @@ void hmp_drive_backup(Monitor *mon, const QDict *qdict)
>
> qmp_drive_backup(device, filename, !!format, format,
> full ? MIRROR_SYNC_MODE_FULL : MIRROR_SYNC_MODE_TOP,
> - true, mode, false, 0, false, 0, false, 0, &err);
> + true, mode, false, 0, false, NULL,
> + false, 0, false, 0, &err);
> hmp_handle_error(mon, &err);
> }
>
> diff --git a/include/block/block.h b/include/block/block.h
> index b2d84d6..8589e77 100644
> --- a/include/block/block.h
> +++ b/include/block/block.h
> @@ -469,6 +469,7 @@ void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
> int64_t cur_sector, int nr_sectors);
> void bdrv_dirty_iter_init(BlockDriverState *bs,
> BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
> +void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
> int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
>
> void bdrv_enable_copy_on_read(BlockDriverState *bs);
> diff --git a/include/block/block_int.h b/include/block/block_int.h
> index 7ad1950..2233790 100644
> --- a/include/block/block_int.h
> +++ b/include/block/block_int.h
> @@ -588,6 +588,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
> * @target: Block device to write to.
> * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
> * @sync_mode: What parts of the disk image should be copied to the destination.
> + * @sync_bitmap: The dirty bitmap if sync_mode is MIRROR_SYNC_MODE_DIRTY_BITMAP.
> * @on_source_error: The action to take upon error reading from the source.
> * @on_target_error: The action to take upon error writing to the target.
> * @cb: Completion function for the job.
> @@ -598,6 +599,7 @@ void mirror_start(BlockDriverState *bs, BlockDriverState *target,
> */
> void backup_start(BlockDriverState *bs, BlockDriverState *target,
> int64_t speed, MirrorSyncMode sync_mode,
> + BdrvDirtyBitmap *sync_bitmap,
> BlockdevOnError on_source_error,
> BlockdevOnError on_target_error,
> BlockCompletionFunc *cb, void *opaque,
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 940eff7..9c5a99c 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -508,10 +508,12 @@
> #
> # @none: only copy data written from now on
> #
> +# @dirty-bitmap: only copy data described by the dirty bitmap. Since: 2.3
> +#
> # Since: 1.3
> ##
> { 'enum': 'MirrorSyncMode',
> - 'data': ['top', 'full', 'none'] }
> + 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
>
> ##
> # @BlockJobType:
> @@ -686,14 +688,17 @@
> # probe if @mode is 'existing', else the format of the source
> #
> # @sync: what parts of the disk image should be copied to the destination
> -# (all the disk, only the sectors allocated in the topmost image, or
> -# only new I/O).
> +# (all the disk, only the sectors allocated in the topmost image, from a
> +# dirty bitmap, or only new I/O).
> #
> # @mode: #optional whether and how QEMU should create a new image, default is
> # 'absolute-paths'.
> #
> # @speed: #optional the maximum speed, in bytes per second
> #
> +# @bitmap: #optional the name of dirty bitmap if sync is "dirty-bitmap"
> +# (Since 2.3)
> +#
> # @on-source-error: #optional the action to take on an error on the source,
> # default 'report'. 'stop' and 'enospc' can only be used
> # if the block device supports io-status (see BlockInfo).
> @@ -711,7 +716,7 @@
> { 'type': 'DriveBackup',
> 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
> 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
> - '*speed': 'int',
> + '*speed': 'int', '*bitmap': 'str',
> '*on-source-error': 'BlockdevOnError',
> '*on-target-error': 'BlockdevOnError' } }
>
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index ce7782f..5aa3845 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -1048,7 +1048,7 @@ EQMP
> {
> .name = "drive-backup",
> .args_type = "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
> - "on-source-error:s?,on-target-error:s?",
> + "bitmap:s?,on-source-error:s?,on-target-error:s?",
> .mhandler.cmd_new = qmp_marshal_input_drive_backup,
> },
>
> @@ -1075,8 +1075,9 @@ Arguments:
> (json-string, optional)
> - "sync": what parts of the disk image should be copied to the destination;
> possibilities include "full" for all the disk, "top" for only the sectors
> - allocated in the topmost image, or "none" to only replicate new I/O
> - (MirrorSyncMode).
> + allocated in the topmost image, "dirty-bitmap" for only the dirty sectors in
> + the bitmap, or "none" to only replicate new I/O (MirrorSyncMode).
> +- "bitmap": dirty bitmap name for sync==dirty-bitmap
> - "mode": whether and how QEMU should create a new image
> (NewImageMode, optional, default 'absolute-paths')
> - "speed": the maximum speed, in bytes per second (json-int, optional)
Looks good to me in general, now I need to find out what the successor
bitmap is used for; but I guess I'll find that out by reviewing the rest
of this series.
Max
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-11 17:47 ` Max Reitz
@ 2015-02-11 17:54 ` John Snow
2015-02-11 18:18 ` Max Reitz
2015-02-11 21:13 ` John Snow
1 sibling, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-11 17:54 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/11/2015 12:47 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> For "dirty-bitmap" sync mode, the block job will iterate through the
>> given dirty bitmap to decide if a sector needs backup (backup all the
>> dirty clusters and skip clean ones), just as allocation conditions of
>> "top" sync mode.
>>
>> Signed-off-by: Fam Zheng <famz@redhat.com>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 9 +++
>> block/backup.c | 149
>> +++++++++++++++++++++++++++++++++++++++-------
>> block/mirror.c | 4 ++
>> blockdev.c | 18 +++++-
>> hmp.c | 3 +-
>> include/block/block.h | 1 +
>> include/block/block_int.h | 2 +
>> qapi/block-core.json | 13 ++--
>> qmp-commands.hx | 7 ++-
>> 9 files changed, 172 insertions(+), 34 deletions(-)
>>
>> diff --git a/block.c b/block.c
>> index 8d84ace..e93fceb 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -5648,6 +5648,15 @@ static void bdrv_reset_dirty(BlockDriverState
>> *bs, int64_t cur_sector,
>> }
>> }
>> +/**
>> + * Advance an HBitmapIter to an arbitrary offset.
>> + */
>> +void bdrv_set_dirty_iter(HBitmapIter *hbi, int64_t offset)
>> +{
>> + assert(hbi->hb);
>> + hbitmap_iter_init(hbi, hbi->hb, offset);
>> +}
>> +
>> int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap
>> *bitmap)
>> {
>> return hbitmap_count(bitmap->bitmap);
>> diff --git a/block/backup.c b/block/backup.c
>> index 1c535b1..bf8c0e7 100644
>> --- a/block/backup.c
>> +++ b/block/backup.c
>> @@ -37,6 +37,8 @@ typedef struct CowRequest {
>> typedef struct BackupBlockJob {
>> BlockJob common;
>> BlockDriverState *target;
>> + /* bitmap for sync=dirty-bitmap */
>> + BdrvDirtyBitmap *sync_bitmap;
>> MirrorSyncMode sync_mode;
>> RateLimit limit;
>> BlockdevOnError on_source_error;
>> @@ -242,6 +244,31 @@ static void backup_complete(BlockJob *job, void
>> *opaque)
>> g_free(data);
>> }
>> +static bool coroutine_fn yield_and_check(BackupBlockJob *job)
>> +{
>> + if (block_job_is_cancelled(&job->common)) {
>> + return true;
>> + }
>> +
>> + /* we need to yield so that qemu_aio_flush() returns.
>> + * (without, VM does not reboot)
>> + */
>> + if (job->common.speed) {
>> + uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
>> +
>> job->sectors_read);
>> + job->sectors_read = 0;
>> + block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
>> + } else {
>> + block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
>> + }
>> +
>> + if (block_job_is_cancelled(&job->common)) {
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> static void coroutine_fn backup_run(void *opaque)
>> {
>> BackupBlockJob *job = opaque;
>> @@ -254,13 +281,13 @@ static void coroutine_fn backup_run(void *opaque)
>> };
>> int64_t start, end;
>> int ret = 0;
>> + bool error_is_read;
>> QLIST_INIT(&job->inflight_reqs);
>> qemu_co_rwlock_init(&job->flush_rwlock);
>> start = 0;
>> - end = DIV_ROUND_UP(job->common.len / BDRV_SECTOR_SIZE,
>> - BACKUP_SECTORS_PER_CLUSTER);
>> + end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
>> job->bitmap = hbitmap_alloc(end, 0);
>> @@ -278,28 +305,61 @@ static void coroutine_fn backup_run(void *opaque)
>> qemu_coroutine_yield();
>> job->common.busy = true;
>> }
>> + } else if (job->sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
>> + /* Dirty Bitmap sync has a slightly different iteration
>> method */
>> + HBitmapIter hbi;
>> + int64_t sector;
>> + int64_t cluster;
>> + int64_t last_cluster = -1;
>> + bool polyrhythmic;
>> +
>> + bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
>> + /* Does the granularity happen to match our backup cluster
>> size? */
>> + polyrhythmic =
>> (bdrv_dirty_bitmap_granularity(job->sync_bitmap) !=
>> + BACKUP_CLUSTER_SIZE);
>> +
>> + /* Find the next dirty /sector/ and copy that /cluster/ */
>> + while ((sector = hbitmap_iter_next(&hbi)) != -1) {
>> + if (yield_and_check(job)) {
>> + goto leave;
>> + }
>> + cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
>> +
>> + /* Play some catchup with the progress meter */
>> + if (cluster != last_cluster + 1) {
>> + job->common.offset += ((cluster - last_cluster - 1) *
>> + BACKUP_CLUSTER_SIZE);
>> + }
>
> I guess the "- 1" in the calculation comes from backup_do_cow()
> modifying job->common.offset, too. How about simply overwriting it like
> "job->common.offset = cluster * BACKUP_CLUSTER_SIZE"? And it may be a
> good idea to move this update above yield_and_check() because that's the
> progress we have actually done up to that point (and I imagine
> yield_and_check() is the point where the caller might check the progress).
>
>> +
>> + do {
>> + ret = backup_do_cow(bs, cluster *
>> BACKUP_SECTORS_PER_CLUSTER,
>> + BACKUP_SECTORS_PER_CLUSTER,
>> &error_is_read);
>> + if ((ret < 0) &&
>> + backup_error_action(job, error_is_read, -ret) ==
>> + BLOCK_ERROR_ACTION_REPORT) {
>> + goto leave;
>> + }
>> + } while (ret < 0);
>> +
>> + /* Advance (or rewind) our iterator if we need to. */
>> + if (polyrhythmic) {
>> + bdrv_set_dirty_iter(&hbi,
>> + (cluster + 1) *
>> BACKUP_SECTORS_PER_CLUSTER);
>> + }
>> +
>> + last_cluster = cluster;
>> + }
>> +
>> + /* Play some final catchup with the progress meter */
>> + if (last_cluster + 1 < end) {
>> + job->common.offset += ((end - last_cluster - 1) *
>> + BACKUP_CLUSTER_SIZE);
>> + }
>> +
>
> Superfluous empty line.
>
>> } else {
>> /* Both FULL and TOP SYNC_MODE's require copying.. */
>> for (; start < end; start++) {
>> - bool error_is_read;
>> -
>> - if (block_job_is_cancelled(&job->common)) {
>> - break;
>> - }
>> -
>> - /* we need to yield so that qemu_aio_flush() returns.
>> - * (without, VM does not reboot)
>> - */
>> - if (job->common.speed) {
>> - uint64_t delay_ns = ratelimit_calculate_delay(
>> - &job->limit, job->sectors_read);
>> - job->sectors_read = 0;
>> - block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME,
>> delay_ns);
>> - } else {
>> - block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME,
>> 0);
>> - }
>> -
>> - if (block_job_is_cancelled(&job->common)) {
>> + if (yield_and_check(job)) {
>> break;
>> }
>> @@ -351,12 +411,26 @@ static void coroutine_fn backup_run(void *opaque)
>> }
>> }
>> +leave:
>> notifier_with_return_remove(&before_write);
>> /* wait until pending backup_do_cow() calls have completed */
>> qemu_co_rwlock_wrlock(&job->flush_rwlock);
>> qemu_co_rwlock_unlock(&job->flush_rwlock);
>> + if (job->sync_bitmap) {
>> + BdrvDirtyBitmap *bm;
>> + if (ret < 0) {
>> + /* Merge the successor back into the parent, delete
>> nothing. */
>> + bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
>> + assert(bm);
>> + bdrv_enable_dirty_bitmap(job->sync_bitmap);
>> + } else {
>> + /* Everything is fine, delete this bitmap and install the
>> backup. */
>> + bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
>> + assert(bm);
>> + }
>> + }
>> hbitmap_free(job->bitmap);
>> bdrv_iostatus_disable(target);
>> @@ -369,6 +443,7 @@ static void coroutine_fn backup_run(void *opaque)
>> void backup_start(BlockDriverState *bs, BlockDriverState *target,
>> int64_t speed, MirrorSyncMode sync_mode,
>> + BdrvDirtyBitmap *sync_bitmap,
>> BlockdevOnError on_source_error,
>> BlockdevOnError on_target_error,
>> BlockCompletionFunc *cb, void *opaque,
>> @@ -412,17 +487,37 @@ void backup_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> return;
>> }
>> + if (sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
>> + if (!sync_bitmap) {
>> + error_setg(errp, "must provide a valid bitmap name for "
>> + "\"dirty-bitmap\" sync mode");
>> + return;
>> + }
>> +
>> + /* Create a new bitmap, and freeze/disable this one. */
>> + if (bdrv_dirty_bitmap_create_successor(bs, sync_bitmap, errp)
>> < 0) {
>> + return;
>> + }
>> +
>
> Another superfluous empty line.
>
>> + } else if (sync_bitmap) {
>> + error_setg(errp,
>> + "a sync_bitmap was provided to backup_run, "
>> + "but received an incompatible sync_mode (%s)",
>> + MirrorSyncMode_lookup[sync_mode]);
>> + return;
>> + }
>> +
>> len = bdrv_getlength(bs);
>> if (len < 0) {
>> error_setg_errno(errp, -len, "unable to get length for '%s'",
>> bdrv_get_device_name(bs));
>> - return;
>> + goto error;
>> }
>> BackupBlockJob *job = block_job_create(&backup_job_driver, bs,
>> speed,
>> cb, opaque, errp);
>> if (!job) {
>> - return;
>> + goto error;
>> }
>> bdrv_op_block_all(target, job->common.blocker);
>> @@ -431,7 +526,15 @@ void backup_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> job->on_target_error = on_target_error;
>> job->target = target;
>> job->sync_mode = sync_mode;
>> + job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP ?
>> + sync_bitmap : NULL;
>> job->common.len = len;
>> job->common.co = qemu_coroutine_create(backup_run);
>> qemu_coroutine_enter(job->common.co, job);
>> + return;
>> +
>> + error:
>> + if (sync_bitmap) {
>> + bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
>> + }
>> }
>> diff --git a/block/mirror.c b/block/mirror.c
>> index 77bd1ed..271dbf3 100644
>> --- a/block/mirror.c
>> +++ b/block/mirror.c
>> @@ -718,6 +718,10 @@ void mirror_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> bool is_none_mode;
>> BlockDriverState *base;
>> + if (mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
>> + error_setg(errp, "Sync mode 'dirty-bitmap' not supported");
>> + return;
>> + }
>> is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
>> base = mode == MIRROR_SYNC_MODE_TOP ? bs->backing_hd : NULL;
>> mirror_start_job(bs, target, replaces,
>> diff --git a/blockdev.c b/blockdev.c
>> index 826d0c1..3f41e82 100644
>> --- a/blockdev.c
>> +++ b/blockdev.c
>> @@ -1569,6 +1569,7 @@ static void
>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>> backup->sync,
>> backup->has_mode, backup->mode,
>> backup->has_speed, backup->speed,
>> + backup->has_bitmap, backup->bitmap,
>> backup->has_on_source_error,
>> backup->on_source_error,
>> backup->has_on_target_error,
>> backup->on_target_error,
>> &local_err);
>> @@ -2429,6 +2430,7 @@ void qmp_drive_backup(const char *device, const
>> char *target,
>> enum MirrorSyncMode sync,
>> bool has_mode, enum NewImageMode mode,
>> bool has_speed, int64_t speed,
>> + bool has_bitmap, const char *bitmap,
>> bool has_on_source_error, BlockdevOnError
>> on_source_error,
>> bool has_on_target_error, BlockdevOnError
>> on_target_error,
>> Error **errp)
>> @@ -2436,6 +2438,7 @@ void qmp_drive_backup(const char *device, const
>> char *target,
>> BlockDriverState *bs;
>> BlockDriverState *target_bs;
>> BlockDriverState *source = NULL;
>> + BdrvDirtyBitmap *bmap = NULL;
>> AioContext *aio_context;
>> BlockDriver *drv = NULL;
>> Error *local_err = NULL;
>> @@ -2534,7 +2537,16 @@ void qmp_drive_backup(const char *device, const
>> char *target,
>> bdrv_set_aio_context(target_bs, aio_context);
>> - backup_start(bs, target_bs, speed, sync, on_source_error,
>> on_target_error,
>> + if (has_bitmap) {
>> + bmap = bdrv_find_dirty_bitmap(bs, bitmap);
>> + if (!bmap) {
>> + error_setg(errp, "Bitmap '%s' could not be found", bitmap);
>> + goto out;
>> + }
>> + }
>> +
>> + backup_start(bs, target_bs, speed, sync, bmap,
>> + on_source_error, on_target_error,
>> block_job_cb, bs, &local_err);
>> if (local_err != NULL) {
>> bdrv_unref(target_bs);
>> @@ -2592,8 +2604,8 @@ void qmp_blockdev_backup(const char *device,
>> const char *target,
>> bdrv_ref(target_bs);
>> bdrv_set_aio_context(target_bs, aio_context);
>> - backup_start(bs, target_bs, speed, sync, on_source_error,
>> on_target_error,
>> - block_job_cb, bs, &local_err);
>> + backup_start(bs, target_bs, speed, sync, NULL, on_source_error,
>> + on_target_error, block_job_cb, bs, &local_err);
>> if (local_err != NULL) {
>> bdrv_unref(target_bs);
>> error_propagate(errp, local_err);
>> diff --git a/hmp.c b/hmp.c
>> index b47f331..015499f 100644
>> --- a/hmp.c
>> +++ b/hmp.c
>> @@ -1027,7 +1027,8 @@ void hmp_drive_backup(Monitor *mon, const QDict
>> *qdict)
>> qmp_drive_backup(device, filename, !!format, format,
>> full ? MIRROR_SYNC_MODE_FULL :
>> MIRROR_SYNC_MODE_TOP,
>> - true, mode, false, 0, false, 0, false, 0, &err);
>> + true, mode, false, 0, false, NULL,
>> + false, 0, false, 0, &err);
>> hmp_handle_error(mon, &err);
>> }
>> diff --git a/include/block/block.h b/include/block/block.h
>> index b2d84d6..8589e77 100644
>> --- a/include/block/block.h
>> +++ b/include/block/block.h
>> @@ -469,6 +469,7 @@ void bdrv_reset_dirty_bitmap(BlockDriverState *bs,
>> BdrvDirtyBitmap *bitmap,
>> int64_t cur_sector, int nr_sectors);
>> void bdrv_dirty_iter_init(BlockDriverState *bs,
>> BdrvDirtyBitmap *bitmap, struct
>> HBitmapIter *hbi);
>> +void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
>> int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap
>> *bitmap);
>> void bdrv_enable_copy_on_read(BlockDriverState *bs);
>> diff --git a/include/block/block_int.h b/include/block/block_int.h
>> index 7ad1950..2233790 100644
>> --- a/include/block/block_int.h
>> +++ b/include/block/block_int.h
>> @@ -588,6 +588,7 @@ void mirror_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> * @target: Block device to write to.
>> * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
>> * @sync_mode: What parts of the disk image should be copied to the
>> destination.
>> + * @sync_bitmap: The dirty bitmap if sync_mode is
>> MIRROR_SYNC_MODE_DIRTY_BITMAP.
>> * @on_source_error: The action to take upon error reading from the
>> source.
>> * @on_target_error: The action to take upon error writing to the
>> target.
>> * @cb: Completion function for the job.
>> @@ -598,6 +599,7 @@ void mirror_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> */
>> void backup_start(BlockDriverState *bs, BlockDriverState *target,
>> int64_t speed, MirrorSyncMode sync_mode,
>> + BdrvDirtyBitmap *sync_bitmap,
>> BlockdevOnError on_source_error,
>> BlockdevOnError on_target_error,
>> BlockCompletionFunc *cb, void *opaque,
>> diff --git a/qapi/block-core.json b/qapi/block-core.json
>> index 940eff7..9c5a99c 100644
>> --- a/qapi/block-core.json
>> +++ b/qapi/block-core.json
>> @@ -508,10 +508,12 @@
>> #
>> # @none: only copy data written from now on
>> #
>> +# @dirty-bitmap: only copy data described by the dirty bitmap. Since:
>> 2.3
>> +#
>> # Since: 1.3
>> ##
>> { 'enum': 'MirrorSyncMode',
>> - 'data': ['top', 'full', 'none'] }
>> + 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
>> ##
>> # @BlockJobType:
>> @@ -686,14 +688,17 @@
>> # probe if @mode is 'existing', else the format of the source
>> #
>> # @sync: what parts of the disk image should be copied to the
>> destination
>> -# (all the disk, only the sectors allocated in the topmost
>> image, or
>> -# only new I/O).
>> +# (all the disk, only the sectors allocated in the topmost
>> image, from a
>> +# dirty bitmap, or only new I/O).
>> #
>> # @mode: #optional whether and how QEMU should create a new image,
>> default is
>> # 'absolute-paths'.
>> #
>> # @speed: #optional the maximum speed, in bytes per second
>> #
>> +# @bitmap: #optional the name of dirty bitmap if sync is "dirty-bitmap"
>> +# (Since 2.3)
>> +#
>> # @on-source-error: #optional the action to take on an error on the
>> source,
>> # default 'report'. 'stop' and 'enospc' can only
>> be used
>> # if the block device supports io-status (see
>> BlockInfo).
>> @@ -711,7 +716,7 @@
>> { 'type': 'DriveBackup',
>> 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>> 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
>> - '*speed': 'int',
>> + '*speed': 'int', '*bitmap': 'str',
>> '*on-source-error': 'BlockdevOnError',
>> '*on-target-error': 'BlockdevOnError' } }
>> diff --git a/qmp-commands.hx b/qmp-commands.hx
>> index ce7782f..5aa3845 100644
>> --- a/qmp-commands.hx
>> +++ b/qmp-commands.hx
>> @@ -1048,7 +1048,7 @@ EQMP
>> {
>> .name = "drive-backup",
>> .args_type =
>> "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
>> - "on-source-error:s?,on-target-error:s?",
>> + "bitmap:s?,on-source-error:s?,on-target-error:s?",
>> .mhandler.cmd_new = qmp_marshal_input_drive_backup,
>> },
>> @@ -1075,8 +1075,9 @@ Arguments:
>> (json-string, optional)
>> - "sync": what parts of the disk image should be copied to the
>> destination;
>> possibilities include "full" for all the disk, "top" for only the
>> sectors
>> - allocated in the topmost image, or "none" to only replicate new I/O
>> - (MirrorSyncMode).
>> + allocated in the topmost image, "dirty-bitmap" for only the dirty
>> sectors in
>> + the bitmap, or "none" to only replicate new I/O (MirrorSyncMode).
>> +- "bitmap": dirty bitmap name for sync==dirty-bitmap
>> - "mode": whether and how QEMU should create a new image
>> (NewImageMode, optional, default 'absolute-paths')
>> - "speed": the maximum speed, in bytes per second (json-int, optional)
>
> Looks good to me in general, now I need to find out what the successor
> bitmap is used for; but I guess I'll find that out by reviewing the rest
> of this series.
>
> Max
They don't really come up again, actually.
The basic idea is this: While the backup is going on, reads and writes
may occur (albeit delayed) and we want to track those writes in a
separate bitmap for the duration of the backup operation.
If the backup operation fails, we use the dirty sector tracking info in
the successor to know what has changed since we started the backup, and
we merge this bitmap back into the originating bitmap; then if an
incremental backup is tried again, it includes all of the original data
plus any data changed while we failed to do a backup.
If the backup operation succeeds, the originating bitmap is deleted and
the successor is installed in its place.
It's a namespace trick: by having an anonymous bitmap as a child of the
"real" bitmap, the real bitmap can be frozen and prohibited from being
moved, renamed, deleted, etc. This prevents the user from adding a new
bitmap with the same name or similar while the backup is in progress.
A previous approach was to immediately take the bitmap off of the BDS,
but in the error case here, the logic becomes more complicated when we
need to re-install the bitmap but the user has already installed a new
bitmap with the same name, etc.
So the general lifetime is this:
(1) A backup is started. the block/backup routine calls create_successor.
(2) If the backup fails to start, the block/backup routine will call the
"reclaim" method, which will merge the (empty) successor back into the
original bitmap, unfreezing it.
(3) If the backup starts, and then fails, the bitmap is "reclaim"ed
(merged back into one bitmap.)
(4) If the backup succeeds, the bitmap "abdicates" to the successor.
(The parent bitmap is erased and the successor is installed in its place.)
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-11 17:54 ` John Snow
@ 2015-02-11 18:18 ` Max Reitz
2015-02-11 18:31 ` John Snow
0 siblings, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-11 18:18 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-11 at 12:54, John Snow wrote:
>
> On 02/11/2015 12:47 PM, Max Reitz wrote:
>> Looks good to me in general, now I need to find out what the successor
>> bitmap is used for; but I guess I'll find that out by reviewing the rest
>> of this series.
>>
>> Max
>
> They don't really come up again, actually.
>
> The basic idea is this: While the backup is going on, reads and writes
> may occur (albeit delayed) and we want to track those writes in a
> separate bitmap for the duration of the backup operation.
Yes, I thought as much; but where are writes to the named bitmap being
redirected to its successor? bdrv_set_dirty() doesn't do that, as far as
I can see.
> If the backup operation fails, we use the dirty sector tracking info
> in the successor to know what has changed since we started the backup,
> and we merge this bitmap back into the originating bitmap; then if an
> incremental backup is tried again, it includes all of the original
> data plus any data changed while we failed to do a backup.
>
> If the backup operation succeeds, the originating bitmap is deleted
> and the successor is installed in its place.
>
> It's a namespace trick: by having an anonymous bitmap as a child of
> the "real" bitmap, the real bitmap can be frozen and prohibited from
> being moved, renamed, deleted, etc. This prevents the user from adding
> a new bitmap with the same name or similar while the backup is in
> progress.
Hm, if it's just for that, wouldn't disabling the bitmap suffice?
Max
> A previous approach was to immediately take the bitmap off of the BDS,
> but in the error case here, the logic becomes more complicated when we
> need to re-install the bitmap but the user has already installed a new
> bitmap with the same name, etc.
>
> So the general lifetime is this:
>
> (1) A backup is started. the block/backup routine calls create_successor.
> (2) If the backup fails to start, the block/backup routine will call
> the "reclaim" method, which will merge the (empty) successor back into
> the original bitmap, unfreezing it.
> (3) If the backup starts, and then fails, the bitmap is "reclaim"ed
> (merged back into one bitmap.)
> (4) If the backup succeeds, the bitmap "abdicates" to the successor.
> (The parent bitmap is erased and the successor is installed in its
> place.)
Yes, see the graph at the whiteboard behind me. :-)
Max
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-11 18:18 ` Max Reitz
@ 2015-02-11 18:31 ` John Snow
2015-02-11 18:33 ` Max Reitz
0 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-11 18:31 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/11/2015 01:18 PM, Max Reitz wrote:
> On 2015-02-11 at 12:54, John Snow wrote:
>>
>> On 02/11/2015 12:47 PM, Max Reitz wrote:
>>> Looks good to me in general, now I need to find out what the successor
>>> bitmap is used for; but I guess I'll find that out by reviewing the rest
>>> of this series.
>>>
>>> Max
>>
>> They don't really come up again, actually.
>>
>> The basic idea is this: While the backup is going on, reads and writes
>> may occur (albeit delayed) and we want to track those writes in a
>> separate bitmap for the duration of the backup operation.
>
> Yes, I thought as much; but where are writes to the named bitmap being
> redirected to its successor? bdrv_set_dirty() doesn't do that, as far as
> I can see.
>
bdrv_dirty_bitmap_create_successor calls bdrv_create_dirty_bitmap, which
installs it in the bitmap chain attached to a BDS:
QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
which is read by bdrv_set_dirty.
bdrv_set_dirty operates on all bitmaps attached to a BDS, while
bdrv_set_dirty_bitmap operates on a single specific instance.
>> If the backup operation fails, we use the dirty sector tracking info
>> in the successor to know what has changed since we started the backup,
>> and we merge this bitmap back into the originating bitmap; then if an
>> incremental backup is tried again, it includes all of the original
>> data plus any data changed while we failed to do a backup.
>>
>> If the backup operation succeeds, the originating bitmap is deleted
>> and the successor is installed in its place.
>>
>> It's a namespace trick: by having an anonymous bitmap as a child of
>> the "real" bitmap, the real bitmap can be frozen and prohibited from
>> being moved, renamed, deleted, etc. This prevents the user from adding
>> a new bitmap with the same name or similar while the backup is in
>> progress.
>
> Hm, if it's just for that, wouldn't disabling the bitmap suffice?
>
> Max
>
Kind of? We still want to track writes while it's disabled.
If we try to use a single bitmap, we have no real way to know which bits
to clear after the operation succeeds. I think two bitmaps is a
requirement to accommodate both failure and success cases.
A distinction is made between a disabled bitmap (which is just
read-only: it can be deleted) and a frozen bitmap (which is in-use by an
operation, implicitly disabled, and cannot be enabled, disabled,
deleted, cleared, set or reset.)
>> A previous approach was to immediately take the bitmap off of the BDS,
>> but in the error case here, the logic becomes more complicated when we
>> need to re-install the bitmap but the user has already installed a new
>> bitmap with the same name, etc.
>>
>> So the general lifetime is this:
>>
>> (1) A backup is started. the block/backup routine calls create_successor.
>> (2) If the backup fails to start, the block/backup routine will call
>> the "reclaim" method, which will merge the (empty) successor back into
>> the original bitmap, unfreezing it.
>> (3) If the backup starts, and then fails, the bitmap is "reclaim"ed
>> (merged back into one bitmap.)
>> (4) If the backup succeeds, the bitmap "abdicates" to the successor.
>> (The parent bitmap is erased and the successor is installed in its
>> place.)
>
> Yes, see the graph at the whiteboard behind me. :-)
>
> Max
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-11 18:31 ` John Snow
@ 2015-02-11 18:33 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 18:33 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-11 at 13:31, John Snow wrote:
>
>
> On 02/11/2015 01:18 PM, Max Reitz wrote:
>> On 2015-02-11 at 12:54, John Snow wrote:
>>>
>>> On 02/11/2015 12:47 PM, Max Reitz wrote:
>>>> Looks good to me in general, now I need to find out what the successor
>>>> bitmap is used for; but I guess I'll find that out by reviewing the
>>>> rest
>>>> of this series.
>>>>
>>>> Max
>>>
>>> They don't really come up again, actually.
>>>
>>> The basic idea is this: While the backup is going on, reads and writes
>>> may occur (albeit delayed) and we want to track those writes in a
>>> separate bitmap for the duration of the backup operation.
>>
>> Yes, I thought as much; but where are writes to the named bitmap being
>> redirected to its successor? bdrv_set_dirty() doesn't do that, as far as
>> I can see.
>>
>
> bdrv_dirty_bitmap_create_successor calls bdrv_create_dirty_bitmap,
> which installs it in the bitmap chain attached to a BDS:
>
> QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
>
> which is read by bdrv_set_dirty.
Oooh, clever. Right.
>
> bdrv_set_dirty operates on all bitmaps attached to a BDS, while
> bdrv_set_dirty_bitmap operates on a single specific instance.
>
>>> If the backup operation fails, we use the dirty sector tracking info
>>> in the successor to know what has changed since we started the backup,
>>> and we merge this bitmap back into the originating bitmap; then if an
>>> incremental backup is tried again, it includes all of the original
>>> data plus any data changed while we failed to do a backup.
>>>
>>> If the backup operation succeeds, the originating bitmap is deleted
>>> and the successor is installed in its place.
>>>
>>> It's a namespace trick: by having an anonymous bitmap as a child of
>>> the "real" bitmap, the real bitmap can be frozen and prohibited from
>>> being moved, renamed, deleted, etc. This prevents the user from adding
>>> a new bitmap with the same name or similar while the backup is in
>>> progress.
>>
>> Hm, if it's just for that, wouldn't disabling the bitmap suffice?
>>
>> Max
>>
>
> Kind of? We still want to track writes while it's disabled.
Right, I was assuming here that writes are not tracked in the successor.
Thanks for pointing out that they are!
Max
>
> If we try to use a single bitmap, we have no real way to know which
> bits to clear after the operation succeeds. I think two bitmaps is a
> requirement to accommodate both failure and success cases.
>
> A distinction is made between a disabled bitmap (which is just
> read-only: it can be deleted) and a frozen bitmap (which is in-use by
> an operation, implicitly disabled, and cannot be enabled, disabled,
> deleted, cleared, set or reset.)
>
>>> A previous approach was to immediately take the bitmap off of the BDS,
>>> but in the error case here, the logic becomes more complicated when we
>>> need to re-install the bitmap but the user has already installed a new
>>> bitmap with the same name, etc.
>>>
>>> So the general lifetime is this:
>>>
>>> (1) A backup is started. the block/backup routine calls
>>> create_successor.
>>> (2) If the backup fails to start, the block/backup routine will call
>>> the "reclaim" method, which will merge the (empty) successor back into
>>> the original bitmap, unfreezing it.
>>> (3) If the backup starts, and then fails, the bitmap is "reclaim"ed
>>> (merged back into one bitmap.)
>>> (4) If the backup succeeds, the bitmap "abdicates" to the successor.
>>> (The parent bitmap is erased and the successor is installed in its
>>> place.)
>>
>> Yes, see the graph at the whiteboard behind me. :-)
>>
>> Max
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-11 17:47 ` Max Reitz
2015-02-11 17:54 ` John Snow
@ 2015-02-11 21:13 ` John Snow
1 sibling, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-11 21:13 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/11/2015 12:47 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> For "dirty-bitmap" sync mode, the block job will iterate through the
>> given dirty bitmap to decide if a sector needs backup (backup all the
>> dirty clusters and skip clean ones), just as allocation conditions of
>> "top" sync mode.
>>
>> Signed-off-by: Fam Zheng <famz@redhat.com>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 9 +++
>> block/backup.c | 149
>> +++++++++++++++++++++++++++++++++++++++-------
>> block/mirror.c | 4 ++
>> blockdev.c | 18 +++++-
>> hmp.c | 3 +-
>> include/block/block.h | 1 +
>> include/block/block_int.h | 2 +
>> qapi/block-core.json | 13 ++--
>> qmp-commands.hx | 7 ++-
>> 9 files changed, 172 insertions(+), 34 deletions(-)
>>
>> diff --git a/block.c b/block.c
>> index 8d84ace..e93fceb 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -5648,6 +5648,15 @@ static void bdrv_reset_dirty(BlockDriverState
>> *bs, int64_t cur_sector,
>> }
>> }
>> +/**
>> + * Advance an HBitmapIter to an arbitrary offset.
>> + */
>> +void bdrv_set_dirty_iter(HBitmapIter *hbi, int64_t offset)
>> +{
>> + assert(hbi->hb);
>> + hbitmap_iter_init(hbi, hbi->hb, offset);
>> +}
>> +
>> int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap
>> *bitmap)
>> {
>> return hbitmap_count(bitmap->bitmap);
>> diff --git a/block/backup.c b/block/backup.c
>> index 1c535b1..bf8c0e7 100644
>> --- a/block/backup.c
>> +++ b/block/backup.c
>> @@ -37,6 +37,8 @@ typedef struct CowRequest {
>> typedef struct BackupBlockJob {
>> BlockJob common;
>> BlockDriverState *target;
>> + /* bitmap for sync=dirty-bitmap */
>> + BdrvDirtyBitmap *sync_bitmap;
>> MirrorSyncMode sync_mode;
>> RateLimit limit;
>> BlockdevOnError on_source_error;
>> @@ -242,6 +244,31 @@ static void backup_complete(BlockJob *job, void
>> *opaque)
>> g_free(data);
>> }
>> +static bool coroutine_fn yield_and_check(BackupBlockJob *job)
>> +{
>> + if (block_job_is_cancelled(&job->common)) {
>> + return true;
>> + }
>> +
>> + /* we need to yield so that qemu_aio_flush() returns.
>> + * (without, VM does not reboot)
>> + */
>> + if (job->common.speed) {
>> + uint64_t delay_ns = ratelimit_calculate_delay(&job->limit,
>> +
>> job->sectors_read);
>> + job->sectors_read = 0;
>> + block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, delay_ns);
>> + } else {
>> + block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME, 0);
>> + }
>> +
>> + if (block_job_is_cancelled(&job->common)) {
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> static void coroutine_fn backup_run(void *opaque)
>> {
>> BackupBlockJob *job = opaque;
>> @@ -254,13 +281,13 @@ static void coroutine_fn backup_run(void *opaque)
>> };
>> int64_t start, end;
>> int ret = 0;
>> + bool error_is_read;
>> QLIST_INIT(&job->inflight_reqs);
>> qemu_co_rwlock_init(&job->flush_rwlock);
>> start = 0;
>> - end = DIV_ROUND_UP(job->common.len / BDRV_SECTOR_SIZE,
>> - BACKUP_SECTORS_PER_CLUSTER);
>> + end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
>> job->bitmap = hbitmap_alloc(end, 0);
>> @@ -278,28 +305,61 @@ static void coroutine_fn backup_run(void *opaque)
>> qemu_coroutine_yield();
>> job->common.busy = true;
>> }
>> + } else if (job->sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
>> + /* Dirty Bitmap sync has a slightly different iteration
>> method */
>> + HBitmapIter hbi;
>> + int64_t sector;
>> + int64_t cluster;
>> + int64_t last_cluster = -1;
>> + bool polyrhythmic;
>> +
>> + bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
>> + /* Does the granularity happen to match our backup cluster
>> size? */
>> + polyrhythmic =
>> (bdrv_dirty_bitmap_granularity(job->sync_bitmap) !=
>> + BACKUP_CLUSTER_SIZE);
>> +
>> + /* Find the next dirty /sector/ and copy that /cluster/ */
>> + while ((sector = hbitmap_iter_next(&hbi)) != -1) {
>> + if (yield_and_check(job)) {
>> + goto leave;
>> + }
>> + cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
>> +
>> + /* Play some catchup with the progress meter */
>> + if (cluster != last_cluster + 1) {
>> + job->common.offset += ((cluster - last_cluster - 1) *
>> + BACKUP_CLUSTER_SIZE);
>> + }
>
> I guess the "- 1" in the calculation comes from backup_do_cow()
> modifying job->common.offset, too. How about simply overwriting it like
> "job->common.offset = cluster * BACKUP_CLUSTER_SIZE"? And it may be a
> good idea to move this update above yield_and_check() because that's the
> progress we have actually done up to that point (and I imagine
> yield_and_check() is the point where the caller might check the progress).
>
Yes: If the last cluster we wrote out was 2, and we're on 4 now, we only
need to make up for #3, because #4 will be handled in the usual ways.
I don't know how wise this was, but I wanted to only "fake" progress
updates for clusters I *know* I skipped in incremental backups, so I
wanted to make sure the math worked out. Just overwriting the progress
seemed like cheating a little, I guess ...
It was kind of just a "testing thing" to just do the additions and see
if the math checked out in the end. You may notice that unlike Fam's
iotests, I leave the progress checks enabled for my tests.
Putting it above the cancellation check does make sense, though. I
didn't test cancellations as well as I ought have.
>> +
>> + do {
>> + ret = backup_do_cow(bs, cluster *
>> BACKUP_SECTORS_PER_CLUSTER,
>> + BACKUP_SECTORS_PER_CLUSTER,
>> &error_is_read);
>> + if ((ret < 0) &&
>> + backup_error_action(job, error_is_read, -ret) ==
>> + BLOCK_ERROR_ACTION_REPORT) {
>> + goto leave;
>> + }
>> + } while (ret < 0);
>> +
>> + /* Advance (or rewind) our iterator if we need to. */
>> + if (polyrhythmic) {
>> + bdrv_set_dirty_iter(&hbi,
>> + (cluster + 1) *
>> BACKUP_SECTORS_PER_CLUSTER);
>> + }
>> +
>> + last_cluster = cluster;
>> + }
>> +
>> + /* Play some final catchup with the progress meter */
>> + if (last_cluster + 1 < end) {
>> + job->common.offset += ((end - last_cluster - 1) *
>> + BACKUP_CLUSTER_SIZE);
>> + }
>> +
>
> Superfluous empty line.
>
>> } else {
>> /* Both FULL and TOP SYNC_MODE's require copying.. */
>> for (; start < end; start++) {
>> - bool error_is_read;
>> -
>> - if (block_job_is_cancelled(&job->common)) {
>> - break;
>> - }
>> -
>> - /* we need to yield so that qemu_aio_flush() returns.
>> - * (without, VM does not reboot)
>> - */
>> - if (job->common.speed) {
>> - uint64_t delay_ns = ratelimit_calculate_delay(
>> - &job->limit, job->sectors_read);
>> - job->sectors_read = 0;
>> - block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME,
>> delay_ns);
>> - } else {
>> - block_job_sleep_ns(&job->common, QEMU_CLOCK_REALTIME,
>> 0);
>> - }
>> -
>> - if (block_job_is_cancelled(&job->common)) {
>> + if (yield_and_check(job)) {
>> break;
>> }
>> @@ -351,12 +411,26 @@ static void coroutine_fn backup_run(void *opaque)
>> }
>> }
>> +leave:
>> notifier_with_return_remove(&before_write);
>> /* wait until pending backup_do_cow() calls have completed */
>> qemu_co_rwlock_wrlock(&job->flush_rwlock);
>> qemu_co_rwlock_unlock(&job->flush_rwlock);
>> + if (job->sync_bitmap) {
>> + BdrvDirtyBitmap *bm;
>> + if (ret < 0) {
>> + /* Merge the successor back into the parent, delete
>> nothing. */
>> + bm = bdrv_reclaim_dirty_bitmap(bs, job->sync_bitmap, NULL);
>> + assert(bm);
>> + bdrv_enable_dirty_bitmap(job->sync_bitmap);
>> + } else {
>> + /* Everything is fine, delete this bitmap and install the
>> backup. */
>> + bm = bdrv_dirty_bitmap_abdicate(bs, job->sync_bitmap, NULL);
>> + assert(bm);
>> + }
>> + }
>> hbitmap_free(job->bitmap);
>> bdrv_iostatus_disable(target);
>> @@ -369,6 +443,7 @@ static void coroutine_fn backup_run(void *opaque)
>> void backup_start(BlockDriverState *bs, BlockDriverState *target,
>> int64_t speed, MirrorSyncMode sync_mode,
>> + BdrvDirtyBitmap *sync_bitmap,
>> BlockdevOnError on_source_error,
>> BlockdevOnError on_target_error,
>> BlockCompletionFunc *cb, void *opaque,
>> @@ -412,17 +487,37 @@ void backup_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> return;
>> }
>> + if (sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
>> + if (!sync_bitmap) {
>> + error_setg(errp, "must provide a valid bitmap name for "
>> + "\"dirty-bitmap\" sync mode");
>> + return;
>> + }
>> +
>> + /* Create a new bitmap, and freeze/disable this one. */
>> + if (bdrv_dirty_bitmap_create_successor(bs, sync_bitmap, errp)
>> < 0) {
>> + return;
>> + }
>> +
>
> Another superfluous empty line.
>
>> + } else if (sync_bitmap) {
>> + error_setg(errp,
>> + "a sync_bitmap was provided to backup_run, "
>> + "but received an incompatible sync_mode (%s)",
>> + MirrorSyncMode_lookup[sync_mode]);
>> + return;
>> + }
>> +
>> len = bdrv_getlength(bs);
>> if (len < 0) {
>> error_setg_errno(errp, -len, "unable to get length for '%s'",
>> bdrv_get_device_name(bs));
>> - return;
>> + goto error;
>> }
>> BackupBlockJob *job = block_job_create(&backup_job_driver, bs,
>> speed,
>> cb, opaque, errp);
>> if (!job) {
>> - return;
>> + goto error;
>> }
>> bdrv_op_block_all(target, job->common.blocker);
>> @@ -431,7 +526,15 @@ void backup_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> job->on_target_error = on_target_error;
>> job->target = target;
>> job->sync_mode = sync_mode;
>> + job->sync_bitmap = sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP ?
>> + sync_bitmap : NULL;
>> job->common.len = len;
>> job->common.co = qemu_coroutine_create(backup_run);
>> qemu_coroutine_enter(job->common.co, job);
>> + return;
>> +
>> + error:
>> + if (sync_bitmap) {
>> + bdrv_reclaim_dirty_bitmap(bs, sync_bitmap, NULL);
>> + }
>> }
>> diff --git a/block/mirror.c b/block/mirror.c
>> index 77bd1ed..271dbf3 100644
>> --- a/block/mirror.c
>> +++ b/block/mirror.c
>> @@ -718,6 +718,10 @@ void mirror_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> bool is_none_mode;
>> BlockDriverState *base;
>> + if (mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
>> + error_setg(errp, "Sync mode 'dirty-bitmap' not supported");
>> + return;
>> + }
>> is_none_mode = mode == MIRROR_SYNC_MODE_NONE;
>> base = mode == MIRROR_SYNC_MODE_TOP ? bs->backing_hd : NULL;
>> mirror_start_job(bs, target, replaces,
>> diff --git a/blockdev.c b/blockdev.c
>> index 826d0c1..3f41e82 100644
>> --- a/blockdev.c
>> +++ b/blockdev.c
>> @@ -1569,6 +1569,7 @@ static void
>> drive_backup_prepare(BlkTransactionState *common, Error **errp)
>> backup->sync,
>> backup->has_mode, backup->mode,
>> backup->has_speed, backup->speed,
>> + backup->has_bitmap, backup->bitmap,
>> backup->has_on_source_error,
>> backup->on_source_error,
>> backup->has_on_target_error,
>> backup->on_target_error,
>> &local_err);
>> @@ -2429,6 +2430,7 @@ void qmp_drive_backup(const char *device, const
>> char *target,
>> enum MirrorSyncMode sync,
>> bool has_mode, enum NewImageMode mode,
>> bool has_speed, int64_t speed,
>> + bool has_bitmap, const char *bitmap,
>> bool has_on_source_error, BlockdevOnError
>> on_source_error,
>> bool has_on_target_error, BlockdevOnError
>> on_target_error,
>> Error **errp)
>> @@ -2436,6 +2438,7 @@ void qmp_drive_backup(const char *device, const
>> char *target,
>> BlockDriverState *bs;
>> BlockDriverState *target_bs;
>> BlockDriverState *source = NULL;
>> + BdrvDirtyBitmap *bmap = NULL;
>> AioContext *aio_context;
>> BlockDriver *drv = NULL;
>> Error *local_err = NULL;
>> @@ -2534,7 +2537,16 @@ void qmp_drive_backup(const char *device, const
>> char *target,
>> bdrv_set_aio_context(target_bs, aio_context);
>> - backup_start(bs, target_bs, speed, sync, on_source_error,
>> on_target_error,
>> + if (has_bitmap) {
>> + bmap = bdrv_find_dirty_bitmap(bs, bitmap);
>> + if (!bmap) {
>> + error_setg(errp, "Bitmap '%s' could not be found", bitmap);
>> + goto out;
>> + }
>> + }
>> +
>> + backup_start(bs, target_bs, speed, sync, bmap,
>> + on_source_error, on_target_error,
>> block_job_cb, bs, &local_err);
>> if (local_err != NULL) {
>> bdrv_unref(target_bs);
>> @@ -2592,8 +2604,8 @@ void qmp_blockdev_backup(const char *device,
>> const char *target,
>> bdrv_ref(target_bs);
>> bdrv_set_aio_context(target_bs, aio_context);
>> - backup_start(bs, target_bs, speed, sync, on_source_error,
>> on_target_error,
>> - block_job_cb, bs, &local_err);
>> + backup_start(bs, target_bs, speed, sync, NULL, on_source_error,
>> + on_target_error, block_job_cb, bs, &local_err);
>> if (local_err != NULL) {
>> bdrv_unref(target_bs);
>> error_propagate(errp, local_err);
>> diff --git a/hmp.c b/hmp.c
>> index b47f331..015499f 100644
>> --- a/hmp.c
>> +++ b/hmp.c
>> @@ -1027,7 +1027,8 @@ void hmp_drive_backup(Monitor *mon, const QDict
>> *qdict)
>> qmp_drive_backup(device, filename, !!format, format,
>> full ? MIRROR_SYNC_MODE_FULL :
>> MIRROR_SYNC_MODE_TOP,
>> - true, mode, false, 0, false, 0, false, 0, &err);
>> + true, mode, false, 0, false, NULL,
>> + false, 0, false, 0, &err);
>> hmp_handle_error(mon, &err);
>> }
>> diff --git a/include/block/block.h b/include/block/block.h
>> index b2d84d6..8589e77 100644
>> --- a/include/block/block.h
>> +++ b/include/block/block.h
>> @@ -469,6 +469,7 @@ void bdrv_reset_dirty_bitmap(BlockDriverState *bs,
>> BdrvDirtyBitmap *bitmap,
>> int64_t cur_sector, int nr_sectors);
>> void bdrv_dirty_iter_init(BlockDriverState *bs,
>> BdrvDirtyBitmap *bitmap, struct
>> HBitmapIter *hbi);
>> +void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
>> int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap
>> *bitmap);
>> void bdrv_enable_copy_on_read(BlockDriverState *bs);
>> diff --git a/include/block/block_int.h b/include/block/block_int.h
>> index 7ad1950..2233790 100644
>> --- a/include/block/block_int.h
>> +++ b/include/block/block_int.h
>> @@ -588,6 +588,7 @@ void mirror_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> * @target: Block device to write to.
>> * @speed: The maximum speed, in bytes per second, or 0 for unlimited.
>> * @sync_mode: What parts of the disk image should be copied to the
>> destination.
>> + * @sync_bitmap: The dirty bitmap if sync_mode is
>> MIRROR_SYNC_MODE_DIRTY_BITMAP.
>> * @on_source_error: The action to take upon error reading from the
>> source.
>> * @on_target_error: The action to take upon error writing to the
>> target.
>> * @cb: Completion function for the job.
>> @@ -598,6 +599,7 @@ void mirror_start(BlockDriverState *bs,
>> BlockDriverState *target,
>> */
>> void backup_start(BlockDriverState *bs, BlockDriverState *target,
>> int64_t speed, MirrorSyncMode sync_mode,
>> + BdrvDirtyBitmap *sync_bitmap,
>> BlockdevOnError on_source_error,
>> BlockdevOnError on_target_error,
>> BlockCompletionFunc *cb, void *opaque,
>> diff --git a/qapi/block-core.json b/qapi/block-core.json
>> index 940eff7..9c5a99c 100644
>> --- a/qapi/block-core.json
>> +++ b/qapi/block-core.json
>> @@ -508,10 +508,12 @@
>> #
>> # @none: only copy data written from now on
>> #
>> +# @dirty-bitmap: only copy data described by the dirty bitmap. Since:
>> 2.3
>> +#
>> # Since: 1.3
>> ##
>> { 'enum': 'MirrorSyncMode',
>> - 'data': ['top', 'full', 'none'] }
>> + 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
>> ##
>> # @BlockJobType:
>> @@ -686,14 +688,17 @@
>> # probe if @mode is 'existing', else the format of the source
>> #
>> # @sync: what parts of the disk image should be copied to the
>> destination
>> -# (all the disk, only the sectors allocated in the topmost
>> image, or
>> -# only new I/O).
>> +# (all the disk, only the sectors allocated in the topmost
>> image, from a
>> +# dirty bitmap, or only new I/O).
>> #
>> # @mode: #optional whether and how QEMU should create a new image,
>> default is
>> # 'absolute-paths'.
>> #
>> # @speed: #optional the maximum speed, in bytes per second
>> #
>> +# @bitmap: #optional the name of dirty bitmap if sync is "dirty-bitmap"
>> +# (Since 2.3)
>> +#
>> # @on-source-error: #optional the action to take on an error on the
>> source,
>> # default 'report'. 'stop' and 'enospc' can only
>> be used
>> # if the block device supports io-status (see
>> BlockInfo).
>> @@ -711,7 +716,7 @@
>> { 'type': 'DriveBackup',
>> 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>> 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
>> - '*speed': 'int',
>> + '*speed': 'int', '*bitmap': 'str',
>> '*on-source-error': 'BlockdevOnError',
>> '*on-target-error': 'BlockdevOnError' } }
>> diff --git a/qmp-commands.hx b/qmp-commands.hx
>> index ce7782f..5aa3845 100644
>> --- a/qmp-commands.hx
>> +++ b/qmp-commands.hx
>> @@ -1048,7 +1048,7 @@ EQMP
>> {
>> .name = "drive-backup",
>> .args_type =
>> "sync:s,device:B,target:s,speed:i?,mode:s?,format:s?,"
>> - "on-source-error:s?,on-target-error:s?",
>> + "bitmap:s?,on-source-error:s?,on-target-error:s?",
>> .mhandler.cmd_new = qmp_marshal_input_drive_backup,
>> },
>> @@ -1075,8 +1075,9 @@ Arguments:
>> (json-string, optional)
>> - "sync": what parts of the disk image should be copied to the
>> destination;
>> possibilities include "full" for all the disk, "top" for only the
>> sectors
>> - allocated in the topmost image, or "none" to only replicate new I/O
>> - (MirrorSyncMode).
>> + allocated in the topmost image, "dirty-bitmap" for only the dirty
>> sectors in
>> + the bitmap, or "none" to only replicate new I/O (MirrorSyncMode).
>> +- "bitmap": dirty bitmap name for sync==dirty-bitmap
>> - "mode": whether and how QEMU should create a new image
>> (NewImageMode, optional, default 'absolute-paths')
>> - "speed": the maximum speed, in bytes per second (json-int, optional)
>
> Looks good to me in general, now I need to find out what the successor
> bitmap is used for; but I guess I'll find that out by reviewing the rest
> of this series.
>
> Max
--
—js
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
2015-02-11 17:47 ` Max Reitz
@ 2015-02-13 17:33 ` Vladimir Sementsov-Ogievskiy
2015-02-13 18:35 ` John Snow
1 sibling, 1 reply; 55+ messages in thread
From: Vladimir Sementsov-Ogievskiy @ 2015-02-13 17:33 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, mreitz, stefanha
On 10.02.2015 04:35, John Snow wrote:
> ......
>
> @@ -278,28 +305,61 @@ static void coroutine_fn backup_run(void *opaque)
> qemu_coroutine_yield();
> job->common.busy = true;
> }
> + } else if (job->sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
> + /* Dirty Bitmap sync has a slightly different iteration method */
> + HBitmapIter hbi;
> + int64_t sector;
> + int64_t cluster;
> + int64_t last_cluster = -1;
> + bool polyrhythmic;
> +
> + bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
> + /* Does the granularity happen to match our backup cluster size? */
> + polyrhythmic = (bdrv_dirty_bitmap_granularity(job->sync_bitmap) !=
> + BACKUP_CLUSTER_SIZE);
let it be false, i.e. granularity == cluster
> +
> + /* Find the next dirty /sector/ and copy that /cluster/ */
> + while ((sector = hbitmap_iter_next(&hbi)) != -1) {
then, don't we skip here the very first cluster, if it is dirty?
> + if (yield_and_check(job)) {
> + goto leave;
> + }
> + cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
> +
> + /* Play some catchup with the progress meter */
> + if (cluster != last_cluster + 1) {
> + job->common.offset += ((cluster - last_cluster - 1) *
> + BACKUP_CLUSTER_SIZE);
> + }
> +
> + do {
> + ret = backup_do_cow(bs, cluster * BACKUP_SECTORS_PER_CLUSTER,
> + BACKUP_SECTORS_PER_CLUSTER, &error_is_read);
> + if ((ret < 0) &&
> + backup_error_action(job, error_is_read, -ret) ==
> + BLOCK_ERROR_ACTION_REPORT) {
> + goto leave;
> + }
> + } while (ret < 0);
> +
> + /* Advance (or rewind) our iterator if we need to. */
> + if (polyrhythmic) {
> + bdrv_set_dirty_iter(&hbi,
> + (cluster + 1) * BACKUP_SECTORS_PER_CLUSTER);
> + }
> +
> + last_cluster = cluster;
> + }
> +
> + /* Play some final catchup with the progress meter */
> + if (last_cluster + 1 < end) {
> + job->common.offset += ((end - last_cluster - 1) *
> + BACKUP_CLUSTER_SIZE);
> + }
> +
> } else {
>
--
Best regards,
Vladimir
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-02-13 17:33 ` Vladimir Sementsov-Ogievskiy
@ 2015-02-13 18:35 ` John Snow
0 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-13 18:35 UTC (permalink / raw)
To: qemu-devel, vladimir Sementsov-Ogievskiy
On 02/13/2015 12:33 PM, Vladimir Sementsov-Ogievskiy wrote:
> On 10.02.2015 04:35, John Snow wrote:
>> ......
>> @@ -278,28 +305,61 @@ static void coroutine_fn backup_run(void *opaque)
>> qemu_coroutine_yield();
>> job->common.busy = true;
>> }
>> + } else if (job->sync_mode == MIRROR_SYNC_MODE_DIRTY_BITMAP) {
>> + /* Dirty Bitmap sync has a slightly different iteration
>> method */
>> + HBitmapIter hbi;
>> + int64_t sector;
>> + int64_t cluster;
>> + int64_t last_cluster = -1;
>> + bool polyrhythmic;
>> +
>> + bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
>> + /* Does the granularity happen to match our backup cluster
>> size? */
>> + polyrhythmic =
>> (bdrv_dirty_bitmap_granularity(job->sync_bitmap) !=
>> + BACKUP_CLUSTER_SIZE);
> let it be false, i.e. granularity == cluster
>> +
>> + /* Find the next dirty /sector/ and copy that /cluster/ */
>> + while ((sector = hbitmap_iter_next(&hbi)) != -1) {
> then, don't we skip here the very first cluster, if it is dirty?
I don't think so. The HBitmapIterator is still in its initial state
here, and it can and will return 0 if the first sector is dirty.
The iotest submitted in v12 tests writes to the very first sector, and I
just verified it quickly that this function *does* return 0 for the
first go-around.
>> + if (yield_and_check(job)) {
>> + goto leave;
>> + }
>> + cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
>> +
>> + /* Play some catchup with the progress meter */
>> + if (cluster != last_cluster + 1) {
>> + job->common.offset += ((cluster - last_cluster - 1) *
>> + BACKUP_CLUSTER_SIZE);
>> + }
>> +
>> + do {
>> + ret = backup_do_cow(bs, cluster *
>> BACKUP_SECTORS_PER_CLUSTER,
>> + BACKUP_SECTORS_PER_CLUSTER,
>> &error_is_read);
>> + if ((ret < 0) &&
>> + backup_error_action(job, error_is_read, -ret) ==
>> + BLOCK_ERROR_ACTION_REPORT) {
>> + goto leave;
>> + }
>> + } while (ret < 0);
>> +
>> + /* Advance (or rewind) our iterator if we need to. */
>> + if (polyrhythmic) {
>> + bdrv_set_dirty_iter(&hbi,
>> + (cluster + 1) *
>> BACKUP_SECTORS_PER_CLUSTER);
>> + }
>> +
>> + last_cluster = cluster;
>> + }
>> +
>> + /* Play some final catchup with the progress meter */
>> + if (last_cluster + 1 < end) {
>> + job->common.offset += ((end - last_cluster - 1) *
>> + BACKUP_CLUSTER_SIZE);
>> + }
>> +
>> } else {
>
--
—js
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 08/17] qmp: add block-dirty-bitmap-clear
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (6 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 07/17] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 18:28 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 09/17] qapi: Add transaction support to block-dirty-bitmap operations John Snow
` (9 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
Add bdrv_clear_dirty_bitmap and a matching QMP command,
qmp_block_dirty_bitmap_clear that enables a user to reset
the bitmap attached to a drive.
This allows us to reset a bitmap in the event of a full
drive backup.
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 7 +++++++
blockdev.c | 31 +++++++++++++++++++++++++++++++
include/block/block.h | 1 +
qapi/block-core.json | 14 ++++++++++++++
qmp-commands.hx | 24 ++++++++++++++++++++++++
5 files changed, 77 insertions(+)
diff --git a/block.c b/block.c
index e93fceb..ad33d96 100644
--- a/block.c
+++ b/block.c
@@ -62,6 +62,7 @@
struct BdrvDirtyBitmap {
HBitmap *bitmap;
BdrvDirtyBitmap *successor;
+ int64_t size;
char *name;
bool disabled;
QLIST_ENTRY(BdrvDirtyBitmap) list;
@@ -5422,6 +5423,7 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
}
bitmap = g_new0(BdrvDirtyBitmap, 1);
bitmap->bitmap = hbitmap_alloc(bitmap_size, ffs(sector_granularity) - 1);
+ bitmap->size = bitmap_size;
bitmap->name = g_strdup(name);
bitmap->disabled = false;
QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
@@ -5624,6 +5626,11 @@ void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
}
+void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap)
+{
+ hbitmap_reset(bitmap->bitmap, 0, bitmap->size);
+}
+
static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
int nr_sectors)
{
diff --git a/blockdev.c b/blockdev.c
index 3f41e82..83d0608 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2116,6 +2116,37 @@ void qmp_block_dirty_bitmap_disable(const char *node, const char *name,
aio_context_release(aio_context);
}
+/**
+ * Completely clear a bitmap, for the purposes of synchronizing a bitmap
+ * immediately after a full backup operation.
+ */
+void qmp_block_dirty_bitmap_clear(const char *node, const char *name,
+ Error **errp)
+{
+ AioContext *aio_context;
+ BdrvDirtyBitmap *bitmap;
+ BlockDriverState *bs;
+
+ bitmap = block_dirty_bitmap_lookup(node, name, &bs, errp);
+ if (!bitmap) {
+ return;
+ }
+
+ aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(aio_context);
+
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ error_setg(errp,
+ "Bitmap '%s' is currently frozen and cannot be modified.\n",
+ name);
+ goto out;
+ }
+ bdrv_clear_dirty_bitmap(bitmap);
+
+ out:
+ aio_context_release(aio_context);
+}
+
int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
{
const char *id = qdict_get_str(qdict, "id");
diff --git a/include/block/block.h b/include/block/block.h
index 8589e77..8916ecd 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -467,6 +467,7 @@ void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
+void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap);
void bdrv_dirty_iter_init(BlockDriverState *bs,
BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 9c5a99c..702b76a 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1050,6 +1050,20 @@
'data': 'BlockDirtyBitmap' }
##
+# @block-dirty-bitmap-clear
+#
+# Clear (reset) a dirty bitmap on the device
+#
+# Returns: nothing on success
+# If @node is not a valid block device, DeviceNotFound
+# If @name is not found, GenericError with an explanation
+#
+# Since 2.3
+##
+{ 'command': 'block-dirty-bitmap-clear',
+ 'data': 'BlockDirtyBitmap' }
+
+##
# @block_set_io_throttle:
#
# Change I/O throttle limits for a block drive.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 5aa3845..e1d96cd 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1264,6 +1264,11 @@ EQMP
.args_type = "node:B,name:s",
.mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_disable,
},
+ {
+ .name = "block-dirty-bitmap-clear",
+ .args_type = "node:B,name:s",
+ .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_clear,
+ },
SQMP
@@ -1303,6 +1308,25 @@ Example:
"name": "bitmap0" } }
<- { "return": {} }
+block-dirty-bitmap-clear
+-------------------------
+Since 2.3
+
+Reset the dirty bitmap associated with a node so that an incremental backup
+from this point in time forward will only backup clusters modified after this
+clear operation.
+
+Arguments:
+
+- "node": device/node on which to remove dirty bitmap (json-string)
+- "name": name of the dirty bitmap to remove (json-string)
+
+Example:
+
+-> { "execute": "block-dirty-bitmap-clear", "arguments": { "node": "drive0",
+ "name": "bitmap0" } }
+<- { "return": {} }
+
EQMP
{
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 08/17] qmp: add block-dirty-bitmap-clear
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 08/17] qmp: add block-dirty-bitmap-clear John Snow
@ 2015-02-11 18:28 ` Max Reitz
2015-02-11 18:36 ` John Snow
0 siblings, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-11 18:28 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> Add bdrv_clear_dirty_bitmap and a matching QMP command,
> qmp_block_dirty_bitmap_clear that enables a user to reset
> the bitmap attached to a drive.
>
> This allows us to reset a bitmap in the event of a full
> drive backup.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 7 +++++++
> blockdev.c | 31 +++++++++++++++++++++++++++++++
> include/block/block.h | 1 +
> qapi/block-core.json | 14 ++++++++++++++
> qmp-commands.hx | 24 ++++++++++++++++++++++++
> 5 files changed, 77 insertions(+)
>
> diff --git a/block.c b/block.c
> index e93fceb..ad33d96 100644
> --- a/block.c
> +++ b/block.c
> @@ -62,6 +62,7 @@
> struct BdrvDirtyBitmap {
> HBitmap *bitmap;
> BdrvDirtyBitmap *successor;
> + int64_t size;
> char *name;
> bool disabled;
> QLIST_ENTRY(BdrvDirtyBitmap) list;
> @@ -5422,6 +5423,7 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
> }
> bitmap = g_new0(BdrvDirtyBitmap, 1);
> bitmap->bitmap = hbitmap_alloc(bitmap_size, ffs(sector_granularity) - 1);
> + bitmap->size = bitmap_size;
> bitmap->name = g_strdup(name);
> bitmap->disabled = false;
> QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
> @@ -5624,6 +5626,11 @@ void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
> hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
> }
>
> +void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap)
> +{
> + hbitmap_reset(bitmap->bitmap, 0, bitmap->size);
> +}
Is it fine to use this function on frozen or disabled bitmaps? The QMP
interface at least suggests the former isn't, but I think the latter
shouldn't be either (which may be wrong, though).
> +
> static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
> int nr_sectors)
> {
> diff --git a/blockdev.c b/blockdev.c
> index 3f41e82..83d0608 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -2116,6 +2116,37 @@ void qmp_block_dirty_bitmap_disable(const char *node, const char *name,
> aio_context_release(aio_context);
> }
>
> +/**
> + * Completely clear a bitmap, for the purposes of synchronizing a bitmap
> + * immediately after a full backup operation.
> + */
> +void qmp_block_dirty_bitmap_clear(const char *node, const char *name,
> + Error **errp)
> +{
> + AioContext *aio_context;
> + BdrvDirtyBitmap *bitmap;
> + BlockDriverState *bs;
> +
> + bitmap = block_dirty_bitmap_lookup(node, name, &bs, errp);
> + if (!bitmap) {
> + return;
> + }
> +
> + aio_context = bdrv_get_aio_context(bs);
> + aio_context_acquire(aio_context);
> +
> + if (bdrv_dirty_bitmap_frozen(bitmap)) {
> + error_setg(errp,
> + "Bitmap '%s' is currently frozen and cannot be modified.\n",
> + name);
> + goto out;
> + }
> + bdrv_clear_dirty_bitmap(bitmap);
> +
> + out:
> + aio_context_release(aio_context);
> +}
> +
> int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
> {
> const char *id = qdict_get_str(qdict, "id");
> diff --git a/include/block/block.h b/include/block/block.h
> index 8589e77..8916ecd 100644
> --- a/include/block/block.h
> +++ b/include/block/block.h
> @@ -467,6 +467,7 @@ void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
> int64_t cur_sector, int nr_sectors);
> void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
> int64_t cur_sector, int nr_sectors);
> +void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap);
> void bdrv_dirty_iter_init(BlockDriverState *bs,
> BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
> void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 9c5a99c..702b76a 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -1050,6 +1050,20 @@
> 'data': 'BlockDirtyBitmap' }
>
> ##
> +# @block-dirty-bitmap-clear
> +#
> +# Clear (reset) a dirty bitmap on the device
> +#
> +# Returns: nothing on success
> +# If @node is not a valid block device, DeviceNotFound
> +# If @name is not found, GenericError with an explanation
> +#
> +# Since 2.3
> +##
> +{ 'command': 'block-dirty-bitmap-clear',
> + 'data': 'BlockDirtyBitmap' }
> +
> +##
> # @block_set_io_throttle:
> #
> # Change I/O throttle limits for a block drive.
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index 5aa3845..e1d96cd 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -1264,6 +1264,11 @@ EQMP
> .args_type = "node:B,name:s",
> .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_disable,
> },
> + {
> + .name = "block-dirty-bitmap-clear",
> + .args_type = "node:B,name:s",
> + .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_clear,
> + },
>
> SQMP
>
> @@ -1303,6 +1308,25 @@ Example:
> "name": "bitmap0" } }
> <- { "return": {} }
>
> +block-dirty-bitmap-clear
> +-------------------------
One dash too many (critical!).
Max
> +Since 2.3
> +
> +Reset the dirty bitmap associated with a node so that an incremental backup
> +from this point in time forward will only backup clusters modified after this
> +clear operation.
> +
> +Arguments:
> +
> +- "node": device/node on which to remove dirty bitmap (json-string)
> +- "name": name of the dirty bitmap to remove (json-string)
> +
> +Example:
> +
> +-> { "execute": "block-dirty-bitmap-clear", "arguments": { "node": "drive0",
> + "name": "bitmap0" } }
> +<- { "return": {} }
> +
> EQMP
>
> {
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 08/17] qmp: add block-dirty-bitmap-clear
2015-02-11 18:28 ` Max Reitz
@ 2015-02-11 18:36 ` John Snow
0 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-11 18:36 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, vsementsov, famz, armbru, stefanha
On 02/11/2015 01:28 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> Add bdrv_clear_dirty_bitmap and a matching QMP command,
>> qmp_block_dirty_bitmap_clear that enables a user to reset
>> the bitmap attached to a drive.
>>
>> This allows us to reset a bitmap in the event of a full
>> drive backup.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 7 +++++++
>> blockdev.c | 31 +++++++++++++++++++++++++++++++
>> include/block/block.h | 1 +
>> qapi/block-core.json | 14 ++++++++++++++
>> qmp-commands.hx | 24 ++++++++++++++++++++++++
>> 5 files changed, 77 insertions(+)
>>
>> diff --git a/block.c b/block.c
>> index e93fceb..ad33d96 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -62,6 +62,7 @@
>> struct BdrvDirtyBitmap {
>> HBitmap *bitmap;
>> BdrvDirtyBitmap *successor;
>> + int64_t size;
>> char *name;
>> bool disabled;
>> QLIST_ENTRY(BdrvDirtyBitmap) list;
>> @@ -5422,6 +5423,7 @@ BdrvDirtyBitmap
>> *bdrv_create_dirty_bitmap(BlockDriverState *bs,
>> }
>> bitmap = g_new0(BdrvDirtyBitmap, 1);
>> bitmap->bitmap = hbitmap_alloc(bitmap_size,
>> ffs(sector_granularity) - 1);
>> + bitmap->size = bitmap_size;
>> bitmap->name = g_strdup(name);
>> bitmap->disabled = false;
>> QLIST_INSERT_HEAD(&bs->dirty_bitmaps, bitmap, list);
>> @@ -5624,6 +5626,11 @@ void bdrv_reset_dirty_bitmap(BlockDriverState
>> *bs, BdrvDirtyBitmap *bitmap,
>> hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
>> }
>> +void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap)
>> +{
>> + hbitmap_reset(bitmap->bitmap, 0, bitmap->size);
>> +}
>
> Is it fine to use this function on frozen or disabled bitmaps? The QMP
> interface at least suggests the former isn't, but I think the latter
> shouldn't be either (which may be wrong, though).
>
Bug. This should be prohibited for frozen or disabled bitmaps, yes.
>> +
>> static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
>> int nr_sectors)
>> {
>> diff --git a/blockdev.c b/blockdev.c
>> index 3f41e82..83d0608 100644
>> --- a/blockdev.c
>> +++ b/blockdev.c
>> @@ -2116,6 +2116,37 @@ void qmp_block_dirty_bitmap_disable(const char
>> *node, const char *name,
>> aio_context_release(aio_context);
>> }
>> +/**
>> + * Completely clear a bitmap, for the purposes of synchronizing a bitmap
>> + * immediately after a full backup operation.
>> + */
>> +void qmp_block_dirty_bitmap_clear(const char *node, const char *name,
>> + Error **errp)
>> +{
>> + AioContext *aio_context;
>> + BdrvDirtyBitmap *bitmap;
>> + BlockDriverState *bs;
>> +
>> + bitmap = block_dirty_bitmap_lookup(node, name, &bs, errp);
>> + if (!bitmap) {
>> + return;
>> + }
>> +
>> + aio_context = bdrv_get_aio_context(bs);
>> + aio_context_acquire(aio_context);
>> +
>> + if (bdrv_dirty_bitmap_frozen(bitmap)) {
>> + error_setg(errp,
>> + "Bitmap '%s' is currently frozen and cannot be
>> modified.\n",
>> + name);
>> + goto out;
>> + }
>> + bdrv_clear_dirty_bitmap(bitmap);
>> +
>> + out:
>> + aio_context_release(aio_context);
>> +}
>> +
>> int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data)
>> {
>> const char *id = qdict_get_str(qdict, "id");
>> diff --git a/include/block/block.h b/include/block/block.h
>> index 8589e77..8916ecd 100644
>> --- a/include/block/block.h
>> +++ b/include/block/block.h
>> @@ -467,6 +467,7 @@ void bdrv_set_dirty_bitmap(BlockDriverState *bs,
>> BdrvDirtyBitmap *bitmap,
>> int64_t cur_sector, int nr_sectors);
>> void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap
>> *bitmap,
>> int64_t cur_sector, int nr_sectors);
>> +void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap);
>> void bdrv_dirty_iter_init(BlockDriverState *bs,
>> BdrvDirtyBitmap *bitmap, struct
>> HBitmapIter *hbi);
>> void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
>> diff --git a/qapi/block-core.json b/qapi/block-core.json
>> index 9c5a99c..702b76a 100644
>> --- a/qapi/block-core.json
>> +++ b/qapi/block-core.json
>> @@ -1050,6 +1050,20 @@
>> 'data': 'BlockDirtyBitmap' }
>> ##
>> +# @block-dirty-bitmap-clear
>> +#
>> +# Clear (reset) a dirty bitmap on the device
>> +#
>> +# Returns: nothing on success
>> +# If @node is not a valid block device, DeviceNotFound
>> +# If @name is not found, GenericError with an explanation
>> +#
>> +# Since 2.3
>> +##
>> +{ 'command': 'block-dirty-bitmap-clear',
>> + 'data': 'BlockDirtyBitmap' }
>> +
>> +##
>> # @block_set_io_throttle:
>> #
>> # Change I/O throttle limits for a block drive.
>> diff --git a/qmp-commands.hx b/qmp-commands.hx
>> index 5aa3845..e1d96cd 100644
>> --- a/qmp-commands.hx
>> +++ b/qmp-commands.hx
>> @@ -1264,6 +1264,11 @@ EQMP
>> .args_type = "node:B,name:s",
>> .mhandler.cmd_new =
>> qmp_marshal_input_block_dirty_bitmap_disable,
>> },
>> + {
>> + .name = "block-dirty-bitmap-clear",
>> + .args_type = "node:B,name:s",
>> + .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_clear,
>> + },
>> SQMP
>> @@ -1303,6 +1308,25 @@ Example:
>> "name":
>> "bitmap0" } }
>> <- { "return": {} }
>> +block-dirty-bitmap-clear
>> +-------------------------
>
> One dash too many (critical!).
>
> Max
>
Yes.
>> +Since 2.3
>> +
>> +Reset the dirty bitmap associated with a node so that an incremental
>> backup
>> +from this point in time forward will only backup clusters modified
>> after this
>> +clear operation.
>> +
>> +Arguments:
>> +
>> +- "node": device/node on which to remove dirty bitmap (json-string)
>> +- "name": name of the dirty bitmap to remove (json-string)
>> +
>> +Example:
>> +
>> +-> { "execute": "block-dirty-bitmap-clear", "arguments": { "node":
>> "drive0",
>> + "name":
>> "bitmap0" } }
>> +<- { "return": {} }
>> +
>> EQMP
>> {
>
>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 09/17] qapi: Add transaction support to block-dirty-bitmap operations
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (7 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 08/17] qmp: add block-dirty-bitmap-clear John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 19:07 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 10/17] qmp: Add dirty bitmap status fields in query-block John Snow
` (8 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
This adds four qmp commands to transactions.
Users can stop a dirty bitmap, start backup of it, and start another
dirty bitmap atomically, so that the dirty bitmap is tracked
incrementally and we don't miss any write.
For starting a new incremental backup chain, users can also chain
together a bitmap clear and a full block backup.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
blockdev.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
qapi-schema.json | 6 ++-
2 files changed, 160 insertions(+), 1 deletion(-)
diff --git a/blockdev.c b/blockdev.c
index 83d0608..ed96e72 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1675,6 +1675,138 @@ static void blockdev_backup_clean(BlkTransactionState *common)
}
}
+static void block_dirty_bitmap_add_prepare(BlkTransactionState *common,
+ Error **errp)
+{
+ BlockDirtyBitmapAdd *action;
+
+ action = common->action->block_dirty_bitmap_add;
+ qmp_block_dirty_bitmap_add(action->node, action->name,
+ action->has_granularity, action->granularity,
+ errp);
+}
+
+static void block_dirty_bitmap_add_abort(BlkTransactionState *common)
+{
+ BlockDirtyBitmapAdd *action;
+
+ action = common->action->block_dirty_bitmap_add;
+ /* Should not fail meaningfully: IF the bitmap was added via .prepare(),
+ * then the node reference and bitmap name must have been valid.
+ * THUS: any failure here could only indicate the lack of a bitmap at all.
+ */
+ qmp_block_dirty_bitmap_remove(action->node, action->name, NULL);
+}
+
+typedef struct BlockDirtyBitmapState {
+ BlkTransactionState common;
+ BdrvDirtyBitmap *bitmap;
+ BlockDriverState *bs;
+ AioContext *aio_context;
+} BlockDirtyBitmapState;
+
+/**
+ * Enable and Disable re-use the same preparation.
+ */
+static void block_dirty_bitmap_toggle_prepare(BlkTransactionState *common,
+ Error **errp)
+{
+ BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+ common, common);
+ BlockDirtyBitmap *action;
+ BlockDriverState *bs;
+
+ /* We may be used by either enable or disable;
+ * We use the "enable" member of the union here,
+ * but "disable" should be functionally equivalent: */
+ action = common->action->block_dirty_bitmap_enable;
+ assert(action == common->action->block_dirty_bitmap_disable);
+
+ state->bitmap = block_dirty_bitmap_lookup(action->node,
+ action->name,
+ &bs,
+ errp);
+ if (!state->bitmap) {
+ return;
+ }
+
+ if (bdrv_dirty_bitmap_frozen(state->bitmap)) {
+ error_setg(errp, "Cannot modify a frozen bitmap.\n");
+ return;
+ }
+
+ /* AioContext is released in .clean() */
+ state->aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(state->aio_context);
+}
+
+static void block_dirty_bitmap_enable_commit(BlkTransactionState *common)
+{
+ BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+ common, common);
+ bdrv_enable_dirty_bitmap(state->bitmap);
+}
+
+static void block_dirty_bitmap_disable_commit(BlkTransactionState *common)
+{
+ BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+ common, common);
+ bdrv_disable_dirty_bitmap(state->bitmap);
+}
+
+static void block_dirty_bitmap_toggle_clean(BlkTransactionState *common)
+{
+ BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+ common, common);
+
+ if (state->aio_context) {
+ aio_context_release(state->aio_context);
+ }
+}
+
+static void block_dirty_bitmap_clear_prepare(BlkTransactionState *common,
+ Error **errp)
+{
+ BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+ common, common);
+ BlockDirtyBitmap *action;
+
+ action = common->action->block_dirty_bitmap_clear;
+ state->bitmap = block_dirty_bitmap_lookup(action->node,
+ action->name,
+ &state->bs,
+ errp);
+ if (!state->bitmap) {
+ return;
+ }
+
+ if (bdrv_dirty_bitmap_frozen(state->bitmap)) {
+ error_setg(errp, "Cannot modify a frozen bitmap.\n");
+ return;
+ }
+
+ /* AioContext is released in .clean() */
+ state->aio_context = bdrv_get_aio_context(state->bs);
+ aio_context_acquire(state->aio_context);
+}
+
+static void block_dirty_bitmap_clear_commit(BlkTransactionState *common)
+{
+ BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+ common, common);
+ bdrv_clear_dirty_bitmap(state->bitmap);
+}
+
+static void block_dirty_bitmap_clear_clean(BlkTransactionState *common)
+{
+ BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
+ common, common);
+
+ if (state->aio_context) {
+ aio_context_release(state->aio_context);
+ }
+}
+
static void abort_prepare(BlkTransactionState *common, Error **errp)
{
error_setg(errp, "Transaction aborted using Abort action");
@@ -1715,6 +1847,29 @@ static const BdrvActionOps actions[] = {
.abort = internal_snapshot_abort,
.clean = internal_snapshot_clean,
},
+ [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD] = {
+ .instance_size = sizeof(BlkTransactionState),
+ .prepare = block_dirty_bitmap_add_prepare,
+ .abort = block_dirty_bitmap_add_abort,
+ },
+ [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ENABLE] = {
+ .instance_size = sizeof(BlockDirtyBitmapState),
+ .prepare = block_dirty_bitmap_toggle_prepare,
+ .commit = block_dirty_bitmap_enable_commit,
+ .clean = block_dirty_bitmap_toggle_clean,
+ },
+ [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_DISABLE] = {
+ .instance_size = sizeof(BlockDirtyBitmapState),
+ .prepare = block_dirty_bitmap_toggle_prepare,
+ .commit = block_dirty_bitmap_disable_commit,
+ .clean = block_dirty_bitmap_toggle_clean,
+ },
+ [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR] = {
+ .instance_size = sizeof(BlockDirtyBitmapState),
+ .prepare = block_dirty_bitmap_clear_prepare,
+ .commit = block_dirty_bitmap_clear_commit,
+ .clean = block_dirty_bitmap_clear_clean,
+ }
};
/*
diff --git a/qapi-schema.json b/qapi-schema.json
index e16f8eb..cf4aa12 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1339,7 +1339,11 @@
'drive-backup': 'DriveBackup',
'blockdev-backup': 'BlockdevBackup',
'abort': 'Abort',
- 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal'
+ 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',
+ 'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd',
+ 'block-dirty-bitmap-enable': 'BlockDirtyBitmap',
+ 'block-dirty-bitmap-disable': 'BlockDirtyBitmap',
+ 'block-dirty-bitmap-clear': 'BlockDirtyBitmap'
} }
##
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 09/17] qapi: Add transaction support to block-dirty-bitmap operations
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 09/17] qapi: Add transaction support to block-dirty-bitmap operations John Snow
@ 2015-02-11 19:07 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 19:07 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> This adds four qmp commands to transactions.
>
> Users can stop a dirty bitmap, start backup of it, and start another
> dirty bitmap atomically, so that the dirty bitmap is tracked
> incrementally and we don't miss any write.
>
> For starting a new incremental backup chain, users can also chain
> together a bitmap clear and a full block backup.
>
> Signed-off-by: Fam Zheng <famz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> blockdev.c | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> qapi-schema.json | 6 ++-
> 2 files changed, 160 insertions(+), 1 deletion(-)
>
> diff --git a/blockdev.c b/blockdev.c
> index 83d0608..ed96e72 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1675,6 +1675,138 @@ static void blockdev_backup_clean(BlkTransactionState *common)
> }
> }
>
> +static void block_dirty_bitmap_add_prepare(BlkTransactionState *common,
> + Error **errp)
> +{
> + BlockDirtyBitmapAdd *action;
> +
> + action = common->action->block_dirty_bitmap_add;
> + qmp_block_dirty_bitmap_add(action->node, action->name,
> + action->has_granularity, action->granularity,
> + errp);
> +}
> +
> +static void block_dirty_bitmap_add_abort(BlkTransactionState *common)
> +{
> + BlockDirtyBitmapAdd *action;
> +
> + action = common->action->block_dirty_bitmap_add;
> + /* Should not fail meaningfully: IF the bitmap was added via .prepare(),
> + * then the node reference and bitmap name must have been valid.
> + * THUS: any failure here could only indicate the lack of a bitmap at all.
> + */
> + qmp_block_dirty_bitmap_remove(action->node, action->name, NULL);
What if block_dirty_bitmap_add_prepare() failed because a bitmap with
that name already exists? Wouldn't this silently delete that existing
bitmap?
I think you should store whether qmp_block_dirty_bitmap_add() was
successful and only call qmp_block_dirty_bitmap_remove() if it was, and
since you say it cannot fail (which I agree on), with &error_abort as
the last argument.
> +}
> +
> +typedef struct BlockDirtyBitmapState {
> + BlkTransactionState common;
> + BdrvDirtyBitmap *bitmap;
> + BlockDriverState *bs;
> + AioContext *aio_context;
> +} BlockDirtyBitmapState;
> +
> +/**
> + * Enable and Disable re-use the same preparation.
> + */
> +static void block_dirty_bitmap_toggle_prepare(BlkTransactionState *common,
> + Error **errp)
> +{
> + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> + common, common);
> + BlockDirtyBitmap *action;
> + BlockDriverState *bs;
> +
> + /* We may be used by either enable or disable;
> + * We use the "enable" member of the union here,
> + * but "disable" should be functionally equivalent: */
> + action = common->action->block_dirty_bitmap_enable;
> + assert(action == common->action->block_dirty_bitmap_disable);
> +
> + state->bitmap = block_dirty_bitmap_lookup(action->node,
> + action->name,
> + &bs,
> + errp);
> + if (!state->bitmap) {
> + return;
> + }
> +
> + if (bdrv_dirty_bitmap_frozen(state->bitmap)) {
> + error_setg(errp, "Cannot modify a frozen bitmap.\n");
I'm sorry.\n
> + return;
> + }
> +
> + /* AioContext is released in .clean() */
> + state->aio_context = bdrv_get_aio_context(bs);
> + aio_context_acquire(state->aio_context);
> +}
> +
> +static void block_dirty_bitmap_enable_commit(BlkTransactionState *common)
> +{
> + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> + common, common);
> + bdrv_enable_dirty_bitmap(state->bitmap);
> +}
> +
> +static void block_dirty_bitmap_disable_commit(BlkTransactionState *common)
> +{
> + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> + common, common);
> + bdrv_disable_dirty_bitmap(state->bitmap);
> +}
> +
> +static void block_dirty_bitmap_toggle_clean(BlkTransactionState *common)
> +{
> + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> + common, common);
> +
> + if (state->aio_context) {
> + aio_context_release(state->aio_context);
> + }
> +}
> +
> +static void block_dirty_bitmap_clear_prepare(BlkTransactionState *common,
> + Error **errp)
> +{
> + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> + common, common);
> + BlockDirtyBitmap *action;
> +
> + action = common->action->block_dirty_bitmap_clear;
> + state->bitmap = block_dirty_bitmap_lookup(action->node,
> + action->name,
> + &state->bs,
> + errp);
> + if (!state->bitmap) {
> + return;
> + }
> +
> + if (bdrv_dirty_bitmap_frozen(state->bitmap)) {
> + error_setg(errp, "Cannot modify a frozen bitmap.\n");
> + return;
> + }
Following your reply regarding patch 8, this needs a check whether the
bitmap is disabled, too.\n
> +
> + /* AioContext is released in .clean() */
> + state->aio_context = bdrv_get_aio_context(state->bs);
> + aio_context_acquire(state->aio_context);
> +}
> +
> +static void block_dirty_bitmap_clear_commit(BlkTransactionState *common)
> +{
> + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> + common, common);
> + bdrv_clear_dirty_bitmap(state->bitmap);
> +}
> +
> +static void block_dirty_bitmap_clear_clean(BlkTransactionState *common)
> +{
> + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
> + common, common);
> +
> + if (state->aio_context) {
> + aio_context_release(state->aio_context);
> + }
> +}
> +
> static void abort_prepare(BlkTransactionState *common, Error **errp)
> {
> error_setg(errp, "Transaction aborted using Abort action");
> @@ -1715,6 +1847,29 @@ static const BdrvActionOps actions[] = {
> .abort = internal_snapshot_abort,
> .clean = internal_snapshot_clean,
> },
> + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD] = {
> + .instance_size = sizeof(BlkTransactionState),
> + .prepare = block_dirty_bitmap_add_prepare,
> + .abort = block_dirty_bitmap_add_abort,
> + },
> + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ENABLE] = {
> + .instance_size = sizeof(BlockDirtyBitmapState),
> + .prepare = block_dirty_bitmap_toggle_prepare,
> + .commit = block_dirty_bitmap_enable_commit,
> + .clean = block_dirty_bitmap_toggle_clean,
> + },
> + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_DISABLE] = {
> + .instance_size = sizeof(BlockDirtyBitmapState),
> + .prepare = block_dirty_bitmap_toggle_prepare,
> + .commit = block_dirty_bitmap_disable_commit,
> + .clean = block_dirty_bitmap_toggle_clean,
> + },
> + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR] = {
> + .instance_size = sizeof(BlockDirtyBitmapState),
> + .prepare = block_dirty_bitmap_clear_prepare,
> + .commit = block_dirty_bitmap_clear_commit,
> + .clean = block_dirty_bitmap_clear_clean,
> + }
> };
>
> /*
> diff --git a/qapi-schema.json b/qapi-schema.json
> index e16f8eb..cf4aa12 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -1339,7 +1339,11 @@
> 'drive-backup': 'DriveBackup',
> 'blockdev-backup': 'BlockdevBackup',
> 'abort': 'Abort',
> - 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal'
> + 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal',
> + 'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd',
> + 'block-dirty-bitmap-enable': 'BlockDirtyBitmap',
> + 'block-dirty-bitmap-disable': 'BlockDirtyBitmap',
> + 'block-dirty-bitmap-clear': 'BlockDirtyBitmap'
Maybe add version information to the comment above this union?
Max
> } }
>
> ##
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 10/17] qmp: Add dirty bitmap status fields in query-block
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (8 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 09/17] qapi: Add transaction support to block-dirty-bitmap operations John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 19:10 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 11/17] block: add BdrvDirtyBitmap documentation John Snow
` (7 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
Adds the "disabled" and "frozen" status booleans.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 2 ++
qapi/block-core.json | 7 ++++++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/block.c b/block.c
index ad33d96..3c0989c 100644
--- a/block.c
+++ b/block.c
@@ -5564,6 +5564,8 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
info->granularity = bdrv_dirty_bitmap_granularity(bm);
info->has_name = !!bm->name;
info->name = g_strdup(bm->name);
+ info->disabled = bm->disabled;
+ info->frozen = bdrv_dirty_bitmap_frozen(bm);
entry->value = info;
*plist = entry;
plist = &entry->next;
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 702b76a..c5d5563 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -332,10 +332,15 @@
#
# @granularity: granularity of the dirty bitmap in bytes (since 1.4)
#
+# @disabled: whether the dirty bitmap is disabled (Since 2.3)
+#
+# @frozen: whether the dirty bitmap is frozen (Since 2.3)
+#
# Since: 1.3
##
{ 'type': 'BlockDirtyInfo',
- 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int'} }
+ 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int',
+ 'disabled': 'bool', 'frozen': 'bool'} }
##
# @BlockInfo:
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 10/17] qmp: Add dirty bitmap status fields in query-block
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 10/17] qmp: Add dirty bitmap status fields in query-block John Snow
@ 2015-02-11 19:10 ` Max Reitz
2015-02-11 19:19 ` John Snow
0 siblings, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-11 19:10 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> Adds the "disabled" and "frozen" status booleans.
>
> Signed-off-by: Fam Zheng <famz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 2 ++
> qapi/block-core.json | 7 ++++++-
> 2 files changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/block.c b/block.c
> index ad33d96..3c0989c 100644
> --- a/block.c
> +++ b/block.c
> @@ -5564,6 +5564,8 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
> info->granularity = bdrv_dirty_bitmap_granularity(bm);
> info->has_name = !!bm->name;
> info->name = g_strdup(bm->name);
> + info->disabled = bm->disabled;
Hm, this is different than !bdrv_dirty_bitmap_enabled(), but I can
understand the reasoning if the difference is intended.
Therefore, as long as it's intended:
Reviewed-by: Max Reitz <mreitz@redhat.com>
> + info->frozen = bdrv_dirty_bitmap_frozen(bm);
> entry->value = info;
> *plist = entry;
> plist = &entry->next;
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 702b76a..c5d5563 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -332,10 +332,15 @@
> #
> # @granularity: granularity of the dirty bitmap in bytes (since 1.4)
> #
> +# @disabled: whether the dirty bitmap is disabled (Since 2.3)
> +#
> +# @frozen: whether the dirty bitmap is frozen (Since 2.3)
> +#
> # Since: 1.3
> ##
> { 'type': 'BlockDirtyInfo',
> - 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int'} }
> + 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int',
> + 'disabled': 'bool', 'frozen': 'bool'} }
>
> ##
> # @BlockInfo:
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 10/17] qmp: Add dirty bitmap status fields in query-block
2015-02-11 19:10 ` Max Reitz
@ 2015-02-11 19:19 ` John Snow
0 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-11 19:19 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/11/2015 02:10 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> Adds the "disabled" and "frozen" status booleans.
>>
>> Signed-off-by: Fam Zheng <famz@redhat.com>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 2 ++
>> qapi/block-core.json | 7 ++++++-
>> 2 files changed, 8 insertions(+), 1 deletion(-)
>>
>> diff --git a/block.c b/block.c
>> index ad33d96..3c0989c 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -5564,6 +5564,8 @@ BlockDirtyInfoList
>> *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
>> info->granularity = bdrv_dirty_bitmap_granularity(bm);
>> info->has_name = !!bm->name;
>> info->name = g_strdup(bm->name);
>> + info->disabled = bm->disabled;
>
> Hm, this is different than !bdrv_dirty_bitmap_enabled(), but I can
> understand the reasoning if the difference is intended.
>
> Therefore, as long as it's intended:
>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
>
Yes, since I am exposing the frozen condition as well, so that error
messages aren't opaque: "Something is busy, sorry, go away." At least
with this you can check what conditions are preventing operations from
completing.
info->disabled was meant to expose only the user-controlled "on/off" states.
>> + info->frozen = bdrv_dirty_bitmap_frozen(bm);
>> entry->value = info;
>> *plist = entry;
>> plist = &entry->next;
>> diff --git a/qapi/block-core.json b/qapi/block-core.json
>> index 702b76a..c5d5563 100644
>> --- a/qapi/block-core.json
>> +++ b/qapi/block-core.json
>> @@ -332,10 +332,15 @@
>> #
>> # @granularity: granularity of the dirty bitmap in bytes (since 1.4)
>> #
>> +# @disabled: whether the dirty bitmap is disabled (Since 2.3)
>> +#
>> +# @frozen: whether the dirty bitmap is frozen (Since 2.3)
>> +#
>> # Since: 1.3
>> ##
>> { 'type': 'BlockDirtyInfo',
>> - 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int'} }
>> + 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int',
>> + 'disabled': 'bool', 'frozen': 'bool'} }
>> ##
>> # @BlockInfo:
>
--
—js
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 11/17] block: add BdrvDirtyBitmap documentation
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (9 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 10/17] qmp: Add dirty bitmap status fields in query-block John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 19:14 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 12/17] block: Ensure consistent bitmap function prototypes John Snow
` (6 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/block.c b/block.c
index 3c0989c..e065694 100644
--- a/block.c
+++ b/block.c
@@ -60,11 +60,11 @@
* or enabled. A frozen bitmap can only abdicate() or reclaim().
*/
struct BdrvDirtyBitmap {
- HBitmap *bitmap;
- BdrvDirtyBitmap *successor;
- int64_t size;
- char *name;
- bool disabled;
+ HBitmap *bitmap; /* Dirty sector bitmap implementation */
+ BdrvDirtyBitmap *successor; /* Anonymous child; implies frozen status */
+ char *name; /* Optional non-empty unique ID */
+ int64_t size; /* Size of the bitmap (Number of sectors) */
+ bool disabled; /* Bitmap is read-only */
QLIST_ENTRY(BdrvDirtyBitmap) list;
};
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 11/17] block: add BdrvDirtyBitmap documentation
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 11/17] block: add BdrvDirtyBitmap documentation John Snow
@ 2015-02-11 19:14 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 19:14 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 10 +++++-----
> 1 file changed, 5 insertions(+), 5 deletions(-)
>
> diff --git a/block.c b/block.c
> index 3c0989c..e065694 100644
> --- a/block.c
> +++ b/block.c
> @@ -60,11 +60,11 @@
> * or enabled. A frozen bitmap can only abdicate() or reclaim().
> */
> struct BdrvDirtyBitmap {
> - HBitmap *bitmap;
> - BdrvDirtyBitmap *successor;
> - int64_t size;
> - char *name;
> - bool disabled;
> + HBitmap *bitmap; /* Dirty sector bitmap implementation */
> + BdrvDirtyBitmap *successor; /* Anonymous child; implies frozen status */
> + char *name; /* Optional non-empty unique ID */
> + int64_t size; /* Size of the bitmap (Number of sectors) */
> + bool disabled; /* Bitmap is read-only */
> QLIST_ENTRY(BdrvDirtyBitmap) list;
> };
>
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 12/17] block: Ensure consistent bitmap function prototypes
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (10 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 11/17] block: add BdrvDirtyBitmap documentation John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 19:20 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 13/17] iotests: add invalid input incremental backup tests John Snow
` (5 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
We often don't need the BlockDriverState for functions
that operate on bitmaps. Remove it.
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 13 ++++++-------
block/backup.c | 2 +-
block/mirror.c | 23 ++++++++++-------------
blockdev.c | 2 +-
include/block/block.h | 11 +++++------
migration/block.c | 7 +++----
6 files changed, 26 insertions(+), 32 deletions(-)
diff --git a/block.c b/block.c
index e065694..162d4bb 100644
--- a/block.c
+++ b/block.c
@@ -5391,7 +5391,7 @@ BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs, const char *name)
return NULL;
}
-void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
+void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap)
{
assert(!bdrv_dirty_bitmap_frozen(bitmap));
g_free(bitmap->name);
@@ -5560,7 +5560,7 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
QLIST_FOREACH(bm, &bs->dirty_bitmaps, list) {
BlockDirtyInfo *info = g_new0(BlockDirtyInfo, 1);
BlockDirtyInfoList *entry = g_new0(BlockDirtyInfoList, 1);
- info->count = bdrv_get_dirty_count(bs, bm);
+ info->count = bdrv_get_dirty_count(bm);
info->granularity = bdrv_dirty_bitmap_granularity(bm);
info->has_name = !!bm->name;
info->name = g_strdup(bm->name);
@@ -5608,20 +5608,19 @@ uint64_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap)
return BDRV_SECTOR_SIZE << hbitmap_granularity(bitmap->bitmap);
}
-void bdrv_dirty_iter_init(BlockDriverState *bs,
- BdrvDirtyBitmap *bitmap, HBitmapIter *hbi)
+void bdrv_dirty_iter_init(BdrvDirtyBitmap *bitmap, HBitmapIter *hbi)
{
hbitmap_iter_init(hbi, bitmap->bitmap, 0);
}
-void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
+void bdrv_set_dirty_bitmap(BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors)
{
assert(bdrv_dirty_bitmap_enabled(bitmap));
hbitmap_set(bitmap->bitmap, cur_sector, nr_sectors);
}
-void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
+void bdrv_reset_dirty_bitmap(BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors)
{
assert(bdrv_dirty_bitmap_enabled(bitmap));
@@ -5666,7 +5665,7 @@ void bdrv_set_dirty_iter(HBitmapIter *hbi, int64_t offset)
hbitmap_iter_init(hbi, hbi->hb, offset);
}
-int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
+int64_t bdrv_get_dirty_count(BdrvDirtyBitmap *bitmap)
{
return hbitmap_count(bitmap->bitmap);
}
diff --git a/block/backup.c b/block/backup.c
index bf8c0e7..329d7f6 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -313,7 +313,7 @@ static void coroutine_fn backup_run(void *opaque)
int64_t last_cluster = -1;
bool polyrhythmic;
- bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
+ bdrv_dirty_iter_init(job->sync_bitmap, &hbi);
/* Does the granularity happen to match our backup cluster size? */
polyrhythmic = (bdrv_dirty_bitmap_granularity(job->sync_bitmap) !=
BACKUP_CLUSTER_SIZE);
diff --git a/block/mirror.c b/block/mirror.c
index 271dbf3..04d1710 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -125,10 +125,9 @@ static void mirror_write_complete(void *opaque, int ret)
MirrorOp *op = opaque;
MirrorBlockJob *s = op->s;
if (ret < 0) {
- BlockDriverState *source = s->common.bs;
BlockErrorAction action;
- bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
+ bdrv_set_dirty_bitmap(s->dirty_bitmap, op->sector_num,
op->nb_sectors);
action = mirror_error_action(s, false, -ret);
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
@@ -143,10 +142,9 @@ static void mirror_read_complete(void *opaque, int ret)
MirrorOp *op = opaque;
MirrorBlockJob *s = op->s;
if (ret < 0) {
- BlockDriverState *source = s->common.bs;
BlockErrorAction action;
- bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
+ bdrv_set_dirty_bitmap(s->dirty_bitmap, op->sector_num,
op->nb_sectors);
action = mirror_error_action(s, true, -ret);
if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
@@ -170,10 +168,10 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
s->sector_num = hbitmap_iter_next(&s->hbi);
if (s->sector_num < 0) {
- bdrv_dirty_iter_init(source, s->dirty_bitmap, &s->hbi);
+ bdrv_dirty_iter_init(s->dirty_bitmap, &s->hbi);
s->sector_num = hbitmap_iter_next(&s->hbi);
trace_mirror_restart_iter(s,
- bdrv_get_dirty_count(source, s->dirty_bitmap));
+ bdrv_get_dirty_count(s->dirty_bitmap));
assert(s->sector_num >= 0);
}
@@ -288,8 +286,7 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
next_sector += sectors_per_chunk;
}
- bdrv_reset_dirty_bitmap(source, s->dirty_bitmap, sector_num,
- nb_sectors);
+ bdrv_reset_dirty_bitmap(s->dirty_bitmap, sector_num, nb_sectors);
/* Copy the dirty cluster. */
s->in_flight++;
@@ -446,7 +443,7 @@ static void coroutine_fn mirror_run(void *opaque)
assert(n > 0);
if (ret == 1) {
- bdrv_set_dirty_bitmap(bs, s->dirty_bitmap, sector_num, n);
+ bdrv_set_dirty_bitmap(s->dirty_bitmap, sector_num, n);
sector_num = next;
} else {
sector_num += n;
@@ -454,7 +451,7 @@ static void coroutine_fn mirror_run(void *opaque)
}
}
- bdrv_dirty_iter_init(bs, s->dirty_bitmap, &s->hbi);
+ bdrv_dirty_iter_init(s->dirty_bitmap, &s->hbi);
last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
for (;;) {
uint64_t delay_ns = 0;
@@ -466,7 +463,7 @@ static void coroutine_fn mirror_run(void *opaque)
goto immediate_exit;
}
- cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
+ cnt = bdrv_get_dirty_count(s->dirty_bitmap);
/* s->common.offset contains the number of bytes already processed so
* far, cnt is the number of dirty sectors remaining and
* s->sectors_in_flight is the number of sectors currently being
@@ -516,7 +513,7 @@ static void coroutine_fn mirror_run(void *opaque)
should_complete = s->should_complete ||
block_job_is_cancelled(&s->common);
- cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
+ cnt = bdrv_get_dirty_count(s->dirty_bitmap);
}
}
@@ -531,7 +528,7 @@ static void coroutine_fn mirror_run(void *opaque)
*/
trace_mirror_before_drain(s, cnt);
bdrv_drain(bs);
- cnt = bdrv_get_dirty_count(bs, s->dirty_bitmap);
+ cnt = bdrv_get_dirty_count(s->dirty_bitmap);
}
ret = 0;
diff --git a/blockdev.c b/blockdev.c
index ed96e72..d8ef137 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2209,7 +2209,7 @@ void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
name);
goto out;
}
- bdrv_dirty_bitmap_make_anon(bs, bitmap);
+ bdrv_dirty_bitmap_make_anon(bitmap);
bdrv_release_dirty_bitmap(bs, bitmap);
out:
diff --git a/include/block/block.h b/include/block/block.h
index 8916ecd..4c69a61 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -453,7 +453,7 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
Error **errp);
BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
const char *name);
-void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
+void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
void bdrv_disable_dirty_bitmap(BdrvDirtyBitmap *bitmap);
void bdrv_enable_dirty_bitmap(BdrvDirtyBitmap *bitmap);
@@ -463,15 +463,14 @@ uint64_t bdrv_dirty_bitmap_granularity(BdrvDirtyBitmap *bitmap);
bool bdrv_dirty_bitmap_enabled(BdrvDirtyBitmap *bitmap);
bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap);
int bdrv_get_dirty(BlockDriverState *bs, BdrvDirtyBitmap *bitmap, int64_t sector);
-void bdrv_set_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
+void bdrv_set_dirty_bitmap(BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
-void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
+void bdrv_reset_dirty_bitmap(BdrvDirtyBitmap *bitmap,
int64_t cur_sector, int nr_sectors);
void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap);
-void bdrv_dirty_iter_init(BlockDriverState *bs,
- BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
+void bdrv_dirty_iter_init(BdrvDirtyBitmap *bitmap, struct HBitmapIter *hbi);
void bdrv_set_dirty_iter(struct HBitmapIter *hbi, int64_t offset);
-int64_t bdrv_get_dirty_count(BlockDriverState *bs, BdrvDirtyBitmap *bitmap);
+int64_t bdrv_get_dirty_count(BdrvDirtyBitmap *bitmap);
void bdrv_enable_copy_on_read(BlockDriverState *bs);
void bdrv_disable_copy_on_read(BlockDriverState *bs);
diff --git a/migration/block.c b/migration/block.c
index 5f9b3e5..1a9e7cd 100644
--- a/migration/block.c
+++ b/migration/block.c
@@ -303,7 +303,7 @@ static int mig_save_device_bulk(QEMUFile *f, BlkMigDevState *bmds)
blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov,
nr_sectors, blk_mig_read_cb, blk);
- bdrv_reset_dirty_bitmap(bs, bmds->dirty_bitmap, cur_sector, nr_sectors);
+ bdrv_reset_dirty_bitmap(bmds->dirty_bitmap, cur_sector, nr_sectors);
qemu_mutex_unlock_iothread();
bmds->cur_sector = cur_sector + nr_sectors;
@@ -496,8 +496,7 @@ static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds,
g_free(blk);
}
- bdrv_reset_dirty_bitmap(bmds->bs, bmds->dirty_bitmap, sector,
- nr_sectors);
+ bdrv_reset_dirty_bitmap(bmds->dirty_bitmap, sector, nr_sectors);
break;
}
sector += BDRV_SECTORS_PER_DIRTY_CHUNK;
@@ -583,7 +582,7 @@ static int64_t get_remaining_dirty(void)
int64_t dirty = 0;
QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) {
- dirty += bdrv_get_dirty_count(bmds->bs, bmds->dirty_bitmap);
+ dirty += bdrv_get_dirty_count(bmds->dirty_bitmap);
}
return dirty << BDRV_SECTOR_BITS;
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 12/17] block: Ensure consistent bitmap function prototypes
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 12/17] block: Ensure consistent bitmap function prototypes John Snow
@ 2015-02-11 19:20 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 19:20 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> We often don't need the BlockDriverState for functions
> that operate on bitmaps. Remove it.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 13 ++++++-------
> block/backup.c | 2 +-
> block/mirror.c | 23 ++++++++++-------------
> blockdev.c | 2 +-
> include/block/block.h | 11 +++++------
> migration/block.c | 7 +++----
> 6 files changed, 26 insertions(+), 32 deletions(-)
[snip]
> diff --git a/block/mirror.c b/block/mirror.c
> index 271dbf3..04d1710 100644
> --- a/block/mirror.c
> +++ b/block/mirror.c
> @@ -125,10 +125,9 @@ static void mirror_write_complete(void *opaque, int ret)
> MirrorOp *op = opaque;
> MirrorBlockJob *s = op->s;
> if (ret < 0) {
> - BlockDriverState *source = s->common.bs;
> BlockErrorAction action;
>
> - bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
> + bdrv_set_dirty_bitmap(s->dirty_bitmap, op->sector_num,
> op->nb_sectors);
> action = mirror_error_action(s, false, -ret);
> if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
> @@ -143,10 +142,9 @@ static void mirror_read_complete(void *opaque, int ret)
> MirrorOp *op = opaque;
> MirrorBlockJob *s = op->s;
> if (ret < 0) {
> - BlockDriverState *source = s->common.bs;
> BlockErrorAction action;
>
> - bdrv_set_dirty_bitmap(source, s->dirty_bitmap, op->sector_num,
> + bdrv_set_dirty_bitmap(s->dirty_bitmap, op->sector_num,
> op->nb_sectors);
> action = mirror_error_action(s, true, -ret);
> if (action == BLOCK_ERROR_ACTION_REPORT && s->ret >= 0) {
> @@ -170,10 +168,10 @@ static uint64_t coroutine_fn mirror_iteration(MirrorBlockJob *s)
>
> s->sector_num = hbitmap_iter_next(&s->hbi);
> if (s->sector_num < 0) {
> - bdrv_dirty_iter_init(source, s->dirty_bitmap, &s->hbi);
> + bdrv_dirty_iter_init(s->dirty_bitmap, &s->hbi);
> s->sector_num = hbitmap_iter_next(&s->hbi);
> trace_mirror_restart_iter(s,
> - bdrv_get_dirty_count(source, s->dirty_bitmap));
> + bdrv_get_dirty_count(s->dirty_bitmap));
You can probably join those two lines now (maybe there are more places
where that's possible, I haven't checked, but this looks awfully
suspicious; I see that you did join some lines, so I know that you're
not opposed to it in principle).
Whether you do that or not (here and in all the places possible):
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 13/17] iotests: add invalid input incremental backup tests
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (11 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 12/17] block: Ensure consistent bitmap function prototypes John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 20:45 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 14/17] iotests: add simple incremental backup case John Snow
` (4 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/112 | 89 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/112.out | 5 +++
tests/qemu-iotests/group | 1 +
3 files changed, 95 insertions(+)
create mode 100644 tests/qemu-iotests/112
create mode 100644 tests/qemu-iotests/112.out
diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
new file mode 100644
index 0000000..7985cd1
--- /dev/null
+++ b/tests/qemu-iotests/112
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+#
+# Tests for incremental drive-backup
+#
+# Copyright (C) 2015 John Snow for Red Hat, Inc.
+#
+# Based on 056.
+#
+# 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/>.
+#
+
+import os
+import iotests
+
+
+def io_write_patterns(img, patterns):
+ for pattern in patterns:
+ iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
+
+
+class TestIncrementalBackup(iotests.QMPTestCase):
+ def setUp(self):
+ self.bitmaps = list()
+ self.files = list()
+ self.vm = iotests.VM()
+ self.test_img = os.path.join(iotests.test_dir, 'base.img')
+ self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
+ self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
+ self.img_create(self.test_img, iotests.imgfmt)
+ self.vm.add_drive(self.test_img)
+ # Create a base image with a distinctive patterning
+ io_write_patterns(self.test_img, (('0x41', 0, 512),
+ ('0xd5', '1M', '32k'),
+ ('0xdc', '32M', '124k')))
+ self.vm.launch()
+
+
+ def img_create(self, img, fmt=iotests.imgfmt, size='64M',
+ parent=None, parentFormat=None):
+ plist = list()
+ if parent:
+ if parentFormat is None:
+ parentFormat = fmt
+ iotests.qemu_img('create', '-f', fmt, img, size,
+ '-b', parent, '-F', parentFormat)
+ else:
+ iotests.qemu_img('create', '-f', fmt, img, size)
+ self.files.append(img)
+
+ def test_sync_dirty_bitmap_missing(self):
+ self.assert_no_active_block_jobs()
+ self.files.append(self.foo_img)
+ result = self.vm.qmp('drive-backup', device='drive0',
+ sync='dirty-bitmap', format=iotests.imgfmt,
+ target=self.foo_img)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+
+ def test_sync_dirty_bitmap_not_found(self):
+ self.assert_no_active_block_jobs()
+ self.files.append(self.foo_img)
+ result = self.vm.qmp('drive-backup', device='drive0',
+ sync='dirty-bitmap', bitmap='unknown',
+ format=iotests.imgfmt, target=self.foo_img)
+ self.assert_qmp(result, 'error/class', 'GenericError')
+
+
+ def tearDown(self):
+ self.vm.shutdown()
+ for filename in self.files:
+ try:
+ os.remove(filename)
+ except OSError:
+ pass
+
+
+if __name__ == '__main__':
+ iotests.main(supported_fmts=['qcow2'])
diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
new file mode 100644
index 0000000..fbc63e6
--- /dev/null
+++ b/tests/qemu-iotests/112.out
@@ -0,0 +1,5 @@
+..
+----------------------------------------------------------------------
+Ran 2 tests
+
+OK
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 4b2b93b..b4ddf1b 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -114,6 +114,7 @@
109 rw auto
110 rw auto backing quick
111 rw auto quick
+112 rw auto backing
113 rw auto quick
114 rw auto quick
116 rw auto quick
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 14/17] iotests: add simple incremental backup case
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (12 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 13/17] iotests: add invalid input incremental backup tests John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 21:40 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 15/17] iotests: add transactional incremental backup test John Snow
` (3 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/112 | 120 +++++++++++++++++++++++++++++++++++++++++-
tests/qemu-iotests/112.out | 4 +-
tests/qemu-iotests/iotests.py | 18 ++++---
3 files changed, 133 insertions(+), 9 deletions(-)
diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
index 7985cd1..31431ad 100644
--- a/tests/qemu-iotests/112
+++ b/tests/qemu-iotests/112
@@ -22,18 +22,49 @@
import os
import iotests
+from iotests import qemu_img, qemu_io, create_image
def io_write_patterns(img, patterns):
for pattern in patterns:
iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
+class Bitmap:
+ def __init__(self, name, node):
+ self.name = name
+ self.node = node
+ self.pattern = os.path.join(iotests.test_dir,
+ '%s.backup.%%s.img' % name)
+ self.num = 0
+ self.backups = list()
+
+ def new_target(self, num=None):
+ if num is None:
+ num = self.num
+ self.num = num + 1
+ target = self.pattern % num
+ self.backups.append(target)
+ return target
+
+ def last_target(self):
+ return self.backups[-1]
+
+ def del_target(self):
+ os.remove(self.backups.pop())
+ self.num -= 1
+
+ def __del__(self):
+ for backup in self.backups:
+ try:
+ os.remove(backup)
+ except OSError:
+ pass
class TestIncrementalBackup(iotests.QMPTestCase):
def setUp(self):
self.bitmaps = list()
self.files = list()
- self.vm = iotests.VM()
+ self.vm = iotests.VM().add_args(['-S'])
self.test_img = os.path.join(iotests.test_dir, 'base.img')
self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
@@ -58,6 +89,93 @@ class TestIncrementalBackup(iotests.QMPTestCase):
iotests.qemu_img('create', '-f', fmt, img, size)
self.files.append(img)
+
+ def create_full_backup(self, drive='drive0'):
+ res = self.vm.qmp('drive-backup', device=drive,
+ sync='full', format=iotests.imgfmt,
+ target=self.full_bak)
+ self.assert_qmp(res, 'return', {})
+ self.wait_until_completed(drive)
+ self.check_full_backup()
+ self.files.append(self.full_bak)
+
+
+ def check_full_backup(self):
+ self.assertTrue(iotests.compare_images(self.test_img, self.full_bak))
+
+
+ def add_bitmap(self, name, node='drive0'):
+ bitmap = Bitmap(name, node)
+ self.bitmaps.append(bitmap)
+ result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node,
+ name=bitmap.name)
+ self.assert_qmp(result, 'return', {})
+ return bitmap
+
+
+ def create_incremental(self, bitmap=None, num=None,
+ parent=None, parentFormat=None, validate=True):
+ if bitmap is None:
+ bitmap = self.bitmaps[-1]
+
+ # If this is the first incremental backup for a bitmap,
+ # use the full backup as a backing image. Otherwise, use
+ # the last incremental backup.
+ if parent is None:
+ if bitmap.num is 0:
+ parent = self.full_bak
+ else:
+ parent = self.bitmaps[-1].last_target()
+
+ target = bitmap.new_target(num)
+ self.img_create(target, iotests.imgfmt, parent=parent)
+
+ result = self.vm.qmp('drive-backup', device=bitmap.node,
+ sync='dirty-bitmap', bitmap=bitmap.name,
+ format=iotests.imgfmt, target=target,
+ mode='existing')
+ self.assert_qmp(result, 'return', {})
+
+ event = self.wait_until_completed(bitmap.node, check_offset=validate)
+ if validate:
+ return self.check_incremental(target)
+
+
+ def check_incremental(self, target=None):
+ if target is None:
+ target = self.bitmaps[-1].last_target()
+ self.assertTrue(iotests.compare_images(self.test_img, target))
+ return True
+
+
+ def hmp_io_writes(self, drive, patterns):
+ for pattern in patterns:
+ self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
+ self.vm.hmp_qemu_io(drive, 'flush')
+
+
+ # Create a bitmap and full backup before VM execution begins,
+ # then create a series of incremental backups during execution.
+ def test_incremental_simple(self):
+ self.create_full_backup()
+ self.add_bitmap('bitmap0', 'drive0')
+ self.vm.hmp('c')
+ # Sanity: Create a "hollow" incremental backup
+ self.create_incremental()
+ # Three writes: One complete overwrite, one new segment,
+ # and one partial overlap.
+ self.hmp_io_writes('drive0', (('0xab', 0, 512),
+ ('0xfe', '16M', '256k'),
+ ('0x64', '32736k', '64k')))
+ self.create_incremental()
+ # Three more writes, one of each kind, like above
+ self.hmp_io_writes('drive0', (('0x9a', 0, 512),
+ ('0x55', '8M', '352k'),
+ ('0x78', '15872k', '1M')))
+ self.create_incremental()
+ return True
+
+
def test_sync_dirty_bitmap_missing(self):
self.assert_no_active_block_jobs()
self.files.append(self.foo_img)
diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
index fbc63e6..8d7e996 100644
--- a/tests/qemu-iotests/112.out
+++ b/tests/qemu-iotests/112.out
@@ -1,5 +1,5 @@
-..
+...
----------------------------------------------------------------------
-Ran 2 tests
+Ran 3 tests
OK
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 241b5ee..6bff935 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -88,6 +88,11 @@ class VM(object):
'-display', 'none', '-vga', 'none']
self._num_drives = 0
+ # Add arbitrary arguments to the command-line.
+ def add_args(self, arglist):
+ self._args = self._args + arglist
+ return self
+
# This can be used to add an unused monitor instance.
def add_monitor_telnet(self, ip, port):
args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
@@ -109,23 +114,24 @@ class VM(object):
self._num_drives += 1
return self
+ def hmp(self, args):
+ return self.qmp('human-monitor-command',
+ command_line=args)
+
def pause_drive(self, drive, event=None):
'''Pause drive r/w operations'''
if not event:
self.pause_drive(drive, "read_aio")
self.pause_drive(drive, "write_aio")
return
- self.qmp('human-monitor-command',
- command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
+ self.hmp('qemu-io %s "break %s bp_%s"' % (drive, event, drive))
def resume_drive(self, drive):
- self.qmp('human-monitor-command',
- command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
+ self.hmp('qemu-io %s "remove_break bp_%s"' % (drive, drive))
def hmp_qemu_io(self, drive, cmd):
'''Write to a given drive using an HMP command'''
- return self.qmp('human-monitor-command',
- command_line='qemu-io %s "%s"' % (drive, cmd))
+ return self.hmp('qemu-io %s "%s"' % (drive, cmd))
def add_fd(self, fd, fdset, opaque, opts=''):
'''Pass a file descriptor to the VM'''
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 14/17] iotests: add simple incremental backup case
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 14/17] iotests: add simple incremental backup case John Snow
@ 2015-02-11 21:40 ` Max Reitz
2015-02-11 22:02 ` John Snow
0 siblings, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-11 21:40 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> tests/qemu-iotests/112 | 120 +++++++++++++++++++++++++++++++++++++++++-
> tests/qemu-iotests/112.out | 4 +-
> tests/qemu-iotests/iotests.py | 18 ++++---
> 3 files changed, 133 insertions(+), 9 deletions(-)
>
> diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
> index 7985cd1..31431ad 100644
> --- a/tests/qemu-iotests/112
> +++ b/tests/qemu-iotests/112
> @@ -22,18 +22,49 @@
>
> import os
> import iotests
> +from iotests import qemu_img, qemu_io, create_image
Is this really necessary? For me, it works without, too; and if it is
necessary, shouldn't it be part of patch 13?
> def io_write_patterns(img, patterns):
> for pattern in patterns:
> iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
>
> +class Bitmap:
> + def __init__(self, name, node):
> + self.name = name
> + self.node = node
> + self.pattern = os.path.join(iotests.test_dir,
> + '%s.backup.%%s.img' % name)
Shouldn't this be %%i? %%s works for me, but I think %%i might look better.
> + self.num = 0
> + self.backups = list()
> +
> + def new_target(self, num=None):
> + if num is None:
> + num = self.num
> + self.num = num + 1
> + target = self.pattern % num
I hope nobody has a % in the test path...
Although it does look nice, maybe just self.base =
os.path.join(iotests.test_dir, '%s.backup.img.' % name) and target =
self.base + str(num) is more robust (putting the '.img' at the end is a
bonus).
> + self.backups.append(target)
> + return target
> +
> + def last_target(self):
> + return self.backups[-1]
> +
> + def del_target(self):
> + os.remove(self.backups.pop())
> + self.num -= 1
> +
> + def __del__(self):
> + for backup in self.backups:
> + try:
> + os.remove(backup)
> + except OSError:
> + pass
>
> class TestIncrementalBackup(iotests.QMPTestCase):
> def setUp(self):
> self.bitmaps = list()
> self.files = list()
> - self.vm = iotests.VM()
> + self.vm = iotests.VM().add_args(['-S'])
Isn't -machine accel=qtest (which is specified by default) enough?
> self.test_img = os.path.join(iotests.test_dir, 'base.img')
> self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
> self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
> @@ -58,6 +89,93 @@ class TestIncrementalBackup(iotests.QMPTestCase):
> iotests.qemu_img('create', '-f', fmt, img, size)
> self.files.append(img)
>
> +
> + def create_full_backup(self, drive='drive0'):
> + res = self.vm.qmp('drive-backup', device=drive,
> + sync='full', format=iotests.imgfmt,
> + target=self.full_bak)
> + self.assert_qmp(res, 'return', {})
> + self.wait_until_completed(drive)
> + self.check_full_backup()
> + self.files.append(self.full_bak)
> +
> +
> + def check_full_backup(self):
> + self.assertTrue(iotests.compare_images(self.test_img, self.full_bak))
> +
> +
> + def add_bitmap(self, name, node='drive0'):
> + bitmap = Bitmap(name, node)
> + self.bitmaps.append(bitmap)
> + result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node,
> + name=bitmap.name)
> + self.assert_qmp(result, 'return', {})
> + return bitmap
> +
> +
> + def create_incremental(self, bitmap=None, num=None,
> + parent=None, parentFormat=None, validate=True):
> + if bitmap is None:
> + bitmap = self.bitmaps[-1]
> +
> + # If this is the first incremental backup for a bitmap,
> + # use the full backup as a backing image. Otherwise, use
> + # the last incremental backup.
> + if parent is None:
> + if bitmap.num is 0:
Why not == 0?
> + parent = self.full_bak
> + else:
> + parent = self.bitmaps[-1].last_target()
Is this intentional or should this be bitmap.last_target()? In this
patch, no caller of create_incremental() gives any parameter, so there's
no difference.
> +
> + target = bitmap.new_target(num)
> + self.img_create(target, iotests.imgfmt, parent=parent)
> +
> + result = self.vm.qmp('drive-backup', device=bitmap.node,
> + sync='dirty-bitmap', bitmap=bitmap.name,
> + format=iotests.imgfmt, target=target,
> + mode='existing')
> + self.assert_qmp(result, 'return', {})
> +
> + event = self.wait_until_completed(bitmap.node, check_offset=validate)
> + if validate:
> + return self.check_incremental(target)
> +
> +
> + def check_incremental(self, target=None):
> + if target is None:
> + target = self.bitmaps[-1].last_target()
> + self.assertTrue(iotests.compare_images(self.test_img, target))
> + return True
> +
> +
> + def hmp_io_writes(self, drive, patterns):
> + for pattern in patterns:
> + self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
> + self.vm.hmp_qemu_io(drive, 'flush')
> +
> +
> + # Create a bitmap and full backup before VM execution begins,
> + # then create a series of incremental backups during execution.
> + def test_incremental_simple(self):
> + self.create_full_backup()
> + self.add_bitmap('bitmap0', 'drive0')
> + self.vm.hmp('c')
self.vm.qmp('continue') works, too, but as I said above, I think
accel=qtest should be enough so you may not need this at all.
(I'm saying this because we all know that QMP > HMP (obviously))
So I had some small questions which I think I want to have answers
before giving an R-b, but the test logic looks good to me.
Max
> + # Sanity: Create a "hollow" incremental backup
> + self.create_incremental()
> + # Three writes: One complete overwrite, one new segment,
> + # and one partial overlap.
> + self.hmp_io_writes('drive0', (('0xab', 0, 512),
> + ('0xfe', '16M', '256k'),
> + ('0x64', '32736k', '64k')))
> + self.create_incremental()
> + # Three more writes, one of each kind, like above
> + self.hmp_io_writes('drive0', (('0x9a', 0, 512),
> + ('0x55', '8M', '352k'),
> + ('0x78', '15872k', '1M')))
> + self.create_incremental()
> + return True
> +
> +
> def test_sync_dirty_bitmap_missing(self):
> self.assert_no_active_block_jobs()
> self.files.append(self.foo_img)
> diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
> index fbc63e6..8d7e996 100644
> --- a/tests/qemu-iotests/112.out
> +++ b/tests/qemu-iotests/112.out
> @@ -1,5 +1,5 @@
> -..
> +...
> ----------------------------------------------------------------------
> -Ran 2 tests
> +Ran 3 tests
>
> OK
> diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
> index 241b5ee..6bff935 100644
> --- a/tests/qemu-iotests/iotests.py
> +++ b/tests/qemu-iotests/iotests.py
> @@ -88,6 +88,11 @@ class VM(object):
> '-display', 'none', '-vga', 'none']
> self._num_drives = 0
>
> + # Add arbitrary arguments to the command-line.
> + def add_args(self, arglist):
> + self._args = self._args + arglist
> + return self
> +
> # This can be used to add an unused monitor instance.
> def add_monitor_telnet(self, ip, port):
> args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
> @@ -109,23 +114,24 @@ class VM(object):
> self._num_drives += 1
> return self
>
> + def hmp(self, args):
> + return self.qmp('human-monitor-command',
> + command_line=args)
> +
> def pause_drive(self, drive, event=None):
> '''Pause drive r/w operations'''
> if not event:
> self.pause_drive(drive, "read_aio")
> self.pause_drive(drive, "write_aio")
> return
> - self.qmp('human-monitor-command',
> - command_line='qemu-io %s "break %s bp_%s"' % (drive, event, drive))
> + self.hmp('qemu-io %s "break %s bp_%s"' % (drive, event, drive))
>
> def resume_drive(self, drive):
> - self.qmp('human-monitor-command',
> - command_line='qemu-io %s "remove_break bp_%s"' % (drive, drive))
> + self.hmp('qemu-io %s "remove_break bp_%s"' % (drive, drive))
>
> def hmp_qemu_io(self, drive, cmd):
> '''Write to a given drive using an HMP command'''
> - return self.qmp('human-monitor-command',
> - command_line='qemu-io %s "%s"' % (drive, cmd))
> + return self.hmp('qemu-io %s "%s"' % (drive, cmd))
>
> def add_fd(self, fd, fdset, opaque, opts=''):
> '''Pass a file descriptor to the VM'''
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 14/17] iotests: add simple incremental backup case
2015-02-11 21:40 ` Max Reitz
@ 2015-02-11 22:02 ` John Snow
0 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-11 22:02 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/11/2015 04:40 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> tests/qemu-iotests/112 | 120
>> +++++++++++++++++++++++++++++++++++++++++-
>> tests/qemu-iotests/112.out | 4 +-
>> tests/qemu-iotests/iotests.py | 18 ++++---
>> 3 files changed, 133 insertions(+), 9 deletions(-)
>>
>> diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
>> index 7985cd1..31431ad 100644
>> --- a/tests/qemu-iotests/112
>> +++ b/tests/qemu-iotests/112
>> @@ -22,18 +22,49 @@
>> import os
>> import iotests
>> +from iotests import qemu_img, qemu_io, create_image
>
> Is this really necessary? For me, it works without, too; and if it is
> necessary, shouldn't it be part of patch 13?
>
Maybe not? It's baggage from copying 056 as a template.
>> def io_write_patterns(img, patterns):
>> for pattern in patterns:
>> iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
>> +class Bitmap:
>> + def __init__(self, name, node):
>> + self.name = name
>> + self.node = node
>> + self.pattern = os.path.join(iotests.test_dir,
>> + '%s.backup.%%s.img' % name)
>
> Shouldn't this be %%i? %%s works for me, but I think %%i might look better.
>
It's probably more semantically appropriate, yes.
>> + self.num = 0
>> + self.backups = list()
>> +
>> + def new_target(self, num=None):
>> + if num is None:
>> + num = self.num
>> + self.num = num + 1
>> + target = self.pattern % num
>
> I hope nobody has a % in the test path...
>
Oh, good point.
> Although it does look nice, maybe just self.base =
> os.path.join(iotests.test_dir, '%s.backup.img.' % name) and target =
> self.base + str(num) is more robust (putting the '.img' at the end is a
> bonus).
>
>> + self.backups.append(target)
>> + return target
>> +
>> + def last_target(self):
>> + return self.backups[-1]
>> +
>> + def del_target(self):
>> + os.remove(self.backups.pop())
>> + self.num -= 1
>> +
>> + def __del__(self):
>> + for backup in self.backups:
>> + try:
>> + os.remove(backup)
>> + except OSError:
>> + pass
>> class TestIncrementalBackup(iotests.QMPTestCase):
>> def setUp(self):
>> self.bitmaps = list()
>> self.files = list()
>> - self.vm = iotests.VM()
>> + self.vm = iotests.VM().add_args(['-S'])
>
> Isn't -machine accel=qtest (which is specified by default) enough?
Yes, this is actually somewhat vestigial from a standalone version of
this that would tolerate an arbitrary image. I left it in because I
thought it was harmless and it served as an illustration about the
inferred timing of the commands being tested.
That said, it /can/ be removed. Its only purpose is illustrative.
>
>> self.test_img = os.path.join(iotests.test_dir, 'base.img')
>> self.full_bak = os.path.join(iotests.test_dir, 'backup.img')
>> self.foo_img = os.path.join(iotests.test_dir, 'foo.bar')
>> @@ -58,6 +89,93 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>> iotests.qemu_img('create', '-f', fmt, img, size)
>> self.files.append(img)
>> +
>> + def create_full_backup(self, drive='drive0'):
>> + res = self.vm.qmp('drive-backup', device=drive,
>> + sync='full', format=iotests.imgfmt,
>> + target=self.full_bak)
>> + self.assert_qmp(res, 'return', {})
>> + self.wait_until_completed(drive)
>> + self.check_full_backup()
>> + self.files.append(self.full_bak)
>> +
>> +
>> + def check_full_backup(self):
>> + self.assertTrue(iotests.compare_images(self.test_img,
>> self.full_bak))
>> +
>> +
>> + def add_bitmap(self, name, node='drive0'):
>> + bitmap = Bitmap(name, node)
>> + self.bitmaps.append(bitmap)
>> + result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node,
>> + name=bitmap.name)
>> + self.assert_qmp(result, 'return', {})
>> + return bitmap
>> +
>> +
>> + def create_incremental(self, bitmap=None, num=None,
>> + parent=None, parentFormat=None,
>> validate=True):
>> + if bitmap is None:
>> + bitmap = self.bitmaps[-1]
>> +
>> + # If this is the first incremental backup for a bitmap,
>> + # use the full backup as a backing image. Otherwise, use
>> + # the last incremental backup.
>> + if parent is None:
>> + if bitmap.num is 0:
>
> Why not == 0?
>
Just having a really good time in python.
I think "is" tests instances; it's like a strict equals. == is
equivalence as you and I are used to it. In this case, it winds up being
the same because python has some numerical objects cached, and any
equation that evaluates to 0 evaluates to the /same/ 0.
== is more correct, though, apparently.
>> + parent = self.full_bak
>> + else:
>> + parent = self.bitmaps[-1].last_target()
>
> Is this intentional or should this be bitmap.last_target()? In this
> patch, no caller of create_incremental() gives any parameter, so there's
> no difference.
>
Vestigial from the standalone edition. 'bitmap' should be preferred over
bitmaps[-1], yes.
>> +
>> + target = bitmap.new_target(num)
>> + self.img_create(target, iotests.imgfmt, parent=parent)
>> +
>> + result = self.vm.qmp('drive-backup', device=bitmap.node,
>> + sync='dirty-bitmap', bitmap=bitmap.name,
>> + format=iotests.imgfmt, target=target,
>> + mode='existing')
>> + self.assert_qmp(result, 'return', {})
>> +
>> + event = self.wait_until_completed(bitmap.node,
>> check_offset=validate)
>> + if validate:
>> + return self.check_incremental(target)
>> +
>> +
>> + def check_incremental(self, target=None):
>> + if target is None:
>> + target = self.bitmaps[-1].last_target()
>> + self.assertTrue(iotests.compare_images(self.test_img, target))
>> + return True
>> +
>> +
>> + def hmp_io_writes(self, drive, patterns):
>> + for pattern in patterns:
>> + self.vm.hmp_qemu_io(drive, 'write -P%s %s %s' % pattern)
>> + self.vm.hmp_qemu_io(drive, 'flush')
>> +
>> +
>> + # Create a bitmap and full backup before VM execution begins,
>> + # then create a series of incremental backups during execution.
>> + def test_incremental_simple(self):
>> + self.create_full_backup()
>> + self.add_bitmap('bitmap0', 'drive0')
>> + self.vm.hmp('c')
>
> self.vm.qmp('continue') works, too, but as I said above, I think
> accel=qtest should be enough so you may not need this at all.
>
> (I'm saying this because we all know that QMP > HMP (obviously))
>
>
> So I had some small questions which I think I want to have answers
> before giving an R-b, but the test logic looks good to me.
>
> Max
>
Explanation above covers this; a more human-focused demi-interactive
version preceded this iotest.
I'll excise it and just use comments to create the same documentation
effect.
>> + # Sanity: Create a "hollow" incremental backup
>> + self.create_incremental()
>> + # Three writes: One complete overwrite, one new segment,
>> + # and one partial overlap.
>> + self.hmp_io_writes('drive0', (('0xab', 0, 512),
>> + ('0xfe', '16M', '256k'),
>> + ('0x64', '32736k', '64k')))
>> + self.create_incremental()
>> + # Three more writes, one of each kind, like above
>> + self.hmp_io_writes('drive0', (('0x9a', 0, 512),
>> + ('0x55', '8M', '352k'),
>> + ('0x78', '15872k', '1M')))
>> + self.create_incremental()
>> + return True
>> +
>> +
>> def test_sync_dirty_bitmap_missing(self):
>> self.assert_no_active_block_jobs()
>> self.files.append(self.foo_img)
>> diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
>> index fbc63e6..8d7e996 100644
>> --- a/tests/qemu-iotests/112.out
>> +++ b/tests/qemu-iotests/112.out
>> @@ -1,5 +1,5 @@
>> -..
>> +...
>> ----------------------------------------------------------------------
>> -Ran 2 tests
>> +Ran 3 tests
>> OK
>> diff --git a/tests/qemu-iotests/iotests.py
>> b/tests/qemu-iotests/iotests.py
>> index 241b5ee..6bff935 100644
>> --- a/tests/qemu-iotests/iotests.py
>> +++ b/tests/qemu-iotests/iotests.py
>> @@ -88,6 +88,11 @@ class VM(object):
>> '-display', 'none', '-vga', 'none']
>> self._num_drives = 0
>> + # Add arbitrary arguments to the command-line.
>> + def add_args(self, arglist):
>> + self._args = self._args + arglist
>> + return self
>> +
>> # This can be used to add an unused monitor instance.
>> def add_monitor_telnet(self, ip, port):
>> args = 'tcp:%s:%d,server,nowait,telnet' % (ip, port)
>> @@ -109,23 +114,24 @@ class VM(object):
>> self._num_drives += 1
>> return self
>> + def hmp(self, args):
>> + return self.qmp('human-monitor-command',
>> + command_line=args)
>> +
>> def pause_drive(self, drive, event=None):
>> '''Pause drive r/w operations'''
>> if not event:
>> self.pause_drive(drive, "read_aio")
>> self.pause_drive(drive, "write_aio")
>> return
>> - self.qmp('human-monitor-command',
>> - command_line='qemu-io %s "break %s bp_%s"' %
>> (drive, event, drive))
>> + self.hmp('qemu-io %s "break %s bp_%s"' % (drive, event, drive))
>> def resume_drive(self, drive):
>> - self.qmp('human-monitor-command',
>> - command_line='qemu-io %s "remove_break bp_%s"' %
>> (drive, drive))
>> + self.hmp('qemu-io %s "remove_break bp_%s"' % (drive, drive))
>> def hmp_qemu_io(self, drive, cmd):
>> '''Write to a given drive using an HMP command'''
>> - return self.qmp('human-monitor-command',
>> - command_line='qemu-io %s "%s"' % (drive, cmd))
>> + return self.hmp('qemu-io %s "%s"' % (drive, cmd))
>> def add_fd(self, fd, fdset, opaque, opts=''):
>> '''Pass a file descriptor to the VM'''
>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 15/17] iotests: add transactional incremental backup test
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (13 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 14/17] iotests: add simple incremental backup case John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 21:49 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 16/17] blkdebug: fix "once" rule John Snow
` (2 subsequent siblings)
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/112 | 45 +++++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/112.out | 4 ++--
2 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
index 31431ad..7d1a6ec 100644
--- a/tests/qemu-iotests/112
+++ b/tests/qemu-iotests/112
@@ -176,6 +176,51 @@ class TestIncrementalBackup(iotests.QMPTestCase):
return True
+ # Create a bitmap and full backup *after* VM execution begins.
+ # Use transactions to accomplish a bitmap re-sync, then create
+ # an incremental backup.
+ def test_incremental_transaction(self):
+ bitmap0 = self.add_bitmap('bitmap0', 'drive0')
+ self.vm.hmp('c')
+ self.hmp_io_writes('drive0', (('0xab', 0, 512),
+ ('0xfe', '16M', '256k'),
+ ('0x64', '32736k', '64k')))
+ bitmap1 = self.add_bitmap('bitmap1', 'drive0')
+
+ result = self.vm.qmp('transaction', actions=[
+ {
+ 'type': 'block-dirty-bitmap-clear',
+ 'data': { 'node': 'drive0',
+ 'name': 'bitmap0' },
+ },
+ {
+ 'type': 'block-dirty-bitmap-clear',
+ 'data': { 'node': 'drive0',
+ 'name': 'bitmap1' },
+ },
+ {
+ 'type': 'drive-backup',
+ 'data': { 'device': 'drive0',
+ 'sync': 'full',
+ 'format': iotests.imgfmt,
+ 'target': self.full_bak },
+ }
+ ])
+ self.assert_qmp(result, 'return', {})
+ self.wait_until_completed()
+ self.files.append(self.full_bak)
+ self.check_full_backup()
+
+ self.hmp_io_writes('drive0', (('0x9a', 0, 512),
+ ('0x55', '8M', '352k'),
+ ('0x78', '15872k', '1M')))
+ # Both bitmaps should be in sync and create fully valid
+ # incremental backups
+ res1 = self.create_incremental(bitmap0)
+ res2 = self.create_incremental(bitmap1)
+ self.assertTrue(res1 and res2)
+
+
def test_sync_dirty_bitmap_missing(self):
self.assert_no_active_block_jobs()
self.files.append(self.foo_img)
diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
index 8d7e996..89968f3 100644
--- a/tests/qemu-iotests/112.out
+++ b/tests/qemu-iotests/112.out
@@ -1,5 +1,5 @@
-...
+....
----------------------------------------------------------------------
-Ran 3 tests
+Ran 4 tests
OK
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 16/17] blkdebug: fix "once" rule
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (14 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 15/17] iotests: add transactional incremental backup test John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 21:50 ` Max Reitz
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 17/17] iotests: add incremental backup failure recovery test John Snow
2015-02-10 16:32 ` [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
Background:
The blkdebug scripts are currently engineered so that when a debug
event occurs, a prefilter browses a master list of parsed rules for a
certain event and adds them to an "active list" of rules to be used for
the forthcoming action, provided the events and state numbers match.
Then, once the request is received, the last active rule is used to
inject an error if certain parameters match.
This active list is cleared every time the prefilter injects a new
rule for the first time during a debug event.
The "once" rule currently causes the error injection, if it is
triggered, to only clear the active list. This is insufficient for
preventing future injections of the same rule.
Remedy:
This patch /deletes/ the rule from the list that the prefilter
browses, so it is gone for good. In V2, we remove only the rule of
interest from the active list instead of allowing the "once" rule to
clear the entire list of active rules.
Impact:
This affects iotests 026. Several ENOSPC tests that used "once" can
be seen to have output that shows multiple failure messages. After
this patch, the error messages tend to be smaller and less severe, but
the injection can still be seen to be working. I have patched the
expected output to expect the smaller error messages.
Signed-off-by: John Snow <jsnow@redhat.com>
---
block/blkdebug.c | 6 ++++--
tests/qemu-iotests/026.out | 24 ++++--------------------
2 files changed, 8 insertions(+), 22 deletions(-)
diff --git a/block/blkdebug.c b/block/blkdebug.c
index 9ce35cd..63611e0 100644
--- a/block/blkdebug.c
+++ b/block/blkdebug.c
@@ -472,12 +472,14 @@ static BlockAIOCB *inject_error(BlockDriverState *bs,
int error = rule->options.inject.error;
struct BlkdebugAIOCB *acb;
QEMUBH *bh;
+ bool immediately = rule->options.inject.immediately;
if (rule->options.inject.once) {
- QSIMPLEQ_INIT(&s->active_rules);
+ QSIMPLEQ_REMOVE(&s->active_rules, rule, BlkdebugRule, active_next);
+ remove_rule(rule);
}
- if (rule->options.inject.immediately) {
+ if (immediately) {
return NULL;
}
diff --git a/tests/qemu-iotests/026.out b/tests/qemu-iotests/026.out
index 524f7ee..5e964fb 100644
--- a/tests/qemu-iotests/026.out
+++ b/tests/qemu-iotests/026.out
@@ -140,19 +140,13 @@ No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: l2_update; errno: 5; imm: off; once: on; write
-Failed to flush the L2 table cache: Input/output error
write failed: Input/output error
-
-127 leaked clusters were found on the image.
-This means waste of disk space, but no harm to data.
+No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: l2_update; errno: 5; imm: off; once: on; write -b
-Failed to flush the L2 table cache: Input/output error
write failed: Input/output error
-
-127 leaked clusters were found on the image.
-This means waste of disk space, but no harm to data.
+No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: l2_update; errno: 5; imm: off; once: off; write
@@ -174,19 +168,13 @@ This means waste of disk space, but no harm to data.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: l2_update; errno: 28; imm: off; once: on; write
-Failed to flush the L2 table cache: No space left on device
write failed: No space left on device
-
-127 leaked clusters were found on the image.
-This means waste of disk space, but no harm to data.
+No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: l2_update; errno: 28; imm: off; once: on; write -b
-Failed to flush the L2 table cache: No space left on device
write failed: No space left on device
-
-127 leaked clusters were found on the image.
-This means waste of disk space, but no harm to data.
+No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: l2_update; errno: 28; imm: off; once: off; write
@@ -356,13 +344,11 @@ No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: refblock_update_part; errno: 5; imm: off; once: on; write
-Failed to flush the refcount block cache: Input/output error
write failed: Input/output error
No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: refblock_update_part; errno: 5; imm: off; once: on; write -b
-Failed to flush the refcount block cache: Input/output error
write failed: Input/output error
No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
@@ -382,13 +368,11 @@ No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: refblock_update_part; errno: 28; imm: off; once: on; write
-Failed to flush the refcount block cache: No space left on device
write failed: No space left on device
No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
Event: refblock_update_part; errno: 28; imm: off; once: on; write -b
-Failed to flush the refcount block cache: No space left on device
write failed: No space left on device
No errors were found on the image.
Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 16/17] blkdebug: fix "once" rule
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 16/17] blkdebug: fix "once" rule John Snow
@ 2015-02-11 21:50 ` Max Reitz
2015-02-11 22:04 ` John Snow
0 siblings, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-11 21:50 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> Background:
> The blkdebug scripts are currently engineered so that when a debug
> event occurs, a prefilter browses a master list of parsed rules for a
> certain event and adds them to an "active list" of rules to be used for
> the forthcoming action, provided the events and state numbers match.
>
> Then, once the request is received, the last active rule is used to
> inject an error if certain parameters match.
>
> This active list is cleared every time the prefilter injects a new
> rule for the first time during a debug event.
>
> The "once" rule currently causes the error injection, if it is
> triggered, to only clear the active list. This is insufficient for
> preventing future injections of the same rule.
>
> Remedy:
> This patch /deletes/ the rule from the list that the prefilter
> browses, so it is gone for good. In V2, we remove only the rule of
> interest from the active list instead of allowing the "once" rule to
> clear the entire list of active rules.
>
> Impact:
> This affects iotests 026. Several ENOSPC tests that used "once" can
> be seen to have output that shows multiple failure messages. After
> this patch, the error messages tend to be smaller and less severe, but
> the injection can still be seen to be working. I have patched the
> expected output to expect the smaller error messages.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block/blkdebug.c | 6 ++++--
> tests/qemu-iotests/026.out | 24 ++++--------------------
> 2 files changed, 8 insertions(+), 22 deletions(-)
Well, I think this shouldn't be part of this series, but you can find my
R-b here:
http://lists.nongnu.org/archive/html/qemu-devel/2015-02/msg01209.html
Max
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 16/17] blkdebug: fix "once" rule
2015-02-11 21:50 ` Max Reitz
@ 2015-02-11 22:04 ` John Snow
0 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-11 22:04 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/11/2015 04:50 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> Background:
>> The blkdebug scripts are currently engineered so that when a debug
>> event occurs, a prefilter browses a master list of parsed rules for a
>> certain event and adds them to an "active list" of rules to be used for
>> the forthcoming action, provided the events and state numbers match.
>>
>> Then, once the request is received, the last active rule is used to
>> inject an error if certain parameters match.
>>
>> This active list is cleared every time the prefilter injects a new
>> rule for the first time during a debug event.
>>
>> The "once" rule currently causes the error injection, if it is
>> triggered, to only clear the active list. This is insufficient for
>> preventing future injections of the same rule.
>>
>> Remedy:
>> This patch /deletes/ the rule from the list that the prefilter
>> browses, so it is gone for good. In V2, we remove only the rule of
>> interest from the active list instead of allowing the "once" rule to
>> clear the entire list of active rules.
>>
>> Impact:
>> This affects iotests 026. Several ENOSPC tests that used "once" can
>> be seen to have output that shows multiple failure messages. After
>> this patch, the error messages tend to be smaller and less severe, but
>> the injection can still be seen to be working. I have patched the
>> expected output to expect the smaller error messages.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block/blkdebug.c | 6 ++++--
>> tests/qemu-iotests/026.out | 24 ++++--------------------
>> 2 files changed, 8 insertions(+), 22 deletions(-)
>
> Well, I think this shouldn't be part of this series, but you can find my
> R-b here:
> http://lists.nongnu.org/archive/html/qemu-devel/2015-02/msg01209.html
>
> Max
I will need to do a version bump to address patches 7-9, fix error
messages, and other house-cleaning issues.
I can just put in the cover letter that this series *depends* on that
other patch.
Otherwise, this series can be merged up to this patch before it's
actually needed and it won't have any ill effect.
--js
^ permalink raw reply [flat|nested] 55+ messages in thread
* [Qemu-devel] [PATCH v12 17/17] iotests: add incremental backup failure recovery test
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (15 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 16/17] blkdebug: fix "once" rule John Snow
@ 2015-02-10 1:35 ` John Snow
2015-02-11 22:01 ` Max Reitz
2015-02-10 16:32 ` [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
17 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-10 1:35 UTC (permalink / raw)
To: qemu-devel; +Cc: kwolf, famz, John Snow, armbru, mreitz, vsementsov, stefanha
To test the failure case, we modify iotests.py to allow us to specify
that we'd like to allow failures when we wait for block job events.
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/112 | 55 ++++++++++++++++++++++++++++++++++++++++++-
tests/qemu-iotests/112.out | 4 ++--
tests/qemu-iotests/iotests.py | 6 +++--
3 files changed, 60 insertions(+), 5 deletions(-)
diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
index 7d1a6ec..0c1d35a 100644
--- a/tests/qemu-iotests/112
+++ b/tests/qemu-iotests/112
@@ -24,6 +24,11 @@ import os
import iotests
from iotests import qemu_img, qemu_io, create_image
+def blkdebug_rule(file, type, **kwargs):
+ file.write('[%s]\n' % type)
+ for key, value in kwargs.iteritems():
+ file.write('%s = "%s"\n' % (key, value))
+ file.write('\n')
def io_write_patterns(img, patterns):
for pattern in patterns:
@@ -136,7 +141,11 @@ class TestIncrementalBackup(iotests.QMPTestCase):
mode='existing')
self.assert_qmp(result, 'return', {})
- event = self.wait_until_completed(bitmap.node, check_offset=validate)
+ event = self.wait_until_completed(bitmap.node, check_offset=validate,
+ allow_failures=(not validate))
+ if 'error' in event['data']:
+ bitmap.del_target()
+ return False
if validate:
return self.check_incremental(target)
@@ -221,6 +230,50 @@ class TestIncrementalBackup(iotests.QMPTestCase):
self.assertTrue(res1 and res2)
+ def test_incremental_failure(self):
+ blkdebug = os.path.join(iotests.test_dir, 'blkdebug.conf')
+ self.files.append(blkdebug)
+
+ with open(blkdebug, 'w+') as file:
+ blkdebug_rule(file, 'set-state',
+ event="flush_to_disk",
+ state="1",
+ new_state="2")
+ blkdebug_rule(file, 'inject-error',
+ event="read_aio", errno="5",
+ state="2", immediately="off",
+ once="on")
+
+ # Create a blkdebug interface to this img as 'drive1'
+ result = self.vm.qmp('blockdev-add', options={
+ 'id': 'drive1',
+ 'driver': iotests.imgfmt,
+ 'file': {
+ 'driver': 'blkdebug',
+ 'config': blkdebug,
+ 'image': {
+ 'driver': 'file',
+ 'filename': self.test_img
+ }
+ }
+ })
+ self.assert_qmp(result, 'return', {})
+
+ self.create_full_backup()
+ self.add_bitmap('bitmap0', 'drive1')
+ self.vm.hmp("c")
+ self.hmp_io_writes('drive1', (('0xab', 0, 512),
+ ('0xfe', '16M', '256k'),
+ ('0x64', '32736k', '64k')))
+
+ result = self.create_incremental(validate=False)
+ self.assertFalse(result)
+ self.hmp_io_writes('drive1', (('0x9a', 0, 512),
+ ('0x55', '8M', '352k'),
+ ('0x78', '15872k', '1M')))
+ self.create_incremental()
+
+
def test_sync_dirty_bitmap_missing(self):
self.assert_no_active_block_jobs()
self.files.append(self.foo_img)
diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
index 89968f3..914e373 100644
--- a/tests/qemu-iotests/112.out
+++ b/tests/qemu-iotests/112.out
@@ -1,5 +1,5 @@
-....
+.....
----------------------------------------------------------------------
-Ran 4 tests
+Ran 5 tests
OK
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 6bff935..e452c40 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -264,14 +264,16 @@ class QMPTestCase(unittest.TestCase):
self.assert_no_active_block_jobs()
return result
- def wait_until_completed(self, drive='drive0', check_offset=True):
+ def wait_until_completed(self, drive='drive0', check_offset=True,
+ allow_failures=False):
'''Wait for a block job to finish, returning the event'''
completed = False
while not completed:
for event in self.vm.get_qmp_events(wait=True):
if event['event'] == 'BLOCK_JOB_COMPLETED':
self.assert_qmp(event, 'data/device', drive)
- self.assert_qmp_absent(event, 'data/error')
+ if not allow_failures:
+ self.assert_qmp_absent(event, 'data/error')
if check_offset:
self.assert_qmp(event, 'data/offset', event['data']['len'])
completed = True
--
1.9.3
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 17/17] iotests: add incremental backup failure recovery test
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 17/17] iotests: add incremental backup failure recovery test John Snow
@ 2015-02-11 22:01 ` Max Reitz
2015-02-11 22:08 ` John Snow
0 siblings, 1 reply; 55+ messages in thread
From: Max Reitz @ 2015-02-11 22:01 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-09 at 20:35, John Snow wrote:
> To test the failure case, we modify iotests.py to allow us to specify
> that we'd like to allow failures when we wait for block job events.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> tests/qemu-iotests/112 | 55 ++++++++++++++++++++++++++++++++++++++++++-
> tests/qemu-iotests/112.out | 4 ++--
> tests/qemu-iotests/iotests.py | 6 +++--
> 3 files changed, 60 insertions(+), 5 deletions(-)
>
> diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
> index 7d1a6ec..0c1d35a 100644
> --- a/tests/qemu-iotests/112
> +++ b/tests/qemu-iotests/112
> @@ -24,6 +24,11 @@ import os
> import iotests
> from iotests import qemu_img, qemu_io, create_image
>
> +def blkdebug_rule(file, type, **kwargs):
> + file.write('[%s]\n' % type)
> + for key, value in kwargs.iteritems():
> + file.write('%s = "%s"\n' % (key, value))
> + file.write('\n')
>
> def io_write_patterns(img, patterns):
> for pattern in patterns:
> @@ -136,7 +141,11 @@ class TestIncrementalBackup(iotests.QMPTestCase):
> mode='existing')
> self.assert_qmp(result, 'return', {})
>
> - event = self.wait_until_completed(bitmap.node, check_offset=validate)
> + event = self.wait_until_completed(bitmap.node, check_offset=validate,
> + allow_failures=(not validate))
> + if 'error' in event['data']:
> + bitmap.del_target()
> + return False
> if validate:
> return self.check_incremental(target)
>
> @@ -221,6 +230,50 @@ class TestIncrementalBackup(iotests.QMPTestCase):
> self.assertTrue(res1 and res2)
>
>
> + def test_incremental_failure(self):
> + blkdebug = os.path.join(iotests.test_dir, 'blkdebug.conf')
> + self.files.append(blkdebug)
> +
> + with open(blkdebug, 'w+') as file:
> + blkdebug_rule(file, 'set-state',
> + event="flush_to_disk",
> + state="1",
> + new_state="2")
> + blkdebug_rule(file, 'inject-error',
> + event="read_aio", errno="5",
> + state="2", immediately="off",
> + once="on")
> +
> + # Create a blkdebug interface to this img as 'drive1'
> + result = self.vm.qmp('blockdev-add', options={
> + 'id': 'drive1',
> + 'driver': iotests.imgfmt,
> + 'file': {
> + 'driver': 'blkdebug',
> + 'config': blkdebug,
> + 'image': {
> + 'driver': 'file',
> + 'filename': self.test_img
> + }
> + }
> + })
Awwww, I've written such a nice QMP interface and you won't use it? :'-(
See test 071, I encourage you to try. ;-)
Max
> + self.assert_qmp(result, 'return', {})
> +
> + self.create_full_backup()
> + self.add_bitmap('bitmap0', 'drive1')
> + self.vm.hmp("c")
> + self.hmp_io_writes('drive1', (('0xab', 0, 512),
> + ('0xfe', '16M', '256k'),
> + ('0x64', '32736k', '64k')))
> +
> + result = self.create_incremental(validate=False)
> + self.assertFalse(result)
> + self.hmp_io_writes('drive1', (('0x9a', 0, 512),
> + ('0x55', '8M', '352k'),
> + ('0x78', '15872k', '1M')))
> + self.create_incremental()
> +
> +
> def test_sync_dirty_bitmap_missing(self):
> self.assert_no_active_block_jobs()
> self.files.append(self.foo_img)
> diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
> index 89968f3..914e373 100644
> --- a/tests/qemu-iotests/112.out
> +++ b/tests/qemu-iotests/112.out
> @@ -1,5 +1,5 @@
> -....
> +.....
> ----------------------------------------------------------------------
> -Ran 4 tests
> +Ran 5 tests
>
> OK
> diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
> index 6bff935..e452c40 100644
> --- a/tests/qemu-iotests/iotests.py
> +++ b/tests/qemu-iotests/iotests.py
> @@ -264,14 +264,16 @@ class QMPTestCase(unittest.TestCase):
> self.assert_no_active_block_jobs()
> return result
>
> - def wait_until_completed(self, drive='drive0', check_offset=True):
> + def wait_until_completed(self, drive='drive0', check_offset=True,
> + allow_failures=False):
> '''Wait for a block job to finish, returning the event'''
> completed = False
> while not completed:
> for event in self.vm.get_qmp_events(wait=True):
> if event['event'] == 'BLOCK_JOB_COMPLETED':
> self.assert_qmp(event, 'data/device', drive)
> - self.assert_qmp_absent(event, 'data/error')
> + if not allow_failures:
> + self.assert_qmp_absent(event, 'data/error')
> if check_offset:
> self.assert_qmp(event, 'data/offset', event['data']['len'])
> completed = True
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 17/17] iotests: add incremental backup failure recovery test
2015-02-11 22:01 ` Max Reitz
@ 2015-02-11 22:08 ` John Snow
2015-02-11 22:11 ` Max Reitz
0 siblings, 1 reply; 55+ messages in thread
From: John Snow @ 2015-02-11 22:08 UTC (permalink / raw)
To: Max Reitz, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 02/11/2015 05:01 PM, Max Reitz wrote:
> On 2015-02-09 at 20:35, John Snow wrote:
>> To test the failure case, we modify iotests.py to allow us to specify
>> that we'd like to allow failures when we wait for block job events.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> tests/qemu-iotests/112 | 55
>> ++++++++++++++++++++++++++++++++++++++++++-
>> tests/qemu-iotests/112.out | 4 ++--
>> tests/qemu-iotests/iotests.py | 6 +++--
>> 3 files changed, 60 insertions(+), 5 deletions(-)
>>
>> diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
>> index 7d1a6ec..0c1d35a 100644
>> --- a/tests/qemu-iotests/112
>> +++ b/tests/qemu-iotests/112
>> @@ -24,6 +24,11 @@ import os
>> import iotests
>> from iotests import qemu_img, qemu_io, create_image
>> +def blkdebug_rule(file, type, **kwargs):
>> + file.write('[%s]\n' % type)
>> + for key, value in kwargs.iteritems():
>> + file.write('%s = "%s"\n' % (key, value))
>> + file.write('\n')
>> def io_write_patterns(img, patterns):
>> for pattern in patterns:
>> @@ -136,7 +141,11 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>> mode='existing')
>> self.assert_qmp(result, 'return', {})
>> - event = self.wait_until_completed(bitmap.node,
>> check_offset=validate)
>> + event = self.wait_until_completed(bitmap.node,
>> check_offset=validate,
>> + allow_failures=(not validate))
>> + if 'error' in event['data']:
>> + bitmap.del_target()
>> + return False
>> if validate:
>> return self.check_incremental(target)
>> @@ -221,6 +230,50 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>> self.assertTrue(res1 and res2)
>> + def test_incremental_failure(self):
>> + blkdebug = os.path.join(iotests.test_dir, 'blkdebug.conf')
>> + self.files.append(blkdebug)
>> +
>> + with open(blkdebug, 'w+') as file:
>> + blkdebug_rule(file, 'set-state',
>> + event="flush_to_disk",
>> + state="1",
>> + new_state="2")
>> + blkdebug_rule(file, 'inject-error',
>> + event="read_aio", errno="5",
>> + state="2", immediately="off",
>> + once="on")
>> +
>> + # Create a blkdebug interface to this img as 'drive1'
>> + result = self.vm.qmp('blockdev-add', options={
>> + 'id': 'drive1',
>> + 'driver': iotests.imgfmt,
>> + 'file': {
>> + 'driver': 'blkdebug',
>> + 'config': blkdebug,
>> + 'image': {
>> + 'driver': 'file',
>> + 'filename': self.test_img
>> + }
>> + }
>> + })
>
> Awwww, I've written such a nice QMP interface and you won't use it? :'-(
>
> See test 071, I encourage you to try. ;-)
>
> Max
>
Aww, you're going to hold out on me for that?
(Actually, I did notice when you reviewed my blkdebug patch but not
before then. This was already working so I just left it. (Though it is
nicer in that you don't have to create a file. (I'll look.)))
>> + self.assert_qmp(result, 'return', {})
>> +
>> + self.create_full_backup()
>> + self.add_bitmap('bitmap0', 'drive1')
>> + self.vm.hmp("c")
>> + self.hmp_io_writes('drive1', (('0xab', 0, 512),
>> + ('0xfe', '16M', '256k'),
>> + ('0x64', '32736k', '64k')))
>> +
>> + result = self.create_incremental(validate=False)
>> + self.assertFalse(result)
>> + self.hmp_io_writes('drive1', (('0x9a', 0, 512),
>> + ('0x55', '8M', '352k'),
>> + ('0x78', '15872k', '1M')))
>> + self.create_incremental()
>> +
>> +
>> def test_sync_dirty_bitmap_missing(self):
>> self.assert_no_active_block_jobs()
>> self.files.append(self.foo_img)
>> diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
>> index 89968f3..914e373 100644
>> --- a/tests/qemu-iotests/112.out
>> +++ b/tests/qemu-iotests/112.out
>> @@ -1,5 +1,5 @@
>> -....
>> +.....
>> ----------------------------------------------------------------------
>> -Ran 4 tests
>> +Ran 5 tests
>> OK
>> diff --git a/tests/qemu-iotests/iotests.py
>> b/tests/qemu-iotests/iotests.py
>> index 6bff935..e452c40 100644
>> --- a/tests/qemu-iotests/iotests.py
>> +++ b/tests/qemu-iotests/iotests.py
>> @@ -264,14 +264,16 @@ class QMPTestCase(unittest.TestCase):
>> self.assert_no_active_block_jobs()
>> return result
>> - def wait_until_completed(self, drive='drive0', check_offset=True):
>> + def wait_until_completed(self, drive='drive0', check_offset=True,
>> + allow_failures=False):
>> '''Wait for a block job to finish, returning the event'''
>> completed = False
>> while not completed:
>> for event in self.vm.get_qmp_events(wait=True):
>> if event['event'] == 'BLOCK_JOB_COMPLETED':
>> self.assert_qmp(event, 'data/device', drive)
>> - self.assert_qmp_absent(event, 'data/error')
>> + if not allow_failures:
>> + self.assert_qmp_absent(event, 'data/error')
>> if check_offset:
>> self.assert_qmp(event, 'data/offset',
>> event['data']['len'])
>> completed = True
>
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 17/17] iotests: add incremental backup failure recovery test
2015-02-11 22:08 ` John Snow
@ 2015-02-11 22:11 ` Max Reitz
0 siblings, 0 replies; 55+ messages in thread
From: Max Reitz @ 2015-02-11 22:11 UTC (permalink / raw)
To: John Snow, qemu-devel; +Cc: kwolf, famz, armbru, vsementsov, stefanha
On 2015-02-11 at 17:08, John Snow wrote:
>
>
> On 02/11/2015 05:01 PM, Max Reitz wrote:
>> On 2015-02-09 at 20:35, John Snow wrote:
>>> To test the failure case, we modify iotests.py to allow us to specify
>>> that we'd like to allow failures when we wait for block job events.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>> tests/qemu-iotests/112 | 55
>>> ++++++++++++++++++++++++++++++++++++++++++-
>>> tests/qemu-iotests/112.out | 4 ++--
>>> tests/qemu-iotests/iotests.py | 6 +++--
>>> 3 files changed, 60 insertions(+), 5 deletions(-)
>>>
>>> diff --git a/tests/qemu-iotests/112 b/tests/qemu-iotests/112
>>> index 7d1a6ec..0c1d35a 100644
>>> --- a/tests/qemu-iotests/112
>>> +++ b/tests/qemu-iotests/112
>>> @@ -24,6 +24,11 @@ import os
>>> import iotests
>>> from iotests import qemu_img, qemu_io, create_image
>>> +def blkdebug_rule(file, type, **kwargs):
>>> + file.write('[%s]\n' % type)
>>> + for key, value in kwargs.iteritems():
>>> + file.write('%s = "%s"\n' % (key, value))
>>> + file.write('\n')
>>> def io_write_patterns(img, patterns):
>>> for pattern in patterns:
>>> @@ -136,7 +141,11 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>> mode='existing')
>>> self.assert_qmp(result, 'return', {})
>>> - event = self.wait_until_completed(bitmap.node,
>>> check_offset=validate)
>>> + event = self.wait_until_completed(bitmap.node,
>>> check_offset=validate,
>>> + allow_failures=(not
>>> validate))
>>> + if 'error' in event['data']:
>>> + bitmap.del_target()
>>> + return False
>>> if validate:
>>> return self.check_incremental(target)
>>> @@ -221,6 +230,50 @@ class TestIncrementalBackup(iotests.QMPTestCase):
>>> self.assertTrue(res1 and res2)
>>> + def test_incremental_failure(self):
>>> + blkdebug = os.path.join(iotests.test_dir, 'blkdebug.conf')
>>> + self.files.append(blkdebug)
>>> +
>>> + with open(blkdebug, 'w+') as file:
>>> + blkdebug_rule(file, 'set-state',
>>> + event="flush_to_disk",
>>> + state="1",
>>> + new_state="2")
>>> + blkdebug_rule(file, 'inject-error',
>>> + event="read_aio", errno="5",
>>> + state="2", immediately="off",
>>> + once="on")
>>> +
>>> + # Create a blkdebug interface to this img as 'drive1'
>>> + result = self.vm.qmp('blockdev-add', options={
>>> + 'id': 'drive1',
>>> + 'driver': iotests.imgfmt,
>>> + 'file': {
>>> + 'driver': 'blkdebug',
>>> + 'config': blkdebug,
>>> + 'image': {
>>> + 'driver': 'file',
>>> + 'filename': self.test_img
>>> + }
>>> + }
>>> + })
>>
>> Awwww, I've written such a nice QMP interface and you won't use it? :'-(
>>
>> See test 071, I encourage you to try. ;-)
>>
>> Max
>>
>
> Aww, you're going to hold out on me for that?
Yes, not least because I would have to take a peek into Python's I/O
functions first. ;-)
Max
> (Actually, I did notice when you reviewed my blkdebug patch but not
> before then. This was already working so I just left it. (Though it is
> nicer in that you don't have to create a file. (I'll look.)))
>
>>> + self.assert_qmp(result, 'return', {})
>>> +
>>> + self.create_full_backup()
>>> + self.add_bitmap('bitmap0', 'drive1')
>>> + self.vm.hmp("c")
>>> + self.hmp_io_writes('drive1', (('0xab', 0, 512),
>>> + ('0xfe', '16M', '256k'),
>>> + ('0x64', '32736k', '64k')))
>>> +
>>> + result = self.create_incremental(validate=False)
>>> + self.assertFalse(result)
>>> + self.hmp_io_writes('drive1', (('0x9a', 0, 512),
>>> + ('0x55', '8M', '352k'),
>>> + ('0x78', '15872k', '1M')))
>>> + self.create_incremental()
>>> +
>>> +
>>> def test_sync_dirty_bitmap_missing(self):
>>> self.assert_no_active_block_jobs()
>>> self.files.append(self.foo_img)
>>> diff --git a/tests/qemu-iotests/112.out b/tests/qemu-iotests/112.out
>>> index 89968f3..914e373 100644
>>> --- a/tests/qemu-iotests/112.out
>>> +++ b/tests/qemu-iotests/112.out
>>> @@ -1,5 +1,5 @@
>>> -....
>>> +.....
>>> ----------------------------------------------------------------------
>>> -Ran 4 tests
>>> +Ran 5 tests
>>> OK
>>> diff --git a/tests/qemu-iotests/iotests.py
>>> b/tests/qemu-iotests/iotests.py
>>> index 6bff935..e452c40 100644
>>> --- a/tests/qemu-iotests/iotests.py
>>> +++ b/tests/qemu-iotests/iotests.py
>>> @@ -264,14 +264,16 @@ class QMPTestCase(unittest.TestCase):
>>> self.assert_no_active_block_jobs()
>>> return result
>>> - def wait_until_completed(self, drive='drive0', check_offset=True):
>>> + def wait_until_completed(self, drive='drive0', check_offset=True,
>>> + allow_failures=False):
>>> '''Wait for a block job to finish, returning the event'''
>>> completed = False
>>> while not completed:
>>> for event in self.vm.get_qmp_events(wait=True):
>>> if event['event'] == 'BLOCK_JOB_COMPLETED':
>>> self.assert_qmp(event, 'data/device', drive)
>>> - self.assert_qmp_absent(event, 'data/error')
>>> + if not allow_failures:
>>> + self.assert_qmp_absent(event, 'data/error')
>>> if check_offset:
>>> self.assert_qmp(event, 'data/offset',
>>> event['data']['len'])
>>> completed = True
>>
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [Qemu-devel] [PATCH v12 00/17] block: incremental backup series
2015-02-10 1:35 [Qemu-devel] [PATCH v12 00/17] block: incremental backup series John Snow
` (16 preceding siblings ...)
2015-02-10 1:35 ` [Qemu-devel] [PATCH v12 17/17] iotests: add incremental backup failure recovery test John Snow
@ 2015-02-10 16:32 ` John Snow
17 siblings, 0 replies; 55+ messages in thread
From: John Snow @ 2015-02-10 16:32 UTC (permalink / raw)
To: qemu-devel
On 02/09/2015 08:35 PM, John Snow wrote:
> Welcome to the "incremental backup" newsletter, where we discuss
> exciting developments in non-redundant backup technology.
>
> This patchset enables the in-memory part of the incremental backup
> feature. There are two series on the mailing list now by Vladimir
> Sementsov-Ogievskiy that enable the migration and persistence of
> dirty bitmaps.
>
> V12 has a lot of tiny changes from V11, so I've lost a lot of R-B
> fields again. Sorry! For a fuller changelog, see the V11 cover letter
> in the qemu-devel archives.
>
> This series and most patches were originally by Fam Zheng.
>
> Highlights:
> - Renamed "node-ref" parameter to "node" in all QMP patches.
> (A rose by any other name? ...)
> - Fuller, more robust iotests.
> - Fixed progress percentages for incremental backup jobs.
> - This patch relies upon a blkdebug fix, included as patch #16,
> but also posted separately on-list.
>
> V12:
> - Changed authorship from Fam Zheng to John Snow on most patches
> - 02: Fix the error_setg leak in bdrv_dirty_bitmap_lookup
> - Fix error phrasing in bdrv_dirty_bitmap_lookup
> - Renamed "node-ref" to "node" for QMP commands.
> - 03: Granularity helper no longer requires a BDS argument.
> - 04: Return early if the second bitmap is empty.
> - 05: Renamed the 'enabled' field to 'disabled to emphasize what the
> default operating state is.
> - We now guard against bit sets or resets with the bitmap is
> disabled, making it a more pure "read only" mode.
> - Some documentation phrasing changes.
> - 06: Removed explicit "frozen" state in favor of an implicit one.
> A successor present implies a frozen state.
> - Updated all functions that target a single bitmap to use
> assertions that the bitmap they are trying to modify is not
> frozen/disabled.
> - Functions that target multiple bitmaps use only a conditional,
> and will silently skip disabled bitmaps.
> - thaw() function removed. It is implicitly handled in reclaim
> and abdicate.
> - Added check for return code of hbitmap_merge.
> - Functions now check against enable OR disable when in frozen
> state, for consistency and simplicity.
> - Add "frozen" state documentation to remove/enable/disable
> QMP commands.
> - 07: Some documentation for bdrv_set_dirty_iter.
> - Move function calls outside of assert()
> - Cleanup the unused successor if we do not start the backup
> - Version documentation added for dirty-bitmap to block-core.json
> - Job progress is now reported for incremental backup jobs.
> - 08: bdrv_dirty_bitmap_clear is now in its own patch, here.
> - bdrv_dirty_bitmap_clear no longer takes a BDS argument.
> - 09: Added a transaction for bdrv_dirty_bitmap_clear.
> - 10: Change 'enabled' field to 'disabled' field, to match
> above decision in patch 05.
> - 12: Removed extraneous BDS arguments from most bitmap functions.
> - 13-15: New set of iotests.
> - 16: blkdebug fix, already posted upstream.
> - 17: Final iotest, testing failure case.
>
> Fam Zheng (1):
> qapi: Add optional field "name" to block dirty bitmap
>
> John Snow (16):
> qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove
> block: Introduce bdrv_dirty_bitmap_granularity()
> hbitmap: add hbitmap_merge
> qmp: Add block-dirty-bitmap-enable and block-dirty-bitmap-disable
> block: Add bitmap successors
> qmp: Add support of "dirty-bitmap" sync mode for drive-backup
> qmp: add block-dirty-bitmap-clear
> qapi: Add transaction support to block-dirty-bitmap operations
> qmp: Add dirty bitmap status fields in query-block
> block: add BdrvDirtyBitmap documentation
> block: Ensure consistent bitmap function prototypes
> iotests: add invalid input incremental backup tests
> iotests: add simple incremental backup case
> iotests: add transactional incremental backup test
> blkdebug: fix "once" rule
> iotests: add incremental backup failure recovery test
>
> block.c | 225 ++++++++++++++++++++++++--
> block/backup.c | 149 ++++++++++++++---
> block/blkdebug.c | 6 +-
> block/mirror.c | 39 ++---
> blockdev.c | 366 +++++++++++++++++++++++++++++++++++++++++-
> hmp.c | 3 +-
> include/block/block.h | 33 +++-
> include/block/block_int.h | 2 +
> include/qemu/hbitmap.h | 11 ++
> migration/block.c | 9 +-
> qapi-schema.json | 6 +-
> qapi/block-core.json | 122 +++++++++++++-
> qmp-commands.hx | 92 ++++++++++-
> tests/qemu-iotests/026.out | 24 +--
> tests/qemu-iotests/112 | 305 +++++++++++++++++++++++++++++++++++
> tests/qemu-iotests/112.out | 5 +
> tests/qemu-iotests/group | 1 +
> tests/qemu-iotests/iotests.py | 24 ++-
> util/hbitmap.c | 32 ++++
> 19 files changed, 1341 insertions(+), 113 deletions(-)
> create mode 100644 tests/qemu-iotests/112
> create mode 100644 tests/qemu-iotests/112.out
>
For convenience:
https://github.com/jnsnow/qemu/commits/incremental-backup
^ permalink raw reply [flat|nested] 55+ messages in thread