* [Qemu-devel] [PATCH v5 01/21] docs: incremental backup documentation
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 15:06 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 02/21] qapi: Add optional field "name" to block dirty bitmap John Snow
` (19 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Reviewed-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
docs/bitmaps.md | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 311 insertions(+)
create mode 100644 docs/bitmaps.md
diff --git a/docs/bitmaps.md b/docs/bitmaps.md
new file mode 100644
index 0000000..ad8c33b
--- /dev/null
+++ b/docs/bitmaps.md
@@ -0,0 +1,311 @@
+# Dirty Bitmaps and Incremental Backup
+
+* Dirty Bitmaps are objects that track which data needs to be backed up for the
+ next incremental backup.
+
+* Dirty bitmaps can be created at any time and attached to any node
+ (not just complete drives.)
+
+## Dirty Bitmap Names
+
+* A dirty bitmap's name is unique to the node, but bitmaps attached to different
+nodes can share the same name.
+
+## Bitmap Modes
+
+* A Bitmap can be "frozen," which means that it is currently in-use by a backup
+operation and cannot be deleted, renamed, written to, reset,
+etc.
+
+## Basic QMP Usage
+
+### Supported Commands ###
+
+* block-dirty-bitmap-add
+* block-dirty-bitmap-remove
+* block-dirty-bitmap-clear
+
+### Creation
+
+* To create a new bitmap, enabled, on the drive with id=drive0:
+
+```json
+{ "execute": "block-dirty-bitmap-add",
+ "arguments": {
+ "node": "drive0",
+ "name": "bitmap0"
+ }
+}
+```
+
+* This bitmap will have a default granularity that matches the cluster size of
+its associated drive, if available, clamped to between [4KiB, 64KiB].
+The current default for qcow2 is 64KiB.
+
+* To create a new bitmap that tracks changes in 32KiB segments:
+
+```json
+{ "execute": "block-dirty-bitmap-add",
+ "arguments": {
+ "node": "drive0",
+ "name": "bitmap0",
+ "granularity": 32768
+ }
+}
+```
+
+### Deletion
+
+* Can be performed on a disabled bitmap, but not a frozen one.
+
+* Because bitmaps are only unique to the node to which they are attached,
+you must specify the node/drive name here, too.
+
+```json
+{ "execute": "block-dirty-bitmap-remove",
+ "arguments": {
+ "node": "drive0",
+ "name": "bitmap0"
+ }
+}
+```
+
+### Resetting
+
+* Resetting a bitmap will clear all information it holds.
+* An incremental backup created from an empty bitmap will copy no data,
+as if nothing has changed.
+
+```json
+{ "execute": "block-dirty-bitmap-clear",
+ "arguments": {
+ "node": "drive0",
+ "name": "bitmap0"
+ }
+}
+```
+
+## Transactions (Not yet implemented)
+
+* Transactional commands are forthcoming in a future version,
+ and are not yet available for use. This section serves as
+ documentation of intent for their design and usage.
+
+### Justification
+Bitmaps can be safely modified when the VM is paused or halted by using
+the basic QMP commands. For instance, you might perform the following actions:
+
+1. Boot the VM in a paused state.
+2. Create a full drive backup of drive0.
+3. Create a new bitmap attached to drive0.
+4. Resume execution of the VM.
+5. Incremental backups are ready to be created.
+
+At this point, the bitmap and drive backup would be correctly in sync,
+and incremental backups made from this point forward would be correctly aligned
+to the full drive backup.
+
+This is not particularly useful if we decide we want to start incremental
+backups after the VM has been running for a while, for which we will need to
+perform actions such as the following:
+
+1. Boot the VM and begin execution.
+2. Using a single transaction, perform the following operations:
+ * Create bitmap0.
+ * Create a full drive backup of drive0.
+3. Incremental backups are now ready to be created.
+
+### Supported Bitmap Transactions
+
+* block-dirty-bitmap-add
+* block-dirty-bitmap-clear
+
+The usages are identical to their respective QMP commands, but see below
+for examples.
+
+### Example: New Incremental Backup
+
+As outlined in the justification, perhaps we want to create a new incremental
+backup chain attached to a drive.
+
+```json
+{ "execute": "transaction",
+ "arguments": {
+ "actions": [
+ {"type": "block-dirty-bitmap-add",
+ "data": {"node": "drive0", "name": "bitmap0"} },
+ {"type": "drive-backup",
+ "data": {"device": "drive0", "target": "/path/to/full_backup.img",
+ "sync": "full", "format": "qcow2"} }
+ ]
+ }
+}
+```
+
+### Example: New Incremental Backup Anchor Point
+
+Maybe we just want to create a new full backup with an existing bitmap and
+want to reset the bitmap to track the new chain.
+
+```json
+{ "execute": "transaction",
+ "arguments": {
+ "actions": [
+ {"type": "block-dirty-bitmap-clear",
+ "data": {"node": "drive0", "name": "bitmap0"} },
+ {"type": "drive-backup",
+ "data": {"device": "drive0", "target": "/path/to/new_full_backup.img",
+ "sync": "full", "format": "qcow2"} }
+ ]
+ }
+}
+```
+
+## Incremental Backups
+
+The star of the show.
+
+**Nota Bene!** Only incremental backups of entire drives are supported for now.
+So despite the fact that you can attach a bitmap to any arbitrary node, they are
+only currently useful when attached to the root node. This is because
+drive-backup only supports drives/devices instead of arbitrary nodes.
+
+### Example: First Incremental Backup
+
+1. Create a full backup and sync it to the dirty bitmap, as in the transactional
+examples above; or with the VM offline, manually create a full copy and then
+create a new bitmap before the VM begins execution.
+
+ * Let's assume the full backup is named 'full_backup.img'.
+ * Let's assume the bitmap you created is 'bitmap0' attached to 'drive0'.
+
+2. Create a destination image for the incremental backup that utilizes the
+full backup as a backing image.
+
+ * Let's assume it is named 'incremental.0.img'.
+
+ ```sh
+ # qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2
+ ```
+
+3. Issue the incremental backup command:
+
+ ```json
+ { "execute": "drive-backup",
+ "arguments": {
+ "device": "drive0",
+ "bitmap": "bitmap0",
+ "target": "incremental.0.img",
+ "format": "qcow2",
+ "sync": "dirty-bitmap",
+ "mode": "existing"
+ }
+ }
+ ```
+
+### Example: Second Incremental Backup
+
+1. Create a new destination image for the incremental backup that points to the
+ previous one, e.g.: 'incremental.1.img'
+
+ ```sh
+ # qemu-img create -f qcow2 incremental.1.img -b incremental.0.img -F qcow2
+ ```
+
+2. Issue a new incremental backup command. The only difference here is that we
+ have changed the target image below.
+
+ ```json
+ { "execute": "drive-backup",
+ "arguments": {
+ "device": "drive0",
+ "bitmap": "bitmap0",
+ "target": "incremental.1.img",
+ "format": "qcow2",
+ "sync": "dirty-bitmap",
+ "mode": "existing"
+ }
+ }
+ ```
+
+## Errors
+
+* In the event of an error that occurs after a backup job is successfully
+ launched, either by a direct QMP command or a QMP transaction, the user
+ will receive a BLOCK_JOB_COMPLETE event with a failure message, accompanied
+ by a BLOCK_JOB_ERROR event.
+
+* In the case of an event being cancelled, the user will receive a
+ BLOCK_JOB_CANCELLED event instead of a pair of COMPLETE and ERROR events.
+
+* In either case, the incremental backup data contained within the bitmap is
+ safely rolled back, and the data within the bitmap is not lost. The image
+ file created for the failed attempt can be safely deleted.
+
+* Once the underlying problem is fixed (e.g. more storage space is freed up),
+ you can simply retry the incremental backup command with the same bitmap.
+
+### Example
+
+1. Create a target image:
+
+ ```sh
+ # qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2
+ ```
+
+2. Attempt to create an incremental backup via QMP:
+
+ ```json
+ { "execute": "drive-backup",
+ "arguments": {
+ "device": "drive0",
+ "bitmap": "bitmap0",
+ "target": "incremental.0.img",
+ "format": "qcow2",
+ "sync": "dirty-bitmap",
+ "mode": "existing"
+ }
+ }
+ ```
+
+3. Receive an event notifying us of failure:
+
+ ```json
+ { "timestamp": { "seconds": 1424709442, "microseconds": 844524 },
+ "data": { "speed": 0, "offset": 0, "len": 67108864,
+ "error": "No space left on device",
+ "device": "drive1", "type": "backup" },
+ "event": "BLOCK_JOB_COMPLETED" }
+ ```
+
+4. Delete the failed incremental, and re-create the image.
+
+ ```sh
+ # rm incremental.0.img
+ # qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2
+ ```
+
+5. Retry the command after fixing the underlaying problem,
+ such as freeing up space on the backup volume:
+
+ ```json
+ { "execute": "drive-backup",
+ "arguments": {
+ "device": "drive0",
+ "bitmap": "bitmap0",
+ "target": "incremental.0.img",
+ "format": "qcow2",
+ "sync": "dirty-bitmap",
+ "mode": "existing"
+ }
+ }
+ ```
+
+6. Receive confirmation that the job completed successfully:
+
+ ```json
+ { "timestamp": { "seconds": 1424709668, "microseconds": 526525 },
+ "data": { "device": "drive1", "type": "backup",
+ "speed": 0, "len": 67108864, "offset": 67108864},
+ "event": "BLOCK_JOB_COMPLETED" }
+ ```
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 01/21] docs: incremental backup documentation
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 01/21] docs: incremental backup documentation John Snow
@ 2015-04-17 15:06 ` Eric Blake
2015-04-17 15:50 ` John Snow
0 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2015-04-17 15:06 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 1326 bytes --]
On 04/08/2015 04:19 PM, John Snow wrote:
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> docs/bitmaps.md | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 311 insertions(+)
> create mode 100644 docs/bitmaps.md
>
> diff --git a/docs/bitmaps.md b/docs/bitmaps.md
> new file mode 100644
> index 0000000..ad8c33b
> --- /dev/null
> +++ b/docs/bitmaps.md
> @@ -0,0 +1,311 @@
> +# Dirty Bitmaps and Incremental Backup
> +
Still might be nice to list explicit copyright/license instead of
relying on implicit top-level GPLv2+, but I won't insist.
> +### Deletion
> +
> +* Can be performed on a disabled bitmap, but not a frozen one.
Do you still have a notion of disabled bitmaps? Earlier, in '## Bitmap
Modes', you only document 'frozen' (as opposed to the default unnamed
state).
> +
> +## Transactions (Not yet implemented)
I'm assuming that "[PATCH v2 00/11] block: incremental backup
transactions" is incomplete, because it forgot to clean this up as part
of adding transaction support.
> +
> +5. Retry the command after fixing the underlaying problem,
s/underlaying/underlying/
--
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] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 01/21] docs: incremental backup documentation
2015-04-17 15:06 ` Eric Blake
@ 2015-04-17 15:50 ` John Snow
2015-04-17 16:36 ` Eric Blake
0 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-17 15:50 UTC (permalink / raw)
To: Eric Blake, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
On 04/17/2015 11:06 AM, Eric Blake wrote:
> On 04/08/2015 04:19 PM, John Snow wrote:
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> docs/bitmaps.md | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 311 insertions(+)
>> create mode 100644 docs/bitmaps.md
>>
>> diff --git a/docs/bitmaps.md b/docs/bitmaps.md
>> new file mode 100644
>> index 0000000..ad8c33b
>> --- /dev/null
>> +++ b/docs/bitmaps.md
>> @@ -0,0 +1,311 @@
>> +# Dirty Bitmaps and Incremental Backup
>> +
>
> Still might be nice to list explicit copyright/license instead of
> relying on implicit top-level GPLv2+, but I won't insist.
>
I think I would rather not clutter up the document itself, if that
remains suitable. I don't mind those declarations in source code, but
for a document like this, it seems weird to have it in the preamble.
I can attach a license to the footer, if that's suitable?
>
>> +### Deletion
>> +
>> +* Can be performed on a disabled bitmap, but not a frozen one.
>
> Do you still have a notion of disabled bitmaps? Earlier, in '## Bitmap
> Modes', you only document 'frozen' (as opposed to the default unnamed
> state).
>
We do internally. It's not likely to come up from a user's perspective,
but we do intend to disable the bitmap during e.g. migration, boot, etc.
I did pull the "disabled" bit out because it's not a necessary detail yet.
I'll tidy this up and reintroduce the language alongside the patch that
may expose the user to witnessing a disabled bitmap.
>
>> +
>> +## Transactions (Not yet implemented)
>
> I'm assuming that "[PATCH v2 00/11] block: incremental backup
> transactions" is incomplete, because it forgot to clean this up as part
> of adding transaction support.
>
Fixed in my local copy, yes.
>
>> +
>> +5. Retry the command after fixing the underlaying problem,
>
> s/underlaying/underlying/
>
:(
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 01/21] docs: incremental backup documentation
2015-04-17 15:50 ` John Snow
@ 2015-04-17 16:36 ` Eric Blake
0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2015-04-17 16:36 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 1624 bytes --]
On 04/17/2015 09:50 AM, John Snow wrote:
>
>
> On 04/17/2015 11:06 AM, Eric Blake wrote:
>> On 04/08/2015 04:19 PM, John Snow wrote:
>>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>> docs/bitmaps.md | 311
>>> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>> 1 file changed, 311 insertions(+)
>>> create mode 100644 docs/bitmaps.md
>>>
>>> diff --git a/docs/bitmaps.md b/docs/bitmaps.md
>>> new file mode 100644
>>> index 0000000..ad8c33b
>>> --- /dev/null
>>> +++ b/docs/bitmaps.md
>>> @@ -0,0 +1,311 @@
>>> +# Dirty Bitmaps and Incremental Backup
>>> +
>>
>> Still might be nice to list explicit copyright/license instead of
>> relying on implicit top-level GPLv2+, but I won't insist.
>>
>
> I think I would rather not clutter up the document itself, if that
> remains suitable. I don't mind those declarations in source code, but
> for a document like this, it seems weird to have it in the preamble.
>
> I can attach a license to the footer, if that's suitable?
A footer is fine by me (I don't care where it lives in the document,
only that it can be found). As this is a markup document, you should
also consider whether a copyright should be passed on through to the
rendered document, or whether it is fine for just the markup source as a
comment that gets stripped during rendering (I would probably include it
in the rendered document, but am not strongly opposed if you don't agree).
--
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] 48+ messages in thread
* [Qemu-devel] [PATCH v5 02/21] qapi: Add optional field "name" to block dirty bitmap
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 01/21] docs: incremental backup documentation John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 03/21] qmp: Ensure consistent granularity type John Snow
` (18 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
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>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@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 f2f8ae7..0da35ae 100644
--- a/block.c
+++ b/block.c
@@ -53,6 +53,7 @@
struct BdrvDirtyBitmap {
HBitmap *bitmap;
+ char *name;
QLIST_ENTRY(BdrvDirtyBitmap) list;
};
@@ -5435,7 +5436,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;
@@ -5443,6 +5465,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);
@@ -5453,6 +5479,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;
}
@@ -5464,6 +5491,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;
}
@@ -5482,6 +5510,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 4c57d63..05e32f9 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -449,8 +449,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 085c0fa..02a7d26 100644
--- a/migration/block.c
+++ b/migration/block.c
@@ -320,7 +320,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 7873084..c10e664 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -330,6 +330,8 @@
#
# Block dirty bitmap information.
#
+# @name: #optional the name of the dirty bitmap (Since 2.4)
+#
# @count: number of dirty bytes according to the dirty bitmap
#
# @granularity: granularity of the dirty bitmap in bytes (since 1.4)
@@ -337,7 +339,7 @@
# Since: 1.3
##
{ 'type': 'BlockDirtyInfo',
- 'data': {'count': 'int', 'granularity': 'int'} }
+ 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int'} }
##
# @BlockInfo:
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 03/21] qmp: Ensure consistent granularity type
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 01/21] docs: incremental backup documentation John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 02/21] qapi: Add optional field "name" to block dirty bitmap John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 04/21] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove John Snow
` (17 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
We treat this field with a variety of different types everywhere
in the code. Now it's just uint32_t.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 11 ++++++-----
block/mirror.c | 4 ++--
include/block/block.h | 2 +-
include/block/block_int.h | 2 +-
qapi/block-core.json | 2 +-
5 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/block.c b/block.c
index 0da35ae..2b609e7 100644
--- a/block.c
+++ b/block.c
@@ -5456,12 +5456,13 @@ void bdrv_dirty_bitmap_make_anon(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
}
BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
- int granularity,
+ uint32_t granularity,
const char *name,
Error **errp)
{
int64_t bitmap_size;
BdrvDirtyBitmap *bitmap;
+ uint32_t sector_granularity;
assert((granularity & (granularity - 1)) == 0);
@@ -5469,8 +5470,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");
@@ -5478,7 +5479,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;
@@ -5509,7 +5510,7 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs)
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));
+ ((uint32_t) BDRV_SECTOR_SIZE << hbitmap_granularity(bm->bitmap));
info->has_name = !!bm->name;
info->name = g_strdup(bm->name);
entry->value = info;
diff --git a/block/mirror.c b/block/mirror.c
index f073ad7..f8aac33 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -656,7 +656,7 @@ static const BlockJobDriver commit_active_job_driver = {
static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
const char *replaces,
- int64_t speed, int64_t granularity,
+ int64_t speed, uint32_t granularity,
int64_t buf_size,
BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
@@ -717,7 +717,7 @@ static void mirror_start_job(BlockDriverState *bs, BlockDriverState *target,
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
const char *replaces,
- int64_t speed, int64_t granularity, int64_t buf_size,
+ int64_t speed, uint32_t granularity, int64_t buf_size,
MirrorSyncMode mode, BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
BlockCompletionFunc *cb,
diff --git a/include/block/block.h b/include/block/block.h
index 05e32f9..5235086 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -450,7 +450,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,
+ uint32_t granularity,
const char *name,
Error **errp);
BdrvDirtyBitmap *bdrv_find_dirty_bitmap(BlockDriverState *bs,
diff --git a/include/block/block_int.h b/include/block/block_int.h
index dccb092..fb9e100 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -590,7 +590,7 @@ void commit_active_start(BlockDriverState *bs, BlockDriverState *base,
*/
void mirror_start(BlockDriverState *bs, BlockDriverState *target,
const char *replaces,
- int64_t speed, int64_t granularity, int64_t buf_size,
+ int64_t speed, uint32_t granularity, int64_t buf_size,
MirrorSyncMode mode, BlockdevOnError on_source_error,
BlockdevOnError on_target_error,
BlockCompletionFunc *cb,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index c10e664..1bb23be 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -339,7 +339,7 @@
# Since: 1.3
##
{ 'type': 'BlockDirtyInfo',
- 'data': {'*name': 'str', 'count': 'int', 'granularity': 'int'} }
+ 'data': {'*name': 'str', 'count': 'int', 'granularity': 'uint32'} }
##
# @BlockInfo:
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 04/21] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (2 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 03/21] qmp: Ensure consistent granularity type John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 14:54 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 05/21] block: Introduce bdrv_dirty_bitmap_granularity() John Snow
` (16 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
The new command pair is added to manage a 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: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 20 +++++++++
block/mirror.c | 10 +----
blockdev.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++
include/block/block.h | 1 +
qapi/block-core.json | 55 ++++++++++++++++++++++++
qmp-commands.hx | 56 ++++++++++++++++++++++++
6 files changed, 250 insertions(+), 9 deletions(-)
diff --git a/block.c b/block.c
index 2b609e7..ebea54d 100644
--- a/block.c
+++ b/block.c
@@ -5530,6 +5530,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.
+ */
+uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs)
+{
+ BlockDriverInfo bdi;
+ uint32_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 f8aac33..1cb700e 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 fbb3a79..7d71b81 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1164,6 +1164,68 @@ out_aio_context:
return NULL;
}
+/**
+ * block_dirty_bitmap_lookup:
+ * Return a dirty bitmap (if present), after validating
+ * the node reference and bitmap names.
+ *
+ * @node: The name of the BDS node to search for bitmaps
+ * @name: The name of the bitmap to search for
+ * @pbs: Output pointer for BDS lookup, if desired. Can be NULL.
+ * @paio: Output pointer for aio_context acquisition, if desired. Can be NULL.
+ * @errp: Output pointer for error information. Can be NULL.
+ *
+ * @return: A bitmap object on success, or NULL on failure.
+ */
+static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,
+ const char *name,
+ BlockDriverState **pbs,
+ AioContext **paio,
+ Error **errp)
+{
+ BlockDriverState *bs;
+ BdrvDirtyBitmap *bitmap;
+ AioContext *aio_context;
+
+ 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;
+ }
+
+ aio_context = bdrv_get_aio_context(bs);
+ aio_context_acquire(aio_context);
+
+ bitmap = bdrv_find_dirty_bitmap(bs, name);
+ if (!bitmap) {
+ error_setg(errp, "Dirty bitmap '%s' not found", name);
+ goto fail;
+ }
+
+ if (pbs) {
+ *pbs = bs;
+ }
+ if (paio) {
+ *paio = aio_context;
+ } else {
+ aio_context_release(aio_context);
+ }
+
+ return bitmap;
+
+ fail:
+ aio_context_release(aio_context);
+ return NULL;
+}
+
/* New and old BlockDriverState structs for atomic group operations */
typedef struct BlkTransactionState BlkTransactionState;
@@ -1953,6 +2015,61 @@ 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, uint32_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, &aio_context, errp);
+ if (!bitmap || !bs) {
+ return;
+ }
+
+ bdrv_dirty_bitmap_make_anon(bs, bitmap);
+ bdrv_release_dirty_bitmap(bs, bitmap);
+
+ aio_context_release(aio_context);
+}
+
int hmp_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 5235086..0f014a3 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -458,6 +458,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);
+uint32_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 1bb23be..1c03db1 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -960,6 +960,61 @@
'*on-target-error': 'BlockdevOnError' } }
##
+# @BlockDirtyBitmap
+#
+# @node: name of device/node which the bitmap is tracking
+#
+# @name: name of the dirty bitmap
+#
+# Since 2.4
+##
+{ '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.4
+##
+{ 'type': 'BlockDirtyBitmapAdd',
+ 'data': { 'node': 'str', 'name': 'str', '*granularity': 'uint32' } }
+
+##
+# @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.4
+##
+{ '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.4
+##
+{ '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 3a42ad0..b6bd455 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1270,6 +1270,62 @@ 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,
+ },
+
+SQMP
+
+block-dirty-bitmap-add
+----------------------
+Since 2.4
+
+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": {} }
+
+EQMP
+
+ {
+ .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-remove
+-------------------------
+Since 2.4
+
+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,
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 04/21] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 04/21] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove John Snow
@ 2015-04-17 14:54 ` Eric Blake
0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2015-04-17 14:54 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 1811 bytes --]
On 04/08/2015 04:19 PM, John Snow wrote:
> The new command pair is added to manage a 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: John Snow <jsnow@redhat.com>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
> block.c | 20 +++++++++
> block/mirror.c | 10 +----
> blockdev.c | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++
> include/block/block.h | 1 +
> qapi/block-core.json | 55 ++++++++++++++++++++++++
> qmp-commands.hx | 56 ++++++++++++++++++++++++
> 6 files changed, 250 insertions(+), 9 deletions(-)
>
Reviewed-by: Eric Blake <eblake@redhat.com>
--
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] 48+ messages in thread
* [Qemu-devel] [PATCH v5 05/21] block: Introduce bdrv_dirty_bitmap_granularity()
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (3 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 04/21] qmp: Add block-dirty-bitmap-add and block-dirty-bitmap-remove John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 06/21] hbitmap: cache array lengths John Snow
` (15 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
This returns the granularity (in bytes) of dirty bitmap,
which matches the QMP interface and the existing query
interface.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 8 ++++++--
include/block/block.h | 1 +
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/block.c b/block.c
index ebea54d..41c5a67 100644
--- a/block.c
+++ b/block.c
@@ -5509,8 +5509,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 =
- ((uint32_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;
@@ -5550,6 +5549,11 @@ uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs)
return granularity;
}
+uint32_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 0f014a3..493b7c5 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -459,6 +459,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);
uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
+uint32_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);
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 06/21] hbitmap: cache array lengths
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (4 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 05/21] block: Introduce bdrv_dirty_bitmap_granularity() John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 15:18 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 07/21] hbitmap: add hbitmap_merge John Snow
` (14 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
As a convenience: between incremental backups, bitmap migrations
and bitmap persistence we seem to need to recalculate these a lot.
Because the lengths are a little bit-twiddly, let's just solidly
cache them and be done with it.
Reviewed-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
util/hbitmap.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/util/hbitmap.c b/util/hbitmap.c
index ab13971..5b78613 100644
--- a/util/hbitmap.c
+++ b/util/hbitmap.c
@@ -90,6 +90,9 @@ struct HBitmap {
* bitmap will still allocate HBITMAP_LEVELS arrays.
*/
unsigned long *levels[HBITMAP_LEVELS];
+
+ /* The length of each levels[] array. */
+ uint64_t sizes[HBITMAP_LEVELS];
};
/* Advance hbi to the next nonzero word and return it. hbi->pos
@@ -384,6 +387,7 @@ HBitmap *hbitmap_alloc(uint64_t size, int granularity)
hb->granularity = granularity;
for (i = HBITMAP_LEVELS; i-- > 0; ) {
size = MAX((size + BITS_PER_LONG - 1) >> BITS_PER_LEVEL, 1);
+ hb->sizes[i] = size;
hb->levels[i] = g_new0(unsigned long, size);
}
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 06/21] hbitmap: cache array lengths
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 06/21] hbitmap: cache array lengths John Snow
@ 2015-04-17 15:18 ` Eric Blake
0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2015-04-17 15:18 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 617 bytes --]
On 04/08/2015 04:19 PM, John Snow wrote:
> As a convenience: between incremental backups, bitmap migrations
> and bitmap persistence we seem to need to recalculate these a lot.
>
> Because the lengths are a little bit-twiddly, let's just solidly
> cache them and be done with it.
>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> util/hbitmap.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
Reviewed-by: Eric Blake <eblake@redhat.com>
--
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] 48+ messages in thread
* [Qemu-devel] [PATCH v5 07/21] hbitmap: add hbitmap_merge
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (5 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 06/21] hbitmap: cache array lengths John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 15:23 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 08/21] block: Add bitmap disabled status John Snow
` (13 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
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>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
include/qemu/hbitmap.h | 11 +++++++++++
util/hbitmap.c | 29 +++++++++++++++++++++++++++++
2 files changed, 40 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 5b78613..ba11fd3 100644
--- a/util/hbitmap.c
+++ b/util/hbitmap.c
@@ -399,3 +399,32 @@ 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;
+
+ 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.
+ */
+ for (i = HBITMAP_LEVELS - 1; i >= 0; i--) {
+ for (j = 0; j < a->sizes[i]; j++) {
+ a->levels[i][j] |= b->levels[i][j];
+ }
+ }
+
+ return true;
+}
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 07/21] hbitmap: add hbitmap_merge
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 07/21] hbitmap: add hbitmap_merge John Snow
@ 2015-04-17 15:23 ` Eric Blake
2015-04-17 21:30 ` John Snow
0 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2015-04-17 15:23 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 2232 bytes --]
On 04/08/2015 04:19 PM, 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>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
> /**
> + * 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);
No mention of what the return value means.
> +bool hbitmap_merge(HBitmap *a, const HBitmap *b)
> +{
> + int i, j;
> +
> + 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.
> + */
> + for (i = HBITMAP_LEVELS - 1; i >= 0; i--) {
> + for (j = 0; j < a->sizes[i]; j++) {
j is 'int', but a->sizes[i] is uint64_t. If we can ever have a bitmap
large enough that its size exceeds 2G at the largest level, then we have
an inf-loop due to overflow problem.
I'd feel safer if you make j be uint64_t here; but it might also be
possible to fix 6/21 to store sizes in a smaller data type along with
appropriate assertions that our bitmaps are never big enough to need a
level with more than 2G size.
--
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] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 07/21] hbitmap: add hbitmap_merge
2015-04-17 15:23 ` Eric Blake
@ 2015-04-17 21:30 ` John Snow
0 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-17 21:30 UTC (permalink / raw)
To: Eric Blake, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
On 04/17/2015 11:23 AM, Eric Blake wrote:
> On 04/08/2015 04:19 PM, 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>
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
>> ---
>
>> /**
>> + * 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);
>
> No mention of what the return value means.
>
>
>> +bool hbitmap_merge(HBitmap *a, const HBitmap *b)
>> +{
>> + int i, j;
>> +
>> + 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.
>> + */
>> + for (i = HBITMAP_LEVELS - 1; i >= 0; i--) {
>> + for (j = 0; j < a->sizes[i]; j++) {
>
> j is 'int', but a->sizes[i] is uint64_t. If we can ever have a bitmap
> large enough that its size exceeds 2G at the largest level, then we have
> an inf-loop due to overflow problem.
>
> I'd feel safer if you make j be uint64_t here; but it might also be
> possible to fix 6/21 to store sizes in a smaller data type along with
> appropriate assertions that our bitmaps are never big enough to need a
> level with more than 2G size.
>
Unfortunately, our bitmaps may indeed be colossal. uint32_t would
suffice for 32bit, but 64bit bitmaps tolerate up to 2^41 bits, which is
still a ULL size of 2^35, so just a little bit too big for us.
We will never hit this in practice (for this usage, anyway) but I'd
rather not nerf the base class unnecessarily, so I'll just expand out
the index here.
Thanks for the good catch.
--js
^ permalink raw reply [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 08/21] block: Add bitmap disabled status
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (6 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 07/21] hbitmap: add hbitmap_merge John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 09/21] block: Add bitmap successors John Snow
` (12 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Add a status indicating the enabled/disabled state of the bitmap.
A bitmap is by default enabled, but you can lock the bitmap into
a read-only state by setting disabled = true.
A previous version of this patch added a QMP interface for changing
the state of the bitmap, but it has since been removed for now until
a use case emerges where this state must be revealed to the user.
The disabled state WILL be used internally for bitmap migration and
bitmap persistence.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 25 +++++++++++++++++++++++++
include/block/block.h | 3 +++
2 files changed, 28 insertions(+)
diff --git a/block.c b/block.c
index 41c5a67..db742a9 100644
--- a/block.c
+++ b/block.c
@@ -54,6 +54,7 @@
struct BdrvDirtyBitmap {
HBitmap *bitmap;
char *name;
+ bool disabled;
QLIST_ENTRY(BdrvDirtyBitmap) list;
};
@@ -5481,10 +5482,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;
@@ -5499,6 +5506,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;
@@ -5563,12 +5580,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);
}
@@ -5577,6 +5596,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);
}
}
@@ -5586,6 +5608,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/include/block/block.h b/include/block/block.h
index 493b7c5..029a8a7 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -457,9 +457,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);
uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
uint32_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);
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 09/21] block: Add bitmap successors
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (7 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 08/21] block: Add bitmap disabled status John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 22:43 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
` (11 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
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>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++-
blockdev.c | 7 ++++
include/block/block.h | 10 +++++
qapi/block-core.json | 1 +
4 files changed, 121 insertions(+), 1 deletion(-)
diff --git a/block.c b/block.c
index db742a9..9d30379 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;
@@ -5452,6 +5461,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;
}
@@ -5487,9 +5497,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");
+ 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");
+ return NULL;
+ }
+
+ if (!hbitmap_merge(parent->bitmap, successor->bitmap)) {
+ error_setg(errp, "Merging of parent and successor bitmap failed");
+ return NULL;
+ }
+ bdrv_release_dirty_bitmap(bs, successor);
+ parent->successor = NULL;
+
+ return parent;
}
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
@@ -5497,6 +5596,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);
@@ -5508,11 +5608,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 7d71b81..373d19a 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2064,9 +2064,16 @@ void qmp_block_dirty_bitmap_remove(const char *node, const char *name,
return;
}
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ error_setg(errp,
+ "Bitmap '%s' is currently frozen and cannot be removed",
+ name);
+ goto out;
+ }
bdrv_dirty_bitmap_make_anon(bs, bitmap);
bdrv_release_dirty_bitmap(bs, bitmap);
+ out:
aio_context_release(aio_context);
}
diff --git a/include/block/block.h b/include/block/block.h
index 029a8a7..b02c670 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -453,6 +453,15 @@ BdrvDirtyBitmap *bdrv_create_dirty_bitmap(BlockDriverState *bs,
uint32_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);
@@ -463,6 +472,7 @@ BlockDirtyInfoList *bdrv_query_dirty_bitmaps(BlockDriverState *bs);
uint32_t bdrv_get_default_bitmap_granularity(BlockDriverState *bs);
uint32_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 1c03db1..710be68 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1008,6 +1008,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.4
##
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 09/21] block: Add bitmap successors
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 09/21] block: Add bitmap successors John Snow
@ 2015-04-17 22:43 ` Eric Blake
2015-04-17 22:56 ` John Snow
0 siblings, 1 reply; 48+ messages in thread
From: Eric Blake @ 2015-04-17 22:43 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 3287 bytes --]
On 04/08/2015 04:19 PM, 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.
Is this last paragraph stale now?
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
> block.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++-
> blockdev.c | 7 ++++
> include/block/block.h | 10 +++++
> qapi/block-core.json | 1 +
> 4 files changed, 121 insertions(+), 1 deletion(-)
>
> +/**
> + * 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;
'successor is false' reads awkwardly given that it is not a bool; maybe
'successor is NULL' is better?
>
> +bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap)
> +{
> + return bitmap->successor;
Good thing C99 requires conversion of pointer to bool to work :)
> +
> +/**
> + * For a bitmap with a successor, yield our name to the successor,
> + * Delete the old bitmap, and return a handle to the new bitmap.
Sentences with capitals after comma, Even with a line break, Look weird.
(s/Delete/delete/)
> +
> +/**
> + * In cases of failure where we can no longer safely delete the parent,
> + * We may wish to re-join the parent and child/successor.
and again (s/We/we/)
Grammar fixes are minor, so:
Reviewed-by: Eric Blake <eblake@redhat.com>
--
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] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 09/21] block: Add bitmap successors
2015-04-17 22:43 ` Eric Blake
@ 2015-04-17 22:56 ` John Snow
0 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-17 22:56 UTC (permalink / raw)
To: Eric Blake, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
On 04/17/2015 06:43 PM, Eric Blake wrote:
> On 04/08/2015 04:19 PM, 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.
>
> Is this last paragraph stale now?
>
Yes, thank you. Those transactions no longer exist.
The implementations, however, have *assertions* that protect frozen
bitmaps and still exist. Bitmaps can still be enabled/disabled as an
internal concept, and it is still invalid to try to enable/disable one
that is frozen.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> Reviewed-by: Max Reitz <mreitz@redhat.com>
>> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
>> ---
>> block.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++-
>> blockdev.c | 7 ++++
>> include/block/block.h | 10 +++++
>> qapi/block-core.json | 1 +
>> 4 files changed, 121 insertions(+), 1 deletion(-)
>>
>
>> +/**
>> + * 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;
>
> 'successor is false' reads awkwardly given that it is not a bool; maybe
> 'successor is NULL' is better?
>
That is better.
>>
>> +bool bdrv_dirty_bitmap_frozen(BdrvDirtyBitmap *bitmap)
>> +{
>> + return bitmap->successor;
>
> Good thing C99 requires conversion of pointer to bool to work :)
>
Is there a case to be made for explicit NULL checks?
>
>> +
>> +/**
>> + * For a bitmap with a successor, yield our name to the successor,
>> + * Delete the old bitmap, and return a handle to the new bitmap.
>
> Sentences with capitals after comma, Even with a line break, Look weird.
> (s/Delete/delete/)
>
You Are Right, Sorry About That.
>
>> +
>> +/**
>> + * In cases of failure where we can no longer safely delete the parent,
>> + * We may wish to re-join the parent and child/successor.
>
> and again (s/We/we/)
>
It is an /extra/ royal 'we.'
> Grammar fixes are minor, so:
> Reviewed-by: Eric Blake <eblake@redhat.com>
>
^ permalink raw reply [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (8 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 09/21] block: Add bitmap successors John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 13:17 ` Max Reitz
2015-04-17 22:51 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 11/21] qmp: add block-dirty-bitmap-clear John Snow
` (10 subsequent siblings)
20 siblings, 2 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
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 | 156 +++++++++++++++++++++++++++++++++++++++-------
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, 180 insertions(+), 33 deletions(-)
diff --git a/block.c b/block.c
index 9d30379..2367311 100644
--- a/block.c
+++ b/block.c
@@ -5717,6 +5717,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..8513917 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,92 @@ 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 int coroutine_fn backup_run_incremental(BackupBlockJob *job)
+{
+ bool error_is_read;
+ int ret = 0;
+ int clusters_per_iter;
+ uint32_t granularity;
+ int64_t sector;
+ int64_t cluster;
+ int64_t end;
+ int64_t last_cluster = -1;
+ BlockDriverState *bs = job->common.bs;
+ HBitmapIter hbi;
+
+ granularity = bdrv_dirty_bitmap_granularity(job->sync_bitmap);
+ clusters_per_iter = MAX((granularity / BACKUP_CLUSTER_SIZE), 1);
+ bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
+
+ /* Find the next dirty sector(s) */
+ while ((sector = hbitmap_iter_next(&hbi)) != -1) {
+ cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
+
+ /* Fake progress updates for any clusters we skipped */
+ if (cluster != last_cluster + 1) {
+ job->common.offset += ((cluster - last_cluster - 1) *
+ BACKUP_CLUSTER_SIZE);
+ }
+
+ for (end = cluster + clusters_per_iter; cluster < end; cluster++) {
+ if (yield_and_check(job)) {
+ return ret;
+ }
+
+ 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) {
+ return ret;
+ }
+ } while (ret < 0);
+ }
+
+ /* If the bitmap granularity is smaller than the backup granularity,
+ * we need to advance the iterator pointer to the next cluster. */
+ if (granularity < BACKUP_CLUSTER_SIZE) {
+ bdrv_set_dirty_iter(&hbi, cluster * BACKUP_SECTORS_PER_CLUSTER);
+ }
+
+ last_cluster = cluster - 1;
+ }
+
+ /* Play some final catchup with the progress meter */
+ end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
+ if (last_cluster + 1 < end) {
+ job->common.offset += ((end - last_cluster - 1) * BACKUP_CLUSTER_SIZE);
+ }
+
+ return ret;
+}
+
static void coroutine_fn backup_run(void *opaque)
{
BackupBlockJob *job = opaque;
@@ -259,8 +347,7 @@ static void coroutine_fn backup_run(void *opaque)
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 +365,13 @@ 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) {
+ ret = backup_run_incremental(job);
} 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;
}
@@ -357,6 +429,18 @@ static void coroutine_fn backup_run(void *opaque)
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);
+ } 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 +453,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 +497,36 @@ 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 +535,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 1cb700e..f89eccf 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 373d19a..90ba5b6 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -1584,6 +1584,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);
@@ -2394,6 +2395,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)
@@ -2402,6 +2404,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;
@@ -2501,7 +2504,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);
@@ -2562,8 +2574,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 f31ae27..d85d913 100644
--- a/hmp.c
+++ b/hmp.c
@@ -1061,7 +1061,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 b02c670..80ac2cc 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -480,6 +480,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 fb9e100..e0d5561 100644
--- a/include/block/block_int.h
+++ b/include/block/block_int.h
@@ -602,6 +602,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.
@@ -612,6 +613,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 710be68..7f629e2 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -512,10 +512,12 @@
#
# @none: only copy data written from now on
#
+# @dirty-bitmap: only copy data described by the dirty bitmap. Since: 2.4
+#
# Since: 1.3
##
{ 'enum': 'MirrorSyncMode',
- 'data': ['top', 'full', 'none'] }
+ 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
##
# @BlockJobType:
@@ -690,14 +692,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.4)
+#
# @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).
@@ -715,7 +720,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 b6bd455..da750f0 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1074,7 +1074,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,
},
@@ -1101,8 +1101,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)
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
@ 2015-04-17 13:17 ` Max Reitz
2015-04-17 16:21 ` John Snow
2015-04-17 22:51 ` Eric Blake
1 sibling, 1 reply; 48+ messages in thread
From: Max Reitz @ 2015-04-17 13:17 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 09.04.2015 00:19, 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 | 156 +++++++++++++++++++++++++++++++++++++++-------
> 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, 180 insertions(+), 33 deletions(-)
>
> diff --git a/block.c b/block.c
> index 9d30379..2367311 100644
> --- a/block.c
> +++ b/block.c
> @@ -5717,6 +5717,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..8513917 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,92 @@ 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 int coroutine_fn backup_run_incremental(BackupBlockJob *job)
> +{
> + bool error_is_read;
> + int ret = 0;
> + int clusters_per_iter;
> + uint32_t granularity;
> + int64_t sector;
> + int64_t cluster;
> + int64_t end;
> + int64_t last_cluster = -1;
> + BlockDriverState *bs = job->common.bs;
> + HBitmapIter hbi;
> +
> + granularity = bdrv_dirty_bitmap_granularity(job->sync_bitmap);
> + clusters_per_iter = MAX((granularity / BACKUP_CLUSTER_SIZE), 1);
DIV_ROUND_UP(granularity, BACKUP_CLUSTER_SIZE) would've worked, too
(instead of the MAX()), but since both are powers of two, this is
equivalent.
> + bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
> +
> + /* Find the next dirty sector(s) */
> + while ((sector = hbitmap_iter_next(&hbi)) != -1) {
> + cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
> +
> + /* Fake progress updates for any clusters we skipped */
> + if (cluster != last_cluster + 1) {
> + job->common.offset += ((cluster - last_cluster - 1) *
> + BACKUP_CLUSTER_SIZE);
> + }
> +
> + for (end = cluster + clusters_per_iter; cluster < end; cluster++) {
> + if (yield_and_check(job)) {
> + return ret;
> + }
> +
> + 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) {
> + return ret;
> + }
Now that I'm reading this code again... The other backup implementation
handles retries differently; it redoes the whole loop, with the
effective difference being that it calls yield_and_check() between every
retry. Would it make sense to move the yield_and_check() call into this
loop?
> + } while (ret < 0);
> + }
> +
> + /* If the bitmap granularity is smaller than the backup granularity,
> + * we need to advance the iterator pointer to the next cluster. */
> + if (granularity < BACKUP_CLUSTER_SIZE) {
Actually, whenever BACKUP_CLUSTER_SIZE isn't a factor of granularity.
Both are powers of two, though, so that's the case iff granularity <
BACKUP_CLUSTER_SIZE. (thus, the condition is correct)
> + bdrv_set_dirty_iter(&hbi, cluster * BACKUP_SECTORS_PER_CLUSTER);
> + }
> +
> + last_cluster = cluster - 1;
A bit awkward, but hey...
So, what's preventing me from giving an R-b is whether or not
yield_and_check() should be moved.
Max
> + }
> +
> + /* Play some final catchup with the progress meter */
> + end = DIV_ROUND_UP(job->common.len, BACKUP_CLUSTER_SIZE);
> + if (last_cluster + 1 < end) {
> + job->common.offset += ((end - last_cluster - 1) * BACKUP_CLUSTER_SIZE);
> + }
> +
> + return ret;
> +}
> +
> static void coroutine_fn backup_run(void *opaque)
> {
> BackupBlockJob *job = opaque;
> @@ -259,8 +347,7 @@ static void coroutine_fn backup_run(void *opaque)
> 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 +365,13 @@ 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) {
> + ret = backup_run_incremental(job);
> } 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;
> }
>
> @@ -357,6 +429,18 @@ static void coroutine_fn backup_run(void *opaque)
> 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);
> + } 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 +453,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 +497,36 @@ 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 +535,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 1cb700e..f89eccf 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 373d19a..90ba5b6 100644
> --- a/blockdev.c
> +++ b/blockdev.c
> @@ -1584,6 +1584,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);
> @@ -2394,6 +2395,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)
> @@ -2402,6 +2404,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;
> @@ -2501,7 +2504,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);
> @@ -2562,8 +2574,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 f31ae27..d85d913 100644
> --- a/hmp.c
> +++ b/hmp.c
> @@ -1061,7 +1061,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 b02c670..80ac2cc 100644
> --- a/include/block/block.h
> +++ b/include/block/block.h
> @@ -480,6 +480,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 fb9e100..e0d5561 100644
> --- a/include/block/block_int.h
> +++ b/include/block/block_int.h
> @@ -602,6 +602,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.
> @@ -612,6 +613,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 710be68..7f629e2 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -512,10 +512,12 @@
> #
> # @none: only copy data written from now on
> #
> +# @dirty-bitmap: only copy data described by the dirty bitmap. Since: 2.4
> +#
> # Since: 1.3
> ##
> { 'enum': 'MirrorSyncMode',
> - 'data': ['top', 'full', 'none'] }
> + 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
>
> ##
> # @BlockJobType:
> @@ -690,14 +692,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.4)
> +#
> # @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).
> @@ -715,7 +720,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 b6bd455..da750f0 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -1074,7 +1074,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,
> },
>
> @@ -1101,8 +1101,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)
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-04-17 13:17 ` Max Reitz
@ 2015-04-17 16:21 ` John Snow
0 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-17 16:21 UTC (permalink / raw)
To: Max Reitz, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 04/17/2015 09:17 AM, Max Reitz wrote:
> On 09.04.2015 00:19, 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 | 156
>> +++++++++++++++++++++++++++++++++++++++-------
>> 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, 180 insertions(+), 33 deletions(-)
>>
>> diff --git a/block.c b/block.c
>> index 9d30379..2367311 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -5717,6 +5717,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..8513917 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,92 @@ 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 int coroutine_fn backup_run_incremental(BackupBlockJob *job)
>> +{
>> + bool error_is_read;
>> + int ret = 0;
>> + int clusters_per_iter;
>> + uint32_t granularity;
>> + int64_t sector;
>> + int64_t cluster;
>> + int64_t end;
>> + int64_t last_cluster = -1;
>> + BlockDriverState *bs = job->common.bs;
>> + HBitmapIter hbi;
>> +
>> + granularity = bdrv_dirty_bitmap_granularity(job->sync_bitmap);
>> + clusters_per_iter = MAX((granularity / BACKUP_CLUSTER_SIZE), 1);
>
> DIV_ROUND_UP(granularity, BACKUP_CLUSTER_SIZE) would've worked, too
> (instead of the MAX()), but since both are powers of two, this is
> equivalent.
>
But this way we get to put your name in the source code.
>> + bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
>> +
>> + /* Find the next dirty sector(s) */
>> + while ((sector = hbitmap_iter_next(&hbi)) != -1) {
>> + cluster = sector / BACKUP_SECTORS_PER_CLUSTER;
>> +
>> + /* Fake progress updates for any clusters we skipped */
>> + if (cluster != last_cluster + 1) {
>> + job->common.offset += ((cluster - last_cluster - 1) *
>> + BACKUP_CLUSTER_SIZE);
>> + }
>> +
>> + for (end = cluster + clusters_per_iter; cluster < end;
>> cluster++) {
>> + if (yield_and_check(job)) {
>> + return ret;
>> + }
>> +
>> + 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) {
>> + return ret;
>> + }
>
> Now that I'm reading this code again... The other backup implementation
> handles retries differently; it redoes the whole loop, with the
> effective difference being that it calls yield_and_check() between every
> retry. Would it make sense to move the yield_and_check() call into this
> loop?
>
Yes, I should be mindful of the case where we might have to copy many
clusters per dirty bit. I don't think we lose anything by inserting it
at the top of the do{}while(), but we will potentially exit the loop
quicker on cancellation cases.
>> + } while (ret < 0);
>> + }
>> +
>> + /* If the bitmap granularity is smaller than the backup
>> granularity,
>> + * we need to advance the iterator pointer to the next
>> cluster. */
>> + if (granularity < BACKUP_CLUSTER_SIZE) {
>
> Actually, whenever BACKUP_CLUSTER_SIZE isn't a factor of granularity.
> Both are powers of two, though, so that's the case iff granularity <
> BACKUP_CLUSTER_SIZE. (thus, the condition is correct)
>
>> + bdrv_set_dirty_iter(&hbi, cluster *
>> BACKUP_SECTORS_PER_CLUSTER);
>> + }
>> +
>> + last_cluster = cluster - 1;
>
> A bit awkward, but hey...
>
The inner for() is going to advance cluster as an index, plus an extra
before it exits the loop. Either I manually do cluster-- or just
subtract one from last_cluster... Either way I have to fiddle with the
index.
I could also use a separate index and only advance cluster once we make
it into the loop, but that looks awkward too, so I choice my poison.
> So, what's preventing me from giving an R-b is whether or not
> yield_and_check() should be moved.
>
> Max
>
[email_truncate()]
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
2015-04-17 13:17 ` Max Reitz
@ 2015-04-17 22:51 ` Eric Blake
2015-04-17 23:02 ` John Snow
1 sibling, 1 reply; 48+ messages in thread
From: Eric Blake @ 2015-04-17 22:51 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 2760 bytes --]
On 04/08/2015 04:19 PM, 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 | 156 +++++++++++++++++++++++++++++++++++++++-------
> 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, 180 insertions(+), 33 deletions(-)
>
Just reviewing the interface...
> +++ b/qapi/block-core.json
> @@ -512,10 +512,12 @@
> #
> # @none: only copy data written from now on
> #
> +# @dirty-bitmap: only copy data described by the dirty bitmap. Since: 2.4
> +#
> # Since: 1.3
> ##
> { 'enum': 'MirrorSyncMode',
> - 'data': ['top', 'full', 'none'] }
> + 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
>
> ##
> # @BlockJobType:
> @@ -690,14 +692,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.4)
> +#
> # @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).
> @@ -715,7 +720,7 @@
> { 'type': 'DriveBackup',
> 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
> 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
> - '*speed': 'int',
> + '*speed': 'int', '*bitmap': 'str',
Looks okay. Is it an error if bitmap is supplied, but mode is not
dirty-bitmap? Likewise, if mode is dirty-bitmap but bitmap is not supplied?
--
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] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-04-17 22:51 ` Eric Blake
@ 2015-04-17 23:02 ` John Snow
2015-04-17 23:10 ` Eric Blake
0 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-17 23:02 UTC (permalink / raw)
To: Eric Blake, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
On 04/17/2015 06:51 PM, Eric Blake wrote:
> On 04/08/2015 04:19 PM, 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 | 156 +++++++++++++++++++++++++++++++++++++++-------
>> 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, 180 insertions(+), 33 deletions(-)
>>
>
> Just reviewing the interface...
>
>> +++ b/qapi/block-core.json
>> @@ -512,10 +512,12 @@
>> #
>> # @none: only copy data written from now on
>> #
>> +# @dirty-bitmap: only copy data described by the dirty bitmap. Since: 2.4
>> +#
>> # Since: 1.3
>> ##
>> { 'enum': 'MirrorSyncMode',
>> - 'data': ['top', 'full', 'none'] }
>> + 'data': ['top', 'full', 'none', 'dirty-bitmap'] }
>>
>> ##
>> # @BlockJobType:
>> @@ -690,14 +692,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.4)
>> +#
>> # @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).
>> @@ -715,7 +720,7 @@
>> { 'type': 'DriveBackup',
>> 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>> 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
>> - '*speed': 'int',
>> + '*speed': 'int', '*bitmap': 'str',
>
> Looks okay. Is it an error if bitmap is supplied, but mode is not
> dirty-bitmap? Likewise, if mode is dirty-bitmap but bitmap is not supplied?
>
Yes:
+ 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;
+ }
+
[...]
+ } 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;
+ }
+
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup
2015-04-17 23:02 ` John Snow
@ 2015-04-17 23:10 ` Eric Blake
0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2015-04-17 23:10 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 1404 bytes --]
On 04/17/2015 05:02 PM, John Snow wrote:
>>> { 'type': 'DriveBackup',
>>> 'data': { 'device': 'str', 'target': 'str', '*format': 'str',
>>> 'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
>>> - '*speed': 'int',
>>> + '*speed': 'int', '*bitmap': 'str',
>>
>> Looks okay. Is it an error if bitmap is supplied, but mode is not
>> dirty-bitmap? Likewise, if mode is dirty-bitmap but bitmap is not
>> supplied?
>>
>
> Yes:
Then we _could_ do this as a flat union (here, using a shorthand syntax
of anonymous inline types, although qapi does not yet support it):
{ 'type': 'DriveBackupBase',
'data': { 'device': 'str', 'target': 'str', '*format': 'str',
'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
'*speed': 'int', '*on-source-error': 'BlockdevOnError',
'*on-target-error': 'BlockdevOnError' } }
{ 'union': 'DriveBackup', 'base': 'DriveBackupBase',
'discriminator': 'sync',
'data': { 'top': {}, 'full': {}, 'none': {},
'dirty-bitmap': { 'bitmap': 'str' } } }
which would enforce that 'bitmap' be present exactly when
'sync':'dirty-bitmap'. But that's probably overkill; I don't expect you
to do it (especially since qapi shorthand for anonymous inline types is
not there yet).
--
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] 48+ messages in thread
* [Qemu-devel] [PATCH v5 11/21] qmp: add block-dirty-bitmap-clear
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (9 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 10/21] qmp: Add support of "dirty-bitmap" sync mode for drive-backup John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 22:55 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 12/21] qmp: Add dirty bitmap status field in query-block John Snow
` (9 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
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>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 8 ++++++++
blockdev.c | 34 ++++++++++++++++++++++++++++++++++
include/block/block.h | 1 +
qapi/block-core.json | 14 ++++++++++++++
qmp-commands.hx | 29 +++++++++++++++++++++++++++++
5 files changed, 86 insertions(+)
diff --git a/block.c b/block.c
index 2367311..c26ac64 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;
@@ -5491,6 +5492,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);
@@ -5693,6 +5695,12 @@ void bdrv_reset_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap,
hbitmap_reset(bitmap->bitmap, cur_sector, nr_sectors);
}
+void bdrv_clear_dirty_bitmap(BdrvDirtyBitmap *bitmap)
+{
+ assert(bdrv_dirty_bitmap_enabled(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 90ba5b6..df96959 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2078,6 +2078,40 @@ void qmp_block_dirty_bitmap_remove(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, &aio_context, errp);
+ if (!bitmap || !bs) {
+ return;
+ }
+
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ error_setg(errp,
+ "Bitmap '%s' is currently frozen and cannot be modified",
+ name);
+ goto out;
+ } else if (!bdrv_dirty_bitmap_enabled(bitmap)) {
+ error_setg(errp,
+ "Bitmap '%s' is currently disabled and cannot be cleared",
+ name);
+ goto out;
+ }
+
+ bdrv_clear_dirty_bitmap(bitmap);
+
+ out:
+ aio_context_release(aio_context);
+}
+
int hmp_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 80ac2cc..0961b1e 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -478,6 +478,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 7f629e2..11d8e2c 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1021,6 +1021,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.4
+##
+{ '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 da750f0..f2dc66c 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -1327,6 +1327,35 @@ Example:
EQMP
{
+ .name = "block-dirty-bitmap-clear",
+ .args_type = "node:B,name:s",
+ .mhandler.cmd_new = qmp_marshal_input_block_dirty_bitmap_clear,
+ },
+
+SQMP
+
+block-dirty-bitmap-clear
+------------------------
+Since 2.4
+
+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
+
+ {
.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,
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 11/21] qmp: add block-dirty-bitmap-clear
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 11/21] qmp: add block-dirty-bitmap-clear John Snow
@ 2015-04-17 22:55 ` Eric Blake
0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2015-04-17 22:55 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 593 bytes --]
On 04/08/2015 04:19 PM, 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>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
Reviewed-by: Eric Blake <eblake@redhat.com>
--
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] 48+ messages in thread
* [Qemu-devel] [PATCH v5 12/21] qmp: Add dirty bitmap status field in query-block
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (10 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 11/21] qmp: add block-dirty-bitmap-clear John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-17 22:55 ` Eric Blake
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 13/21] block: add BdrvDirtyBitmap documentation John Snow
` (8 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Add the "frozen" status booleans, to inform clients
when a bitmap is occupied doing a task.
Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 1 +
qapi/block-core.json | 5 ++++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/block.c b/block.c
index c26ac64..c104cdd 100644
--- a/block.c
+++ b/block.c
@@ -5633,6 +5633,7 @@ 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->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 11d8e2c..5a77cca 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -336,10 +336,13 @@
#
# @granularity: granularity of the dirty bitmap in bytes (since 1.4)
#
+# @frozen: whether the dirty bitmap is frozen (Since 2.4)
+#
# Since: 1.3
##
{ 'type': 'BlockDirtyInfo',
- 'data': {'*name': 'str', 'count': 'int', 'granularity': 'uint32'} }
+ 'data': {'*name': 'str', 'count': 'int', 'granularity': 'uint32',
+ 'frozen': 'bool'} }
##
# @BlockInfo:
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 12/21] qmp: Add dirty bitmap status field in query-block
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 12/21] qmp: Add dirty bitmap status field in query-block John Snow
@ 2015-04-17 22:55 ` Eric Blake
0 siblings, 0 replies; 48+ messages in thread
From: Eric Blake @ 2015-04-17 22:55 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha, mreitz
[-- Attachment #1: Type: text/plain, Size: 624 bytes --]
On 04/08/2015 04:19 PM, John Snow wrote:
> Add the "frozen" status booleans, to inform clients
> when a bitmap is occupied doing a task.
>
> Signed-off-by: Fam Zheng <famz@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
> block.c | 1 +
> qapi/block-core.json | 5 ++++-
> 2 files changed, 5 insertions(+), 1 deletion(-)
>
Reviewed-by: Eric Blake <eblake@redhat.com>
--
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] 48+ messages in thread
* [Qemu-devel] [PATCH v5 13/21] block: add BdrvDirtyBitmap documentation
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (11 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 12/21] qmp: Add dirty bitmap status field in query-block John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 14/21] block: Ensure consistent bitmap function prototypes John Snow
` (7 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/block.c b/block.c
index c104cdd..843b0bf 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;
};
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 14/21] block: Ensure consistent bitmap function prototypes
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (12 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 13/21] block: add BdrvDirtyBitmap documentation John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate John Snow
` (6 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
We often don't need the BlockDriverState for functions
that operate on bitmaps. Remove it.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
block.c | 13 ++++++-------
block/backup.c | 2 +-
block/mirror.c | 26 ++++++++++----------------
blockdev.c | 2 +-
include/block/block.h | 11 +++++------
migration/block.c | 7 +++----
6 files changed, 26 insertions(+), 35 deletions(-)
diff --git a/block.c b/block.c
index 843b0bf..16209a2 100644
--- a/block.c
+++ b/block.c
@@ -5460,7 +5460,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);
@@ -5629,7 +5629,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);
@@ -5676,20 +5676,19 @@ uint32_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));
@@ -5735,7 +5734,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 8513917..cdd41c5 100644
--- a/block/backup.c
+++ b/block/backup.c
@@ -284,7 +284,7 @@ static int coroutine_fn backup_run_incremental(BackupBlockJob *job)
granularity = bdrv_dirty_bitmap_granularity(job->sync_bitmap);
clusters_per_iter = MAX((granularity / BACKUP_CLUSTER_SIZE), 1);
- bdrv_dirty_iter_init(bs, job->sync_bitmap, &hbi);
+ bdrv_dirty_iter_init(job->sync_bitmap, &hbi);
/* Find the next dirty sector(s) */
while ((sector = hbitmap_iter_next(&hbi)) != -1) {
diff --git a/block/mirror.c b/block/mirror.c
index f89eccf..dcd6f65 100644
--- a/block/mirror.c
+++ b/block/mirror.c
@@ -125,11 +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,
- op->nb_sectors);
+ 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) {
s->ret = ret;
@@ -143,11 +141,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,
- op->nb_sectors);
+ 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) {
s->ret = ret;
@@ -170,10 +166,9 @@ 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));
+ trace_mirror_restart_iter(s, bdrv_get_dirty_count(s->dirty_bitmap));
assert(s->sector_num >= 0);
}
@@ -288,8 +283,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 +440,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 +448,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 +460,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 +510,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 +525,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 df96959..b9c79ed 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -2071,7 +2071,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 0961b1e..36bdf04 100644
--- a/include/block/block.h
+++ b/include/block/block.h
@@ -464,7 +464,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);
@@ -474,15 +474,14 @@ uint32_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 02a7d26..ddb59cc 100644
--- a/migration/block.c
+++ b/migration/block.c
@@ -304,7 +304,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;
@@ -497,8 +497,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;
@@ -584,7 +583,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;
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (13 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 14/21] block: Ensure consistent bitmap function prototypes John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-09 14:38 ` Stefan Hajnoczi
2015-04-17 13:25 ` Max Reitz
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 16/21] hbitmap: truncate tests John Snow
` (5 subsequent siblings)
20 siblings, 2 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Signed-off-by: John Snow <jsnow@redhat.com>
---
block.c | 18 ++++++++++++++++++
include/qemu/hbitmap.h | 10 ++++++++++
util/hbitmap.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 76 insertions(+)
diff --git a/block.c b/block.c
index 16209a2..42839a0 100644
--- a/block.c
+++ b/block.c
@@ -113,6 +113,7 @@ static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
int nr_sectors);
static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
int nr_sectors);
+static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);
/* If non-zero, use only whitelisted block drivers */
static int use_bdrv_whitelist;
@@ -3583,6 +3584,7 @@ int bdrv_truncate(BlockDriverState *bs, int64_t offset)
ret = drv->bdrv_truncate(bs, offset);
if (ret == 0) {
ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS);
+ bdrv_dirty_bitmap_truncate(bs);
if (bs->blk) {
blk_dev_resize_cb(bs->blk);
}
@@ -5593,6 +5595,22 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
return parent;
}
+/**
+ * Truncates _all_ bitmaps attached to a BDS.
+ */
+static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs)
+{
+ BdrvDirtyBitmap *bitmap;
+ uint64_t size = bdrv_nb_sectors(bs);
+
+ QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
+ if (bdrv_dirty_bitmap_frozen(bitmap)) {
+ continue;
+ }
+ hbitmap_truncate(bitmap->bitmap, size);
+ }
+}
+
void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
{
BdrvDirtyBitmap *bm, *next;
diff --git a/include/qemu/hbitmap.h b/include/qemu/hbitmap.h
index c19c1cb..a75157e 100644
--- a/include/qemu/hbitmap.h
+++ b/include/qemu/hbitmap.h
@@ -65,6 +65,16 @@ struct HBitmapIter {
HBitmap *hbitmap_alloc(uint64_t size, int granularity);
/**
+ * hbitmap_truncate:
+ * @hb: The bitmap to change the size of.
+ * @size: The number of elements to change the bitmap to accommodate.
+ *
+ * truncate or grow an existing bitmap to accommodate a new number of elements.
+ * This may invalidate existing HBitmapIterators.
+ */
+void hbitmap_truncate(HBitmap *hb, uint64_t size);
+
+/**
* hbitmap_merge:
* @a: The bitmap to store the result in.
* @b: The bitmap to merge into @a.
diff --git a/util/hbitmap.c b/util/hbitmap.c
index ba11fd3..1ad3bf3 100644
--- a/util/hbitmap.c
+++ b/util/hbitmap.c
@@ -400,6 +400,54 @@ HBitmap *hbitmap_alloc(uint64_t size, int granularity)
return hb;
}
+void hbitmap_truncate(HBitmap *hb, uint64_t size)
+{
+ bool shrink;
+ unsigned i;
+ uint64_t num_elements = size;
+ uint64_t old;
+
+ /* Size comes in as logical elements, adjust for granularity. */
+ size = (size + (1ULL << hb->granularity) - 1) >> hb->granularity;
+ assert(size <= ((uint64_t)1 << HBITMAP_LOG_MAX_SIZE));
+ shrink = size < hb->size;
+
+ /* bit sizes are identical; nothing to do. */
+ if (size == hb->size) {
+ return;
+ }
+
+ /* If we're losing bits, let's clear those bits before we invalidate all of
+ * our invariants. This helps keep the bitcount consistent, and will prevent
+ * us from carrying around garbage bits beyond the end of the map.
+ */
+ if (shrink) {
+ /* Don't clear partial granularity groups;
+ * start at the first full one. */
+ uint64_t start = QEMU_ALIGN_UP(num_elements, 1 << hb->granularity);
+ uint64_t fix_count = (hb->size << hb->granularity) - num_elements;
+
+ assert(fix_count);
+ hbitmap_reset(hb, start, fix_count);
+ }
+
+ hb->size = size;
+ for (i = HBITMAP_LEVELS; i-- > 0; ) {
+ size = MAX(BITS_TO_LONGS(size), 1);
+ if (hb->sizes[i] == size) {
+ break;
+ }
+ old = hb->sizes[i];
+ hb->sizes[i] = size;
+ hb->levels[i] = g_realloc(hb->levels[i], size * sizeof(unsigned long));
+ if (!shrink) {
+ memset(&hb->levels[i][old], 0x00,
+ (size - old) * sizeof(*hb->levels[i]));
+ }
+ }
+}
+
+
/**
* Given HBitmaps A and B, let A := A (BITOR) B.
* Bitmap B will not be modified.
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate John Snow
@ 2015-04-09 14:38 ` Stefan Hajnoczi
2015-04-17 13:25 ` Max Reitz
1 sibling, 0 replies; 48+ messages in thread
From: Stefan Hajnoczi @ 2015-04-09 14:38 UTC (permalink / raw)
To: John Snow; +Cc: kwolf, famz, qemu-block, qemu-devel, armbru, vsementsov, mreitz
[-- Attachment #1: Type: text/plain, Size: 372 bytes --]
On Wed, Apr 08, 2015 at 06:19:58PM -0400, John Snow wrote:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 18 ++++++++++++++++++
> include/qemu/hbitmap.h | 10 ++++++++++
> util/hbitmap.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 76 insertions(+)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: Type: application/pgp-signature, Size: 473 bytes --]
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate John Snow
2015-04-09 14:38 ` Stefan Hajnoczi
@ 2015-04-17 13:25 ` Max Reitz
2015-04-17 16:51 ` John Snow
1 sibling, 1 reply; 48+ messages in thread
From: Max Reitz @ 2015-04-17 13:25 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 09.04.2015 00:19, John Snow wrote:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> block.c | 18 ++++++++++++++++++
> include/qemu/hbitmap.h | 10 ++++++++++
> util/hbitmap.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 76 insertions(+)
>
> diff --git a/block.c b/block.c
> index 16209a2..42839a0 100644
> --- a/block.c
> +++ b/block.c
> @@ -113,6 +113,7 @@ static void bdrv_set_dirty(BlockDriverState *bs, int64_t cur_sector,
> int nr_sectors);
> static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
> int nr_sectors);
> +static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);
> /* If non-zero, use only whitelisted block drivers */
> static int use_bdrv_whitelist;
>
> @@ -3583,6 +3584,7 @@ int bdrv_truncate(BlockDriverState *bs, int64_t offset)
> ret = drv->bdrv_truncate(bs, offset);
> if (ret == 0) {
> ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS);
> + bdrv_dirty_bitmap_truncate(bs);
> if (bs->blk) {
> blk_dev_resize_cb(bs->blk);
> }
> @@ -5593,6 +5595,22 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
> return parent;
> }
>
> +/**
> + * Truncates _all_ bitmaps attached to a BDS.
> + */
> +static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs)
> +{
> + BdrvDirtyBitmap *bitmap;
> + uint64_t size = bdrv_nb_sectors(bs);
> +
> + QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
> + if (bdrv_dirty_bitmap_frozen(bitmap)) {
> + continue;
> + }
> + hbitmap_truncate(bitmap->bitmap, size);
> + }
> +}
> +
> void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap *bitmap)
> {
> BdrvDirtyBitmap *bm, *next;
> diff --git a/include/qemu/hbitmap.h b/include/qemu/hbitmap.h
> index c19c1cb..a75157e 100644
> --- a/include/qemu/hbitmap.h
> +++ b/include/qemu/hbitmap.h
> @@ -65,6 +65,16 @@ struct HBitmapIter {
> HBitmap *hbitmap_alloc(uint64_t size, int granularity);
>
> /**
> + * hbitmap_truncate:
> + * @hb: The bitmap to change the size of.
> + * @size: The number of elements to change the bitmap to accommodate.
> + *
> + * truncate or grow an existing bitmap to accommodate a new number of elements.
> + * This may invalidate existing HBitmapIterators.
> + */
> +void hbitmap_truncate(HBitmap *hb, uint64_t size);
> +
> +/**
> * hbitmap_merge:
> * @a: The bitmap to store the result in.
> * @b: The bitmap to merge into @a.
> diff --git a/util/hbitmap.c b/util/hbitmap.c
> index ba11fd3..1ad3bf3 100644
> --- a/util/hbitmap.c
> +++ b/util/hbitmap.c
> @@ -400,6 +400,54 @@ HBitmap *hbitmap_alloc(uint64_t size, int granularity)
> return hb;
> }
>
> +void hbitmap_truncate(HBitmap *hb, uint64_t size)
> +{
> + bool shrink;
> + unsigned i;
> + uint64_t num_elements = size;
> + uint64_t old;
> +
> + /* Size comes in as logical elements, adjust for granularity. */
> + size = (size + (1ULL << hb->granularity) - 1) >> hb->granularity;
> + assert(size <= ((uint64_t)1 << HBITMAP_LOG_MAX_SIZE));
> + shrink = size < hb->size;
> +
> + /* bit sizes are identical; nothing to do. */
> + if (size == hb->size) {
> + return;
> + }
> +
> + /* If we're losing bits, let's clear those bits before we invalidate all of
> + * our invariants. This helps keep the bitcount consistent, and will prevent
> + * us from carrying around garbage bits beyond the end of the map.
> + */
> + if (shrink) {
> + /* Don't clear partial granularity groups;
> + * start at the first full one. */
> + uint64_t start = QEMU_ALIGN_UP(num_elements, 1 << hb->granularity);
> + uint64_t fix_count = (hb->size << hb->granularity) - num_elements;
Shouldn't this be s/num_elements/start/?
Max
> +
> + assert(fix_count);
> + hbitmap_reset(hb, start, fix_count);
> + }
> +
> + hb->size = size;
> + for (i = HBITMAP_LEVELS; i-- > 0; ) {
> + size = MAX(BITS_TO_LONGS(size), 1);
> + if (hb->sizes[i] == size) {
> + break;
> + }
> + old = hb->sizes[i];
> + hb->sizes[i] = size;
> + hb->levels[i] = g_realloc(hb->levels[i], size * sizeof(unsigned long));
> + if (!shrink) {
> + memset(&hb->levels[i][old], 0x00,
> + (size - old) * sizeof(*hb->levels[i]));
> + }
> + }
> +}
> +
> +
> /**
> * Given HBitmaps A and B, let A := A (BITOR) B.
> * Bitmap B will not be modified.
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate
2015-04-17 13:25 ` Max Reitz
@ 2015-04-17 16:51 ` John Snow
0 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-17 16:51 UTC (permalink / raw)
To: Max Reitz, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 04/17/2015 09:25 AM, Max Reitz wrote:
> On 09.04.2015 00:19, John Snow wrote:
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> block.c | 18 ++++++++++++++++++
>> include/qemu/hbitmap.h | 10 ++++++++++
>> util/hbitmap.c | 48
>> ++++++++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 76 insertions(+)
>>
>> diff --git a/block.c b/block.c
>> index 16209a2..42839a0 100644
>> --- a/block.c
>> +++ b/block.c
>> @@ -113,6 +113,7 @@ static void bdrv_set_dirty(BlockDriverState *bs,
>> int64_t cur_sector,
>> int nr_sectors);
>> static void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector,
>> int nr_sectors);
>> +static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);
>> /* If non-zero, use only whitelisted block drivers */
>> static int use_bdrv_whitelist;
>> @@ -3583,6 +3584,7 @@ int bdrv_truncate(BlockDriverState *bs, int64_t
>> offset)
>> ret = drv->bdrv_truncate(bs, offset);
>> if (ret == 0) {
>> ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS);
>> + bdrv_dirty_bitmap_truncate(bs);
>> if (bs->blk) {
>> blk_dev_resize_cb(bs->blk);
>> }
>> @@ -5593,6 +5595,22 @@ BdrvDirtyBitmap
>> *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,
>> return parent;
>> }
>> +/**
>> + * Truncates _all_ bitmaps attached to a BDS.
>> + */
>> +static void bdrv_dirty_bitmap_truncate(BlockDriverState *bs)
>> +{
>> + BdrvDirtyBitmap *bitmap;
>> + uint64_t size = bdrv_nb_sectors(bs);
>> +
>> + QLIST_FOREACH(bitmap, &bs->dirty_bitmaps, list) {
>> + if (bdrv_dirty_bitmap_frozen(bitmap)) {
>> + continue;
>> + }
>> + hbitmap_truncate(bitmap->bitmap, size);
>> + }
>> +}
>> +
>> void bdrv_release_dirty_bitmap(BlockDriverState *bs, BdrvDirtyBitmap
>> *bitmap)
>> {
>> BdrvDirtyBitmap *bm, *next;
>> diff --git a/include/qemu/hbitmap.h b/include/qemu/hbitmap.h
>> index c19c1cb..a75157e 100644
>> --- a/include/qemu/hbitmap.h
>> +++ b/include/qemu/hbitmap.h
>> @@ -65,6 +65,16 @@ struct HBitmapIter {
>> HBitmap *hbitmap_alloc(uint64_t size, int granularity);
>> /**
>> + * hbitmap_truncate:
>> + * @hb: The bitmap to change the size of.
>> + * @size: The number of elements to change the bitmap to accommodate.
>> + *
>> + * truncate or grow an existing bitmap to accommodate a new number of
>> elements.
>> + * This may invalidate existing HBitmapIterators.
>> + */
>> +void hbitmap_truncate(HBitmap *hb, uint64_t size);
>> +
>> +/**
>> * hbitmap_merge:
>> * @a: The bitmap to store the result in.
>> * @b: The bitmap to merge into @a.
>> diff --git a/util/hbitmap.c b/util/hbitmap.c
>> index ba11fd3..1ad3bf3 100644
>> --- a/util/hbitmap.c
>> +++ b/util/hbitmap.c
>> @@ -400,6 +400,54 @@ HBitmap *hbitmap_alloc(uint64_t size, int
>> granularity)
>> return hb;
>> }
>> +void hbitmap_truncate(HBitmap *hb, uint64_t size)
>> +{
>> + bool shrink;
>> + unsigned i;
>> + uint64_t num_elements = size;
>> + uint64_t old;
>> +
>> + /* Size comes in as logical elements, adjust for granularity. */
>> + size = (size + (1ULL << hb->granularity) - 1) >> hb->granularity;
>> + assert(size <= ((uint64_t)1 << HBITMAP_LOG_MAX_SIZE));
>> + shrink = size < hb->size;
>> +
>> + /* bit sizes are identical; nothing to do. */
>> + if (size == hb->size) {
>> + return;
>> + }
>> +
>> + /* If we're losing bits, let's clear those bits before we
>> invalidate all of
>> + * our invariants. This helps keep the bitcount consistent, and
>> will prevent
>> + * us from carrying around garbage bits beyond the end of the map.
>> + */
>> + if (shrink) {
>> + /* Don't clear partial granularity groups;
>> + * start at the first full one. */
>> + uint64_t start = QEMU_ALIGN_UP(num_elements, 1 <<
>> hb->granularity);
>> + uint64_t fix_count = (hb->size << hb->granularity) -
>> num_elements;
>
> Shouldn't this be s/num_elements/start/?
>
> Max
>
Hmm... Yes, though in practice it doesn't appear to have mattered. Odd.
The effect here is that we will extend the fix count beyond the end of
the bitmap by an amount less than the granularity.
hbitmap_reset doesn't seem to make any particular effort to ensure the
virtual bit ranges you give it are valid in any way.
And I suppose due to the extra bits that hbitmap allocates for its own
use meant that we never ran off the end of the array by a single bit or
anything.
Well, fixed, regardless. Thanks.
>> +
>> + assert(fix_count);
>> + hbitmap_reset(hb, start, fix_count);
>> + }
>> +
>> + hb->size = size;
>> + for (i = HBITMAP_LEVELS; i-- > 0; ) {
>> + size = MAX(BITS_TO_LONGS(size), 1);
>> + if (hb->sizes[i] == size) {
>> + break;
>> + }
>> + old = hb->sizes[i];
>> + hb->sizes[i] = size;
>> + hb->levels[i] = g_realloc(hb->levels[i], size *
>> sizeof(unsigned long));
>> + if (!shrink) {
>> + memset(&hb->levels[i][old], 0x00,
>> + (size - old) * sizeof(*hb->levels[i]));
>> + }
>> + }
>> +}
>> +
>> +
>> /**
>> * Given HBitmaps A and B, let A := A (BITOR) B.
>> * Bitmap B will not be modified.
>
>
^ permalink raw reply [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 16/21] hbitmap: truncate tests
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (14 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 15/21] block: Resize bitmaps on bdrv_truncate John Snow
@ 2015-04-08 22:19 ` John Snow
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 17/21] iotests: add invalid input incremental backup tests John Snow
` (4 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:19 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
The general approach is to set bits close to the boundaries of
where we are truncating and ensure that everything appears to
have gone OK.
We test growing and shrinking by different amounts:
- Less than the granularity
- Less than the granularity, but across a boundary
- Less than sizeof(unsigned long)
- Less than sizeof(unsigned long), but across a ulong boundary
- More than sizeof(unsigned long)
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
tests/test-hbitmap.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 255 insertions(+)
diff --git a/tests/test-hbitmap.c b/tests/test-hbitmap.c
index 8c902f2..9f41b5f 100644
--- a/tests/test-hbitmap.c
+++ b/tests/test-hbitmap.c
@@ -11,6 +11,8 @@
#include <glib.h>
#include <stdarg.h>
+#include <string.h>
+#include <sys/types.h>
#include "qemu/hbitmap.h"
#define LOG_BITS_PER_LONG (BITS_PER_LONG == 32 ? 5 : 6)
@@ -23,6 +25,7 @@ typedef struct TestHBitmapData {
HBitmap *hb;
unsigned long *bits;
size_t size;
+ size_t old_size;
int granularity;
} TestHBitmapData;
@@ -91,6 +94,44 @@ static void hbitmap_test_init(TestHBitmapData *data,
}
}
+static inline size_t hbitmap_test_array_size(size_t bits)
+{
+ size_t n = (bits + BITS_PER_LONG - 1) / BITS_PER_LONG;
+ return n ? n : 1;
+}
+
+static void hbitmap_test_truncate_impl(TestHBitmapData *data,
+ size_t size)
+{
+ size_t n;
+ size_t m;
+ data->old_size = data->size;
+ data->size = size;
+
+ if (data->size == data->old_size) {
+ return;
+ }
+
+ n = hbitmap_test_array_size(size);
+ m = hbitmap_test_array_size(data->old_size);
+ data->bits = g_realloc(data->bits, sizeof(unsigned long) * n);
+ if (n > m) {
+ memset(&data->bits[m], 0x00, sizeof(unsigned long) * (n - m));
+ }
+
+ /* If we shrink to an uneven multiple of sizeof(unsigned long),
+ * scrub the leftover memory. */
+ if (data->size < data->old_size) {
+ m = size % (sizeof(unsigned long) * 8);
+ if (m) {
+ unsigned long mask = (1ULL << m) - 1;
+ data->bits[n-1] &= mask;
+ }
+ }
+
+ hbitmap_truncate(data->hb, size);
+}
+
static void hbitmap_test_teardown(TestHBitmapData *data,
const void *unused)
{
@@ -369,6 +410,198 @@ static void test_hbitmap_iter_granularity(TestHBitmapData *data,
g_assert_cmpint(hbitmap_iter_next(&hbi), <, 0);
}
+static void hbitmap_test_set_boundary_bits(TestHBitmapData *data, ssize_t diff)
+{
+ size_t size = data->size;
+
+ /* First bit */
+ hbitmap_test_set(data, 0, 1);
+ if (diff < 0) {
+ /* Last bit in new, shortened map */
+ hbitmap_test_set(data, size + diff - 1, 1);
+
+ /* First bit to be truncated away */
+ hbitmap_test_set(data, size + diff, 1);
+ }
+ /* Last bit */
+ hbitmap_test_set(data, size - 1, 1);
+ if (data->granularity == 0) {
+ hbitmap_test_check_get(data);
+ }
+}
+
+static void hbitmap_test_check_boundary_bits(TestHBitmapData *data)
+{
+ size_t size = MIN(data->size, data->old_size);
+
+ if (data->granularity == 0) {
+ hbitmap_test_check_get(data);
+ hbitmap_test_check(data, 0);
+ } else {
+ /* If a granularity was set, note that every distinct
+ * (bit >> granularity) value that was set will increase
+ * the bit pop count by 2^granularity, not just 1.
+ *
+ * The hbitmap_test_check facility does not currently tolerate
+ * non-zero granularities, so test the boundaries and the population
+ * count manually.
+ */
+ g_assert(hbitmap_get(data->hb, 0));
+ g_assert(hbitmap_get(data->hb, size - 1));
+ g_assert_cmpint(2 << data->granularity, ==, hbitmap_count(data->hb));
+ }
+}
+
+/* Generic truncate test. */
+static void hbitmap_test_truncate(TestHBitmapData *data,
+ size_t size,
+ ssize_t diff,
+ int granularity)
+{
+ hbitmap_test_init(data, size, granularity);
+ hbitmap_test_set_boundary_bits(data, diff);
+ hbitmap_test_truncate_impl(data, size + diff);
+ hbitmap_test_check_boundary_bits(data);
+}
+
+static void test_hbitmap_truncate_nop(TestHBitmapData *data,
+ const void *unused)
+{
+ hbitmap_test_truncate(data, L2, 0, 0);
+}
+
+/**
+ * Grow by an amount smaller than the granularity, without crossing
+ * a granularity alignment boundary. Effectively a NOP.
+ */
+static void test_hbitmap_truncate_grow_negligible(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2 - 1;
+ size_t diff = 1;
+ int granularity = 1;
+
+ hbitmap_test_truncate(data, size, diff, granularity);
+}
+
+/**
+ * Shrink by an amount smaller than the granularity, without crossing
+ * a granularity alignment boundary. Effectively a NOP.
+ */
+static void test_hbitmap_truncate_shrink_negligible(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2;
+ ssize_t diff = -1;
+ int granularity = 1;
+
+ hbitmap_test_truncate(data, size, diff, granularity);
+}
+
+/**
+ * Grow by an amount smaller than the granularity, but crossing over
+ * a granularity alignment boundary.
+ */
+static void test_hbitmap_truncate_grow_tiny(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2 - 2;
+ ssize_t diff = 1;
+ int granularity = 1;
+
+ hbitmap_test_truncate(data, size, diff, granularity);
+}
+
+/**
+ * Shrink by an amount smaller than the granularity, but crossing over
+ * a granularity alignment boundary.
+ */
+static void test_hbitmap_truncate_shrink_tiny(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2 - 1;
+ ssize_t diff = -1;
+ int granularity = 1;
+
+ hbitmap_test_truncate(data, size, diff, granularity);
+}
+
+/**
+ * Grow by an amount smaller than sizeof(long), and not crossing over
+ * a sizeof(long) alignment boundary.
+ */
+static void test_hbitmap_truncate_grow_small(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2 + 1;
+ size_t diff = sizeof(long) / 2;
+
+ hbitmap_test_truncate(data, size, diff, 0);
+}
+
+/**
+ * Shrink by an amount smaller than sizeof(long), and not crossing over
+ * a sizeof(long) alignment boundary.
+ */
+static void test_hbitmap_truncate_shrink_small(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2;
+ size_t diff = sizeof(long) / 2;
+
+ hbitmap_test_truncate(data, size, -diff, 0);
+}
+
+/**
+ * Grow by an amount smaller than sizeof(long), while crossing over
+ * a sizeof(long) alignment boundary.
+ */
+static void test_hbitmap_truncate_grow_medium(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2 - 1;
+ size_t diff = sizeof(long) / 2;
+
+ hbitmap_test_truncate(data, size, diff, 0);
+}
+
+/**
+ * Shrink by an amount smaller than sizeof(long), while crossing over
+ * a sizeof(long) alignment boundary.
+ */
+static void test_hbitmap_truncate_shrink_medium(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2 + 1;
+ size_t diff = sizeof(long) / 2;
+
+ hbitmap_test_truncate(data, size, -diff, 0);
+}
+
+/**
+ * Grow by an amount larger than sizeof(long).
+ */
+static void test_hbitmap_truncate_grow_large(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2;
+ size_t diff = 8 * sizeof(long);
+
+ hbitmap_test_truncate(data, size, diff, 0);
+}
+
+/**
+ * Shrink by an amount larger than sizeof(long).
+ */
+static void test_hbitmap_truncate_shrink_large(TestHBitmapData *data,
+ const void *unused)
+{
+ size_t size = L2;
+ size_t diff = 8 * sizeof(long);
+
+ hbitmap_test_truncate(data, size, -diff, 0);
+}
+
static void hbitmap_test_add(const char *testpath,
void (*test_func)(TestHBitmapData *data, const void *user_data))
{
@@ -395,6 +628,28 @@ int main(int argc, char **argv)
hbitmap_test_add("/hbitmap/reset/empty", test_hbitmap_reset_empty);
hbitmap_test_add("/hbitmap/reset/general", test_hbitmap_reset);
hbitmap_test_add("/hbitmap/granularity", test_hbitmap_granularity);
+
+ hbitmap_test_add("/hbitmap/truncate/nop", test_hbitmap_truncate_nop);
+ hbitmap_test_add("/hbitmap/truncate/grow/negligible",
+ test_hbitmap_truncate_grow_negligible);
+ hbitmap_test_add("/hbitmap/truncate/shrink/negligible",
+ test_hbitmap_truncate_shrink_negligible);
+ hbitmap_test_add("/hbitmap/truncate/grow/tiny",
+ test_hbitmap_truncate_grow_tiny);
+ hbitmap_test_add("/hbitmap/truncate/shrink/tiny",
+ test_hbitmap_truncate_shrink_tiny);
+ hbitmap_test_add("/hbitmap/truncate/grow/small",
+ test_hbitmap_truncate_grow_small);
+ hbitmap_test_add("/hbitmap/truncate/shrink/small",
+ test_hbitmap_truncate_shrink_small);
+ hbitmap_test_add("/hbitmap/truncate/grow/medium",
+ test_hbitmap_truncate_grow_medium);
+ hbitmap_test_add("/hbitmap/truncate/shrink/medium",
+ test_hbitmap_truncate_shrink_medium);
+ hbitmap_test_add("/hbitmap/truncate/grow/large",
+ test_hbitmap_truncate_grow_large);
+ hbitmap_test_add("/hbitmap/truncate/shrink/large",
+ test_hbitmap_truncate_shrink_large);
g_test_run();
return 0;
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 17/21] iotests: add invalid input incremental backup tests
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (15 preceding siblings ...)
2015-04-08 22:19 ` [Qemu-devel] [PATCH v5 16/21] hbitmap: truncate tests John Snow
@ 2015-04-08 22:20 ` John Snow
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 18/21] iotests: add QMP event waiting queue John Snow
` (3 subsequent siblings)
20 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-08 22:20 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
tests/qemu-iotests/124 | 104 +++++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/124.out | 5 +++
tests/qemu-iotests/group | 1 +
3 files changed, 110 insertions(+)
create mode 100644 tests/qemu-iotests/124
create mode 100644 tests/qemu-iotests/124.out
diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
new file mode 100644
index 0000000..85675ec
--- /dev/null
+++ b/tests/qemu-iotests/124
@@ -0,0 +1,104 @@
+#!/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.drives = list()
+ self.vm = iotests.VM()
+ self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
+
+ # Create a base image with a distinctive patterning
+ drive0 = self.add_node('drive0')
+ self.img_create(drive0['file'], drive0['fmt'])
+ self.vm.add_drive(drive0['file'])
+ io_write_patterns(drive0['file'], (('0x41', 0, 512),
+ ('0xd5', '1M', '32k'),
+ ('0xdc', '32M', '124k')))
+ self.vm.launch()
+
+
+ def add_node(self, node_id, fmt=iotests.imgfmt, path=None, backup=None):
+ if path is None:
+ path = os.path.join(iotests.test_dir, '%s.%s' % (node_id, fmt))
+ if backup is None:
+ backup = os.path.join(iotests.test_dir,
+ '%s.full.backup.%s' % (node_id, fmt))
+
+ self.drives.append({
+ 'id': node_id,
+ 'file': path,
+ 'backup': backup,
+ 'fmt': fmt })
+ return self.drives[-1]
+
+
+ def img_create(self, img, fmt=iotests.imgfmt, size='64M',
+ parent=None, parentFormat=None):
+ 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.err_img)
+ result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
+ sync='dirty-bitmap', format=self.drives[0]['fmt'],
+ target=self.err_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.err_img)
+ result = self.vm.qmp('drive-backup', device=self.drives[0]['id'],
+ sync='dirty-bitmap', bitmap='unknown',
+ format=self.drives[0]['fmt'], target=self.err_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/124.out b/tests/qemu-iotests/124.out
new file mode 100644
index 0000000..fbc63e6
--- /dev/null
+++ b/tests/qemu-iotests/124.out
@@ -0,0 +1,5 @@
+..
+----------------------------------------------------------------------
+Ran 2 tests
+
+OK
diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group
index 6262127..020f357 100644
--- a/tests/qemu-iotests/group
+++ b/tests/qemu-iotests/group
@@ -123,4 +123,5 @@
116 rw auto quick
121 rw auto
123 rw auto quick
+124 rw auto backing
128 rw auto quick
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 18/21] iotests: add QMP event waiting queue
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (16 preceding siblings ...)
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 17/21] iotests: add invalid input incremental backup tests John Snow
@ 2015-04-08 22:20 ` John Snow
2015-04-17 13:33 ` Max Reitz
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 19/21] iotests: add simple incremental backup case John Snow
` (2 subsequent siblings)
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:20 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
A filter is added to allow callers to request very specific
events to be pulled from the event queue, while leaving undesired
events still in the stream.
This allows to poll for completion data for multiple asynchronous
events in any arbitrary order.
A new timeout context is added to the qmp pull_event method's
wait parameter to allow tests to fail if they do not complete
within some expected period of time.
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qmp/qmp.py | 6 +++++-
tests/qemu-iotests/iotests.py | 38 ++++++++++++++++++++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/scripts/qmp/qmp.py b/scripts/qmp/qmp.py
index 20b6ec7..7f9ac1b 100644
--- a/scripts/qmp/qmp.py
+++ b/scripts/qmp/qmp.py
@@ -140,7 +140,8 @@ class QEMUMonitorProtocol:
"""
Get and delete the first available QMP event.
- @param wait: block until an event is available (bool)
+ @param wait: block until an event is available. If a float is provided,
+ use this as a timeout value. (bool, float)
"""
self.__sock.setblocking(0)
try:
@@ -151,7 +152,10 @@ class QEMUMonitorProtocol:
pass
self.__sock.setblocking(1)
if not self.__events and wait:
+ if isinstance(wait, float):
+ self.__sock.settimeout(wait)
self.__json_read(only_event=True)
+ self.__sock.settimeout(None)
event = self.__events[0]
del self.__events[0]
return event
diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 1402854..e93e623 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -78,6 +78,23 @@ def create_image(name, size):
i = i + 512
file.close()
+# Test if 'match' is a recursive subset of 'event'
+def event_match(event, match=None):
+ if match is None:
+ return True
+
+ for key in match:
+ if key in event:
+ if isinstance(event[key], dict):
+ if not event_match(event[key], match[key]):
+ return False
+ elif event[key] != match[key]:
+ return False
+ else:
+ return False
+
+ return True
+
class VM(object):
'''A QEMU VM'''
@@ -92,6 +109,7 @@ class VM(object):
'-machine', 'accel=qtest',
'-display', 'none', '-vga', 'none']
self._num_drives = 0
+ self._events = []
# This can be used to add an unused monitor instance.
def add_monitor_telnet(self, ip, port):
@@ -202,14 +220,34 @@ class VM(object):
def get_qmp_event(self, wait=False):
'''Poll for one queued QMP events and return it'''
+ if len(self._events) > 0:
+ return self._events.pop(0)
return self._qmp.pull_event(wait=wait)
def get_qmp_events(self, wait=False):
'''Poll for queued QMP events and return a list of dicts'''
events = self._qmp.get_events(wait=wait)
+ events.extend(self._events)
+ del self._events[:]
self._qmp.clear_events()
return events
+ def event_wait(self, name='BLOCK_JOB_COMPLETED', timeout=60.0, match=None):
+ # Search cached events
+ for event in self._events:
+ if (event['event'] == name) and event_match(event, match):
+ self._events.remove(event)
+ return event
+
+ # Poll for new events
+ while True:
+ event = self._qmp.pull_event(wait=timeout)
+ if (event['event'] == name) and event_match(event, match):
+ return event
+ self._events.append(event)
+
+ return None
+
index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
class QMPTestCase(unittest.TestCase):
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 18/21] iotests: add QMP event waiting queue
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 18/21] iotests: add QMP event waiting queue John Snow
@ 2015-04-17 13:33 ` Max Reitz
2015-04-17 18:23 ` John Snow
0 siblings, 1 reply; 48+ messages in thread
From: Max Reitz @ 2015-04-17 13:33 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 09.04.2015 00:20, John Snow wrote:
> A filter is added to allow callers to request very specific
> events to be pulled from the event queue, while leaving undesired
> events still in the stream.
>
> This allows to poll for completion data for multiple asynchronous
> events in any arbitrary order.
>
> A new timeout context is added to the qmp pull_event method's
> wait parameter to allow tests to fail if they do not complete
> within some expected period of time.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qmp/qmp.py | 6 +++++-
> tests/qemu-iotests/iotests.py | 38 ++++++++++++++++++++++++++++++++++++++
> 2 files changed, 43 insertions(+), 1 deletion(-)
>
> diff --git a/scripts/qmp/qmp.py b/scripts/qmp/qmp.py
> index 20b6ec7..7f9ac1b 100644
> --- a/scripts/qmp/qmp.py
> +++ b/scripts/qmp/qmp.py
> @@ -140,7 +140,8 @@ class QEMUMonitorProtocol:
> """
> Get and delete the first available QMP event.
>
> - @param wait: block until an event is available (bool)
> + @param wait: block until an event is available. If a float is provided,
> + use this as a timeout value. (bool, float)
Ouch... I guess it works, though...
> """
> self.__sock.setblocking(0)
> try:
> @@ -151,7 +152,10 @@ class QEMUMonitorProtocol:
> pass
> self.__sock.setblocking(1)
> if not self.__events and wait:
> + if isinstance(wait, float):
> + self.__sock.settimeout(wait)
> self.__json_read(only_event=True)
> + self.__sock.settimeout(None)
If a timeout occurs, __json_read will do nothing and return (I guess...).
> event = self.__events[0]
This will therefore probably return None...
> del self.__events[0]
And I don't know what this will do. Will it be a no-op?
> return event
> diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
> index 1402854..e93e623 100644
> --- a/tests/qemu-iotests/iotests.py
> +++ b/tests/qemu-iotests/iotests.py
> @@ -78,6 +78,23 @@ def create_image(name, size):
> i = i + 512
> file.close()
>
> +# Test if 'match' is a recursive subset of 'event'
> +def event_match(event, match=None):
> + if match is None:
> + return True
> +
> + for key in match:
> + if key in event:
> + if isinstance(event[key], dict):
> + if not event_match(event[key], match[key]):
> + return False
> + elif event[key] != match[key]:
> + return False
> + else:
> + return False
> +
> + return True
> +
> class VM(object):
> '''A QEMU VM'''
>
> @@ -92,6 +109,7 @@ class VM(object):
> '-machine', 'accel=qtest',
> '-display', 'none', '-vga', 'none']
> self._num_drives = 0
> + self._events = []
>
> # This can be used to add an unused monitor instance.
> def add_monitor_telnet(self, ip, port):
> @@ -202,14 +220,34 @@ class VM(object):
>
> def get_qmp_event(self, wait=False):
> '''Poll for one queued QMP events and return it'''
> + if len(self._events) > 0:
> + return self._events.pop(0)
> return self._qmp.pull_event(wait=wait)
>
> def get_qmp_events(self, wait=False):
> '''Poll for queued QMP events and return a list of dicts'''
> events = self._qmp.get_events(wait=wait)
> + events.extend(self._events)
> + del self._events[:]
> self._qmp.clear_events()
> return events
>
> + def event_wait(self, name='BLOCK_JOB_COMPLETED', timeout=60.0, match=None):
> + # Search cached events
> + for event in self._events:
> + if (event['event'] == name) and event_match(event, match):
> + self._events.remove(event)
> + return event
> +
> + # Poll for new events
> + while True:
> + event = self._qmp.pull_event(wait=timeout)
So if a timeout occurred, event will probably be None.
> + if (event['event'] == name) and event_match(event, match):
And this (indexing event == None) will probably generate an exception. I
guess it's one way to fail the test, but certainly not the best...
Max
> + return event
> + self._events.append(event)
> +
> + return None
> +
> index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
>
> class QMPTestCase(unittest.TestCase):
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 18/21] iotests: add QMP event waiting queue
2015-04-17 13:33 ` Max Reitz
@ 2015-04-17 18:23 ` John Snow
2015-04-22 15:04 ` Max Reitz
0 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-17 18:23 UTC (permalink / raw)
To: Max Reitz, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 04/17/2015 09:33 AM, Max Reitz wrote:
> On 09.04.2015 00:20, John Snow wrote:
>> A filter is added to allow callers to request very specific
>> events to be pulled from the event queue, while leaving undesired
>> events still in the stream.
>>
>> This allows to poll for completion data for multiple asynchronous
>> events in any arbitrary order.
>>
>> A new timeout context is added to the qmp pull_event method's
>> wait parameter to allow tests to fail if they do not complete
>> within some expected period of time.
>>
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> scripts/qmp/qmp.py | 6 +++++-
>> tests/qemu-iotests/iotests.py | 38
>> ++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 43 insertions(+), 1 deletion(-)
>>
>> diff --git a/scripts/qmp/qmp.py b/scripts/qmp/qmp.py
>> index 20b6ec7..7f9ac1b 100644
>> --- a/scripts/qmp/qmp.py
>> +++ b/scripts/qmp/qmp.py
>> @@ -140,7 +140,8 @@ class QEMUMonitorProtocol:
>> """
>> Get and delete the first available QMP event.
>> - @param wait: block until an event is available (bool)
>> + @param wait: block until an event is available. If a float is
>> provided,
>> + use this as a timeout value. (bool, float)
>
> Ouch... I guess it works, though...
>
It does read awkwardly in the declaration, but I also thought that:
wait=False
wait=True
wait=60.0
wait=0.0
all read fairly clearly and had very clear implications, so from a usage
standpoint it seemed very nice and convenient.
Making a second parameter gets strange:
wait=False, timeout=50.0
^ What does this mean?
wait=True, timeout=0.0
^ Does this mean to wait forever, or to not wait at all?
So in this case I stand by what looks like hackishness as a nice,
semantically appropriate interface.
>> """
>> self.__sock.setblocking(0)
>> try:
>> @@ -151,7 +152,10 @@ class QEMUMonitorProtocol:
>> pass
>> self.__sock.setblocking(1)
>> if not self.__events and wait:
>> + if isinstance(wait, float):
>> + self.__sock.settimeout(wait)
>> self.__json_read(only_event=True)
>> + self.__sock.settimeout(None)
>
> If a timeout occurs, __json_read will do nothing and return (I guess...).
>
It will actually throw an exception.
>> event = self.__events[0]
>
> This will therefore probably return None...
>
So we'll never make it this far.
>> del self.__events[0]
>
> And I don't know what this will do. Will it be a no-op?
>
Better than a no-op!
>> return event
>> diff --git a/tests/qemu-iotests/iotests.py
>> b/tests/qemu-iotests/iotests.py
>> index 1402854..e93e623 100644
>> --- a/tests/qemu-iotests/iotests.py
>> +++ b/tests/qemu-iotests/iotests.py
>> @@ -78,6 +78,23 @@ def create_image(name, size):
>> i = i + 512
>> file.close()
>> +# Test if 'match' is a recursive subset of 'event'
>> +def event_match(event, match=None):
>> + if match is None:
>> + return True
>> +
>> + for key in match:
>> + if key in event:
>> + if isinstance(event[key], dict):
>> + if not event_match(event[key], match[key]):
>> + return False
>> + elif event[key] != match[key]:
>> + return False
>> + else:
>> + return False
>> +
>> + return True
>> +
>> class VM(object):
>> '''A QEMU VM'''
>> @@ -92,6 +109,7 @@ class VM(object):
>> '-machine', 'accel=qtest',
>> '-display', 'none', '-vga', 'none']
>> self._num_drives = 0
>> + self._events = []
>> # This can be used to add an unused monitor instance.
>> def add_monitor_telnet(self, ip, port):
>> @@ -202,14 +220,34 @@ class VM(object):
>> def get_qmp_event(self, wait=False):
>> '''Poll for one queued QMP events and return it'''
>> + if len(self._events) > 0:
>> + return self._events.pop(0)
>> return self._qmp.pull_event(wait=wait)
>> def get_qmp_events(self, wait=False):
>> '''Poll for queued QMP events and return a list of dicts'''
>> events = self._qmp.get_events(wait=wait)
>> + events.extend(self._events)
>> + del self._events[:]
>> self._qmp.clear_events()
>> return events
>> + def event_wait(self, name='BLOCK_JOB_COMPLETED', timeout=60.0,
>> match=None):
>> + # Search cached events
>> + for event in self._events:
>> + if (event['event'] == name) and event_match(event, match):
>> + self._events.remove(event)
>> + return event
>> +
>> + # Poll for new events
>> + while True:
>> + event = self._qmp.pull_event(wait=timeout)
>
> So if a timeout occurred, event will probably be None.
>
>> + if (event['event'] == name) and event_match(event, match):
>
> And this (indexing event == None) will probably generate an exception. I
> guess it's one way to fail the test, but certainly not the best...
>
> Max
>
In practice, the timeout throws a socket.timeout exception I don't
bother to catch, so execution of the test will fail at this point, which
is acceptable.
Here's what happens in the next patch if I modify 'do_qmp_backup' to
wait for a made up event that will never happen:
event = self.vm.event_wait(name="JOHN_SNOW_CHECKS_IN_A_21_PATCH_SERIES",
match={'data': {'device': kwargs['device']}})
self.assertIsNotNone(event)
+======================================================================
+ERROR: test_incremental_simple (__main__.TestIncrementalBackup)
+----------------------------------------------------------------------
+Traceback (most recent call last):
+ File "124", line 223, in test_incremental_simple
+ self.create_anchor_backup()
+ File "124", line 146, in create_anchor_backup
+ format=drive['fmt'], target=drive['backup'])
+ File "124", line 127, in do_qmp_backup
+ match={'data': {'device': kwargs['device']}})
+ File "/home/bos/jhuston/src/qemu/tests/qemu-iotests/iotests.py", line
244, in event_wait
+ event = self._qmp.pull_event(wait=timeout)
+ File
"/home/bos/jhuston/src/qemu/tests/qemu-iotests/../../scripts/qmp/qmp.py", line
157, in pull_event
+ self.__json_read(only_event=True)
+ File
"/home/bos/jhuston/src/qemu/tests/qemu-iotests/../../scripts/qmp/qmp.py", line
63, in __json_read
+ data = self.__sockfile.readline()
+ File "/usr/lib64/python2.7/socket.py", line 447, in readline
+ data = self._sock.recv(self._rbufsize)
+timeout: timed out
+
Python tracebacks are still awful to read, but you can see we failed due
to a timeout.
Hmm, though... I am now noticing that pull_event will definitely fail if
you decide not to wait and there are no items in the queue, so...
I think I will rewrite some of qmp.py while I'm here, and I'll make the
error for timeout a little nicer for the end user.
>> + return event
>> + self._events.append(event)
>> +
>> + return None
>> +
>> index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
>> class QMPTestCase(unittest.TestCase):
>
>
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 18/21] iotests: add QMP event waiting queue
2015-04-17 18:23 ` John Snow
@ 2015-04-22 15:04 ` Max Reitz
0 siblings, 0 replies; 48+ messages in thread
From: Max Reitz @ 2015-04-22 15:04 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 17.04.2015 20:23, John Snow wrote:
>
>
> On 04/17/2015 09:33 AM, Max Reitz wrote:
>> On 09.04.2015 00:20, John Snow wrote:
>>> A filter is added to allow callers to request very specific
>>> events to be pulled from the event queue, while leaving undesired
>>> events still in the stream.
>>>
>>> This allows to poll for completion data for multiple asynchronous
>>> events in any arbitrary order.
>>>
>>> A new timeout context is added to the qmp pull_event method's
>>> wait parameter to allow tests to fail if they do not complete
>>> within some expected period of time.
>>>
>>> Signed-off-by: John Snow <jsnow@redhat.com>
>>> ---
>>> scripts/qmp/qmp.py | 6 +++++-
>>> tests/qemu-iotests/iotests.py | 38
>>> ++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 43 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/scripts/qmp/qmp.py b/scripts/qmp/qmp.py
>>> index 20b6ec7..7f9ac1b 100644
>>> --- a/scripts/qmp/qmp.py
>>> +++ b/scripts/qmp/qmp.py
>>> @@ -140,7 +140,8 @@ class QEMUMonitorProtocol:
>>> """
>>> Get and delete the first available QMP event.
>>> - @param wait: block until an event is available (bool)
>>> + @param wait: block until an event is available. If a float is
>>> provided,
>>> + use this as a timeout value. (bool, float)
>>
>> Ouch... I guess it works, though...
>>
>
> It does read awkwardly in the declaration, but I also thought that:
>
> wait=False
> wait=True
> wait=60.0
> wait=0.0
>
> all read fairly clearly and had very clear implications, so from a
> usage standpoint it seemed very nice and convenient.
>
> Making a second parameter gets strange:
>
> wait=False, timeout=50.0
>
> ^ What does this mean?
>
> wait=True, timeout=0.0
>
> ^ Does this mean to wait forever, or to not wait at all?
>
> So in this case I stand by what looks like hackishness as a nice,
> semantically appropriate interface.
Yes, looks reasonable.
>>> """
>>> self.__sock.setblocking(0)
>>> try:
>>> @@ -151,7 +152,10 @@ class QEMUMonitorProtocol:
>>> pass
>>> self.__sock.setblocking(1)
>>> if not self.__events and wait:
>>> + if isinstance(wait, float):
>>> + self.__sock.settimeout(wait)
>>> self.__json_read(only_event=True)
>>> + self.__sock.settimeout(None)
>>
>> If a timeout occurs, __json_read will do nothing and return (I
>> guess...).
>>
>
> It will actually throw an exception.
>
>>> event = self.__events[0]
>>
>> This will therefore probably return None...
>>
>
> So we'll never make it this far.
>
>>> del self.__events[0]
>>
>> And I don't know what this will do. Will it be a no-op?
>>
>
> Better than a no-op!
>
>>> return event
>>> diff --git a/tests/qemu-iotests/iotests.py
>>> b/tests/qemu-iotests/iotests.py
>>> index 1402854..e93e623 100644
>>> --- a/tests/qemu-iotests/iotests.py
>>> +++ b/tests/qemu-iotests/iotests.py
>>> @@ -78,6 +78,23 @@ def create_image(name, size):
>>> i = i + 512
>>> file.close()
>>> +# Test if 'match' is a recursive subset of 'event'
>>> +def event_match(event, match=None):
>>> + if match is None:
>>> + return True
>>> +
>>> + for key in match:
>>> + if key in event:
>>> + if isinstance(event[key], dict):
>>> + if not event_match(event[key], match[key]):
>>> + return False
>>> + elif event[key] != match[key]:
>>> + return False
>>> + else:
>>> + return False
>>> +
>>> + return True
>>> +
>>> class VM(object):
>>> '''A QEMU VM'''
>>> @@ -92,6 +109,7 @@ class VM(object):
>>> '-machine', 'accel=qtest',
>>> '-display', 'none', '-vga', 'none']
>>> self._num_drives = 0
>>> + self._events = []
>>> # This can be used to add an unused monitor instance.
>>> def add_monitor_telnet(self, ip, port):
>>> @@ -202,14 +220,34 @@ class VM(object):
>>> def get_qmp_event(self, wait=False):
>>> '''Poll for one queued QMP events and return it'''
>>> + if len(self._events) > 0:
>>> + return self._events.pop(0)
>>> return self._qmp.pull_event(wait=wait)
>>> def get_qmp_events(self, wait=False):
>>> '''Poll for queued QMP events and return a list of dicts'''
>>> events = self._qmp.get_events(wait=wait)
>>> + events.extend(self._events)
>>> + del self._events[:]
>>> self._qmp.clear_events()
>>> return events
>>> + def event_wait(self, name='BLOCK_JOB_COMPLETED', timeout=60.0,
>>> match=None):
>>> + # Search cached events
>>> + for event in self._events:
>>> + if (event['event'] == name) and event_match(event, match):
>>> + self._events.remove(event)
>>> + return event
>>> +
>>> + # Poll for new events
>>> + while True:
>>> + event = self._qmp.pull_event(wait=timeout)
>>
>> So if a timeout occurred, event will probably be None.
>>
>>> + if (event['event'] == name) and event_match(event, match):
>>
>> And this (indexing event == None) will probably generate an exception. I
>> guess it's one way to fail the test, but certainly not the best...
>>
>> Max
>>
>
> In practice, the timeout throws a socket.timeout exception I don't
> bother to catch, so execution of the test will fail at this point,
> which is acceptable.
Well, if you find it acceptable, I'll be fine with it.
> Here's what happens in the next patch if I modify 'do_qmp_backup' to
> wait for a made up event that will never happen:
>
> event = self.vm.event_wait(name="JOHN_SNOW_CHECKS_IN_A_21_PATCH_SERIES",
> match={'data': {'device': kwargs['device']}})
> self.assertIsNotNone(event)
>
> +======================================================================
> +ERROR: test_incremental_simple (__main__.TestIncrementalBackup)
> +----------------------------------------------------------------------
> +Traceback (most recent call last):
> + File "124", line 223, in test_incremental_simple
> + self.create_anchor_backup()
> + File "124", line 146, in create_anchor_backup
> + format=drive['fmt'], target=drive['backup'])
> + File "124", line 127, in do_qmp_backup
> + match={'data': {'device': kwargs['device']}})
> + File "/home/bos/jhuston/src/qemu/tests/qemu-iotests/iotests.py",
> line 244, in event_wait
> + event = self._qmp.pull_event(wait=timeout)
> + File
> "/home/bos/jhuston/src/qemu/tests/qemu-iotests/../../scripts/qmp/qmp.py",
> line 157, in pull_event
> + self.__json_read(only_event=True)
> + File
> "/home/bos/jhuston/src/qemu/tests/qemu-iotests/../../scripts/qmp/qmp.py",
> line 63, in __json_read
> + data = self.__sockfile.readline()
> + File "/usr/lib64/python2.7/socket.py", line 447, in readline
> + data = self._sock.recv(self._rbufsize)
> +timeout: timed out
> +
>
> Python tracebacks are still awful to read, but you can see we failed
> due to a timeout.
But I wouldn't understand it. I mean, the content is there, but I know
my mind would go blank just the instand a stacktrace appears, and I
wouldn't bother to try to understand what "timeout" under "__json_read"
could mean.
> Hmm, though... I am now noticing that pull_event will definitely fail
> if you decide not to wait and there are no items in the queue, so...
>
> I think I will rewrite some of qmp.py while I'm here, and I'll make
> the error for timeout a little nicer for the end user.
But please don't try too hard. Even though what I said might not sound
like it, the most important thing is still that the test just fails if
something goes wrong. It's always nice if it's simple to figure out why
it failed, but as long as the test author can figure it out somehow,
it'll suffice. :-)
Max
>
>>> + return event
>>> + self._events.append(event)
>>> +
>>> + return None
>>> +
>>> index_re = re.compile(r'([^\[]+)\[([^\]]+)\]')
>>> class QMPTestCase(unittest.TestCase):
>>
>>
^ permalink raw reply [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 19/21] iotests: add simple incremental backup case
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (17 preceding siblings ...)
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 18/21] iotests: add QMP event waiting queue John Snow
@ 2015-04-08 22:20 ` John Snow
2015-04-17 14:33 ` Max Reitz
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 20/21] iotests: add incremental backup failure recovery test John Snow
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 21/21] iotests: add incremental backup granularity tests John Snow
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:20 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/124 | 174 +++++++++++++++++++++++++++++++++++++++++++--
tests/qemu-iotests/124.out | 4 +-
2 files changed, 172 insertions(+), 6 deletions(-)
diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index 85675ec..5c3b434 100644
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -29,6 +29,51 @@ def io_write_patterns(img, patterns):
iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img)
+def try_remove(img):
+ try:
+ os.remove(img)
+ except OSError:
+ pass
+
+
+class Bitmap:
+ def __init__(self, name, drive):
+ self.name = name
+ self.drive = drive
+ self.num = 0
+ self.backups = list()
+
+ def base_target(self):
+ return (self.drive['backup'], None)
+
+ def new_target(self, num=None):
+ if num is None:
+ num = self.num
+ self.num = num + 1
+ base = os.path.join(iotests.test_dir,
+ "%s.%s." % (self.drive['id'], self.name))
+ suff = "%i.%s" % (num, self.drive['fmt'])
+ target = base + "inc" + suff
+ reference = base + "ref" + suff
+ self.backups.append((target, reference))
+ return (target, reference)
+
+ def last_target(self):
+ if self.backups:
+ return self.backups[-1]
+ return self.base_target()
+
+ def del_target(self):
+ for image in self.backups.pop():
+ try_remove(image)
+ self.num -= 1
+
+ def cleanup(self):
+ for backup in self.backups:
+ for image in backup:
+ try_remove(image)
+
+
class TestIncrementalBackup(iotests.QMPTestCase):
def setUp(self):
self.bitmaps = list()
@@ -73,6 +118,128 @@ class TestIncrementalBackup(iotests.QMPTestCase):
iotests.qemu_img('create', '-f', fmt, img, size)
self.files.append(img)
+
+ def do_qmp_backup(self, error='Input/output error', **kwargs):
+ res = self.vm.qmp('drive-backup', **kwargs)
+ self.assert_qmp(res, 'return', {})
+
+ event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED",
+ match={'data': {'device': kwargs['device']}})
+ self.assertIsNotNone(event)
+
+ try:
+ failure = self.dictpath(event, 'data/error')
+ except AssertionError:
+ # Backup succeeded.
+ self.assert_qmp(event, 'data/offset', event['data']['len'])
+ return True
+ else:
+ # Backup failed.
+ self.assert_qmp(event, 'data/error', error)
+ return False
+
+
+ def create_anchor_backup(self, drive=None):
+ if drive is None:
+ drive = self.drives[-1]
+ res = self.do_qmp_backup(device=drive['id'], sync='full',
+ format=drive['fmt'], target=drive['backup'])
+ self.assertTrue(res)
+ self.files.append(drive['backup'])
+ return drive['backup']
+
+
+ def make_reference_backup(self, bitmap=None):
+ if bitmap is None:
+ bitmap = self.bitmaps[-1]
+ _, reference = bitmap.last_target()
+ res = self.do_qmp_backup(device=bitmap.drive['id'], sync='full',
+ format=bitmap.drive['fmt'], target=reference)
+ self.assertTrue(res)
+
+
+ def add_bitmap(self, name, drive):
+ bitmap = Bitmap(name, drive)
+ self.bitmaps.append(bitmap)
+ result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
+ name=bitmap.name)
+ self.assert_qmp(result, 'return', {})
+ return bitmap
+
+
+ def prepare_backup(self, bitmap=None, parent=None):
+ if bitmap is None:
+ bitmap = self.bitmaps[-1]
+ if parent is None:
+ parent, _ = bitmap.last_target()
+
+ target, _ = bitmap.new_target()
+ self.img_create(target, bitmap.drive['fmt'], parent=parent)
+ return target
+
+
+ def create_incremental(self, bitmap=None, parent=None,
+ parentFormat=None, validate=True):
+ if bitmap is None:
+ bitmap = self.bitmaps[-1]
+ if parent is None:
+ parent, _ = bitmap.last_target()
+
+ target = self.prepare_backup(bitmap, parent)
+ res = self.do_qmp_backup(device=bitmap.drive['id'],
+ sync='dirty-bitmap', bitmap=bitmap.name,
+ format=bitmap.drive['fmt'], target=target,
+ mode='existing')
+ if not res:
+ bitmap.del_target();
+ self.assertFalse(validate)
+ else:
+ self.make_reference_backup(bitmap)
+ return res
+
+
+ def check_backups(self):
+ for bitmap in self.bitmaps:
+ for incremental, reference in bitmap.backups:
+ self.assertTrue(iotests.compare_images(incremental, reference))
+ last = bitmap.last_target()[0]
+ self.assertTrue(iotests.compare_images(last, bitmap.drive['file']))
+
+
+ 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')
+
+
+ def test_incremental_simple(self):
+ '''
+ Test: Create and verify three incremental backups.
+
+ Create a bitmap and a full backup before VM execution begins,
+ then create a series of three incremental backups "during execution,"
+ i.e.; after IO requests begin modifying the drive.
+ '''
+ self.create_anchor_backup()
+ self.add_bitmap('bitmap0', self.drives[0])
+
+ # 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(self.drives[0]['id'], (('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(self.drives[0]['id'], (('0x9a', 0, 512),
+ ('0x55', '8M', '352k'),
+ ('0x78', '15872k', '1M')))
+ self.create_incremental()
+ self.vm.shutdown()
+ self.check_backups()
+
+
def test_sync_dirty_bitmap_missing(self):
self.assert_no_active_block_jobs()
self.files.append(self.err_img)
@@ -93,11 +260,10 @@ class TestIncrementalBackup(iotests.QMPTestCase):
def tearDown(self):
self.vm.shutdown()
+ for bitmap in self.bitmaps:
+ bitmap.cleanup()
for filename in self.files:
- try:
- os.remove(filename)
- except OSError:
- pass
+ try_remove(filename)
if __name__ == '__main__':
diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
index fbc63e6..8d7e996 100644
--- a/tests/qemu-iotests/124.out
+++ b/tests/qemu-iotests/124.out
@@ -1,5 +1,5 @@
-..
+...
----------------------------------------------------------------------
-Ran 2 tests
+Ran 3 tests
OK
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 19/21] iotests: add simple incremental backup case
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 19/21] iotests: add simple incremental backup case John Snow
@ 2015-04-17 14:33 ` Max Reitz
2015-04-17 16:56 ` John Snow
0 siblings, 1 reply; 48+ messages in thread
From: Max Reitz @ 2015-04-17 14:33 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 09.04.2015 00:20, John Snow wrote:
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> tests/qemu-iotests/124 | 174 +++++++++++++++++++++++++++++++++++++++++++--
> tests/qemu-iotests/124.out | 4 +-
> 2 files changed, 172 insertions(+), 6 deletions(-)
I was happier with the previous simpler approach, but I guess I'll have
to get my happiness somewhere else today.
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 19/21] iotests: add simple incremental backup case
2015-04-17 14:33 ` Max Reitz
@ 2015-04-17 16:56 ` John Snow
0 siblings, 0 replies; 48+ messages in thread
From: John Snow @ 2015-04-17 16:56 UTC (permalink / raw)
To: Max Reitz, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 04/17/2015 10:33 AM, Max Reitz wrote:
> On 09.04.2015 00:20, John Snow wrote:
>> Signed-off-by: John Snow <jsnow@redhat.com>
>> ---
>> tests/qemu-iotests/124 | 174
>> +++++++++++++++++++++++++++++++++++++++++++--
>> tests/qemu-iotests/124.out | 4 +-
>> 2 files changed, 172 insertions(+), 6 deletions(-)
>
> I was happier with the previous simpler approach, but I guess I'll have
> to get my happiness somewhere else today.
>
> Reviewed-by: Max Reitz <mreitz@redhat.com>
>
I was much happier with that approach as well. I would actually still
prefer to roll back the tests to the earlier version if at all possible
and use the simple "check as we go" mechanism.
^ permalink raw reply [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 20/21] iotests: add incremental backup failure recovery test
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (18 preceding siblings ...)
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 19/21] iotests: add simple incremental backup case John Snow
@ 2015-04-08 22:20 ` John Snow
2015-04-17 14:33 ` Max Reitz
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 21/21] iotests: add incremental backup granularity tests John Snow
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:20 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Test the failure case for incremental backups.
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/124 | 57 ++++++++++++++++++++++++++++++++++++++++++++++
tests/qemu-iotests/124.out | 4 ++--
2 files changed, 59 insertions(+), 2 deletions(-)
diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index 5c3b434..95f6de5 100644
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -240,6 +240,63 @@ class TestIncrementalBackup(iotests.QMPTestCase):
self.check_backups()
+ def test_incremental_failure(self):
+ '''Test: Verify backups made after a failure are correct.
+
+ Simulate a failure during an incremental backup block job,
+ emulate additional writes, then create another incremental backup
+ afterwards and verify that the backup created is correct.
+ '''
+
+ # Create a blkdebug interface to this img as 'drive1',
+ # but don't actually create a new image.
+ drive1 = self.add_node('drive1', self.drives[0]['fmt'],
+ path=self.drives[0]['file'],
+ backup=self.drives[0]['backup'])
+ result = self.vm.qmp('blockdev-add', options={
+ 'id': drive1['id'],
+ 'driver': drive1['fmt'],
+ 'file': {
+ 'driver': 'blkdebug',
+ 'image': {
+ 'driver': 'file',
+ 'filename': drive1['file']
+ },
+ 'set-state': [{
+ 'event': 'flush_to_disk',
+ 'state': 1,
+ 'new_state': 2
+ }],
+ 'inject-error': [{
+ 'event': 'read_aio',
+ 'errno': 5,
+ 'state': 2,
+ 'immediately': False,
+ 'once': True
+ }],
+ }
+ })
+ self.assert_qmp(result, 'return', {})
+
+ self.create_anchor_backup(self.drives[0])
+ self.add_bitmap('bitmap0', drive1)
+ # Note: at this point, during a normal execution,
+ # Assume that the VM resumes and begins issuing IO requests here.
+
+ self.hmp_io_writes(drive1['id'], (('0xab', 0, 512),
+ ('0xfe', '16M', '256k'),
+ ('0x64', '32736k', '64k')))
+
+ result = self.create_incremental(validate=False)
+ self.assertFalse(result)
+ self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512),
+ ('0x55', '8M', '352k'),
+ ('0x78', '15872k', '1M')))
+ self.create_incremental()
+ self.vm.shutdown()
+ self.check_backups()
+
+
def test_sync_dirty_bitmap_missing(self):
self.assert_no_active_block_jobs()
self.files.append(self.err_img)
diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
index 8d7e996..89968f3 100644
--- a/tests/qemu-iotests/124.out
+++ b/tests/qemu-iotests/124.out
@@ -1,5 +1,5 @@
-...
+....
----------------------------------------------------------------------
-Ran 3 tests
+Ran 4 tests
OK
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 20/21] iotests: add incremental backup failure recovery test
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 20/21] iotests: add incremental backup failure recovery test John Snow
@ 2015-04-17 14:33 ` Max Reitz
0 siblings, 0 replies; 48+ messages in thread
From: Max Reitz @ 2015-04-17 14:33 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 09.04.2015 00:20, John Snow wrote:
> Test the failure case for incremental backups.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> tests/qemu-iotests/124 | 57 ++++++++++++++++++++++++++++++++++++++++++++++
> tests/qemu-iotests/124.out | 4 ++--
> 2 files changed, 59 insertions(+), 2 deletions(-)
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 48+ messages in thread
* [Qemu-devel] [PATCH v5 21/21] iotests: add incremental backup granularity tests
2015-04-08 22:19 [Qemu-devel] [PATCH v5 00/21] block: transactionless incremental backup series John Snow
` (19 preceding siblings ...)
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 20/21] iotests: add incremental backup failure recovery test John Snow
@ 2015-04-08 22:20 ` John Snow
2015-04-17 14:36 ` Max Reitz
20 siblings, 1 reply; 48+ messages in thread
From: John Snow @ 2015-04-08 22:20 UTC (permalink / raw)
To: qemu-block
Cc: kwolf, famz, John Snow, qemu-devel, armbru, vsementsov, stefanha,
mreitz
Test what happens if you fiddle with the granularity.
Signed-off-by: John Snow <jsnow@redhat.com>
---
tests/qemu-iotests/124 | 58 +++++++++++++++++++++++++++++++++++++---------
tests/qemu-iotests/124.out | 4 ++--
2 files changed, 49 insertions(+), 13 deletions(-)
diff --git a/tests/qemu-iotests/124 b/tests/qemu-iotests/124
index 95f6de5..3ee78cd 100644
--- a/tests/qemu-iotests/124
+++ b/tests/qemu-iotests/124
@@ -158,11 +158,11 @@ class TestIncrementalBackup(iotests.QMPTestCase):
self.assertTrue(res)
- def add_bitmap(self, name, drive):
+ def add_bitmap(self, name, drive, **kwargs):
bitmap = Bitmap(name, drive)
self.bitmaps.append(bitmap)
result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'],
- name=bitmap.name)
+ name=bitmap.name, **kwargs)
self.assert_qmp(result, 'return', {})
return bitmap
@@ -212,16 +212,9 @@ class TestIncrementalBackup(iotests.QMPTestCase):
self.vm.hmp_qemu_io(drive, 'flush')
- def test_incremental_simple(self):
- '''
- Test: Create and verify three incremental backups.
-
- Create a bitmap and a full backup before VM execution begins,
- then create a series of three incremental backups "during execution,"
- i.e.; after IO requests begin modifying the drive.
- '''
+ def do_incremental_simple(self, **kwargs):
self.create_anchor_backup()
- self.add_bitmap('bitmap0', self.drives[0])
+ self.add_bitmap('bitmap0', self.drives[0], **kwargs)
# Sanity: Create a "hollow" incremental backup
self.create_incremental()
@@ -240,6 +233,37 @@ class TestIncrementalBackup(iotests.QMPTestCase):
self.check_backups()
+ def test_incremental_simple(self):
+ '''
+ Test: Create and verify three incremental backups.
+
+ Create a bitmap and a full backup before VM execution begins,
+ then create a series of three incremental backups "during execution,"
+ i.e.; after IO requests begin modifying the drive.
+ '''
+ return self.do_incremental_simple()
+
+
+ def test_small_granularity(self):
+ '''
+ Test: Create and verify backups made with a small granularity bitmap.
+
+ Perform the same test as test_incremental_simple, but with a granularity
+ of only 32KiB instead of the present default of 64KiB.
+ '''
+ return self.do_incremental_simple(granularity=32768)
+
+
+ def test_large_granularity(self):
+ '''
+ Test: Create and verify backups made with a large granularity bitmap.
+
+ Perform the same test as test_incremental_simple, but with a granularity
+ of 128KiB instead of the present default of 64KiB.
+ '''
+ return self.do_incremental_simple(granularity=131072)
+
+
def test_incremental_failure(self):
'''Test: Verify backups made after a failure are correct.
@@ -315,6 +339,18 @@ class TestIncrementalBackup(iotests.QMPTestCase):
self.assert_qmp(result, 'error/class', 'GenericError')
+ def test_sync_dirty_bitmap_bad_granularity(self):
+ '''
+ Test: Test what happens if we provide an improper granularity.
+
+ The granularity must always be a power of 2.
+ '''
+ self.assert_no_active_block_jobs()
+ self.assertRaises(AssertionError, self.add_bitmap,
+ 'bitmap0', self.drives[0],
+ granularity=64000)
+
+
def tearDown(self):
self.vm.shutdown()
for bitmap in self.bitmaps:
diff --git a/tests/qemu-iotests/124.out b/tests/qemu-iotests/124.out
index 89968f3..2f7d390 100644
--- a/tests/qemu-iotests/124.out
+++ b/tests/qemu-iotests/124.out
@@ -1,5 +1,5 @@
-....
+.......
----------------------------------------------------------------------
-Ran 4 tests
+Ran 7 tests
OK
--
2.1.0
^ permalink raw reply related [flat|nested] 48+ messages in thread
* Re: [Qemu-devel] [PATCH v5 21/21] iotests: add incremental backup granularity tests
2015-04-08 22:20 ` [Qemu-devel] [PATCH v5 21/21] iotests: add incremental backup granularity tests John Snow
@ 2015-04-17 14:36 ` Max Reitz
0 siblings, 0 replies; 48+ messages in thread
From: Max Reitz @ 2015-04-17 14:36 UTC (permalink / raw)
To: John Snow, qemu-block
Cc: kwolf, famz, qemu-devel, armbru, vsementsov, stefanha
On 09.04.2015 00:20, John Snow wrote:
> Test what happens if you fiddle with the granularity.
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> tests/qemu-iotests/124 | 58 +++++++++++++++++++++++++++++++++++++---------
> tests/qemu-iotests/124.out | 4 ++--
> 2 files changed, 49 insertions(+), 13 deletions(-)
Reviewed-by: Max Reitz <mreitz@redhat.com>
^ permalink raw reply [flat|nested] 48+ messages in thread