block: incremental backup transactions
cover block: incremental backup transactions
This series adds support for incremental backup primitives in QMP transactions. It requires my transactionless incremental backup series.
Patch 1 adds basic support for add and clear transactions. Patch 2 tests this basic support.
Patches 3-7 add support for error scenarios where only some incremental backup jobs complete during a transaction. See commits for patches #3 and #7 for more details, as these are likely to be the most controversial additions.
Patches 8-11 test the feature added in 3-7.
--js
John Snow (11):
qapi: Add transaction support to block-dirty-bitmap operations
iotests: add transactional incremental backup test
block: add transactional callbacks feature
block: add refcount to Job object
block: add delayed bitmap successor cleanup
qmp: Add an implementation wrapper for qmp_drive_backup
block: drive_backup transaction callback support
iotests: add QMP event waiting queue
iotests: test 124 - drive object refactoring
iotests: 124 - backup_prepare refactoring
iotests: 124 - transactional failure test
block.c | 79 +++++++- block/backup.c | 32 +-- blockdev.c | 441 ++++++++++++++++++++++++++++++++++++++++-- blockjob.c | 18 +- include/block/block.h | 11 +- include/block/block_int.h | 8 + include/block/blockjob.h | 21 ++ qapi-schema.json | 6 +- tests/qemu-iotests/124 | 363 +++++++++++++++++++++++++++------- tests/qemu-iotests/124.out | 4 +- tests/qemu-iotests/iotests.py | 38 ++++ 11 files changed, 896 insertions(+), 125 deletions(-)
Signature · 1 line
1.9.3
PATCH 1/11 qapi: Add transaction support to block-dirty-bitmap operations
This adds two qmp commands to transactions.
block-dirty-bitmap-add allows you to create a bitmap simultaneously alongside a new full backup to accomplish a clean synchronization point.
block-dirty-bitmap-clear allows you to reset a bitmap back to as-if it were new, which can also be used alongside a full backup to accomplish a clean synchronization point.
--- blockdev.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ qapi-schema.json | 6 +++- 2 files changed, 106 insertions(+), 1 deletion(-)
blockdev.c +101 -0
@@ -1664,6 +1664,96 @@ static void blockdev_backup_clean(BlkTransactionState *common) } } +typedef struct BlockDirtyBitmapState { +BlkTransactionState common; +BdrvDirtyBitmap *bitmap; +BlockDriverState *bs; +AioContext *aio_context; +bool prepared; +}BlockDirtyBitmapState ; + +static void block_dirty_bitmap_add_prepare (BlkTransactionState *common, +Error **errp) +{ +Error *local_err =NULL ; +BlockDirtyBitmapAdd *action; +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); + + action = common->action->block_dirty_bitmap_add; +/* AIO context taken within qmp_block_dirty_bitmap_add */ +qmp_block_dirty_bitmap_add (action->node, action->name, + action->has_granularity, action->granularity, + &local_err); + +if (!local_err) { + state->prepared = true; + }else { +error_propagate (errp, local_err); + } +} + +static void block_dirty_bitmap_add_abort (BlkTransactionState *common) +{ +BlockDirtyBitmapAdd *action; +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); + + action = common->action->block_dirty_bitmap_add; +/* Should not be able to fail: IF the bitmap was added via .prepare(), +* then the node reference and bitmap name must have been valid. +*/ +if (state->prepared) { +qmp_block_dirty_bitmap_remove (action->node, action->name, &error_abort); + } +} + +static void block_dirty_bitmap_clear_prepare (BlkTransactionState *common, +Error **errp) +{ +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); +BlockDirtyBitmap *action; + + action = common->action->block_dirty_bitmap_clear; + state->bitmap =block_dirty_bitmap_lookup (action->node, + action->name, + &state->bs, + errp); +if (!state->bitmap) { +return ; + } + +if (bdrv_dirty_bitmap_frozen (state->bitmap)) { +error_setg (errp,"Cannot modify a frozen bitmap" ); +return ; + }else if (!bdrv_dirty_bitmap_enabled (state->bitmap)) { +error_setg (errp,"Cannot clear a disabled bitmap" ); +return ; + } + +/* AioContext is released in .clean() */ + state->aio_context =bdrv_get_aio_context (state->bs); +aio_context_acquire (state->aio_context); +} + +static void block_dirty_bitmap_clear_commit (BlkTransactionState *common) +{ +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); +bdrv_clear_dirty_bitmap (state->bitmap); +} + +static void block_dirty_bitmap_clear_clean (BlkTransactionState *common) +{ +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); + +if (state->aio_context) { +aio_context_release (state->aio_context); + } +} +static void abort_prepare (BlkTransactionState *common,Error **errp) {error_setg (errp,"Transaction aborted using Abort action" );
@@ -1704,6 +1794,17 @@ static const BdrvActionOps actions[] = { .abort = internal_snapshot_abort, .clean = internal_snapshot_clean, }, + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD] = { + .instance_size = sizeof(BlockDirtyBitmapState), + .prepare = block_dirty_bitmap_add_prepare, + .abort = block_dirty_bitmap_add_abort, + }, + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR] = { + .instance_size = sizeof(BlockDirtyBitmapState), + .prepare = block_dirty_bitmap_clear_prepare, + .commit = block_dirty_bitmap_clear_commit, + .clean = block_dirty_bitmap_clear_clean, + } };/*
qapi-schema.json +5 -1
@@ -1332,6 +1332,8 @@ # abort since 1.6 # blockdev-snapshot-internal-sync since 1.7 # blockdev-backup since 2.3 +# block-dirty-bitmap-add since 2.3 +# block-dirty-bitmap-clear since 2.3 ## { 'union': 'TransactionAction', 'data': {
@@ -1339,7 +1341,9 @@ 'drive-backup': 'DriveBackup', 'blockdev-backup': 'BlockdevBackup', 'abort': 'Abort', - 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal' + 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal', + 'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd', + 'block-dirty-bitmap-clear': 'BlockDirtyBitmap' } } ##
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 01/11] qapi: Add transaction support to block-dirty-bitmap operations
Quoted 48 lines · John Snow
This adds two qmp commands to transactions.
block-dirty-bitmap-add allows you to create a bitmap simultaneously alongside a new full backup to accomplish a clean synchronization point.
block-dirty-bitmap-clear allows you to reset a bitmap back to as-if it were new, which can also be used alongside a full backup to accomplish a clean synchronization point.
Signed-off-by Fam Zheng <[email protected]>Signed-off-by John Snow <[email protected]>--- blockdev.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ qapi-schema.json | 6 +++- 2 files changed, 106 insertions(+), 1 deletion(-)blockdev.c +23
-0@@ -1664,6 +1664,96 @@ static void blockdev_backup_clean(BlkTransactionState *common) } } +typedef struct BlockDirtyBitmapState { +BlkTransactionState common; +BdrvDirtyBitmap *bitmap; +BlockDriverState *bs; +AioContext *aio_context; +bool prepared; +}BlockDirtyBitmapState ; + +static void block_dirty_bitmap_add_prepare (BlkTransactionState *common, +Error **errp) +{ +Error *local_err =NULL ; +BlockDirtyBitmapAdd *action; +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); + + action = common->action->block_dirty_bitmap_add; +/* AIO context taken within qmp_block_dirty_bitmap_add */ +qmp_block_dirty_bitmap_add (action->node, action->name, + action->has_granularity, action->granularity, + &local_err); + +if (!local_err) {
blockdev.c static void blockdev_backup_clean(BlkTransactionState *common) · line 1699
Quoted 10 lines
blockdev.c +10
-0+ state->prepared = true; + }else { +error_propagate (errp, local_err); + } +} + +static void block_dirty_bitmap_add_abort (BlkTransactionState *common) +{ +BlockDirtyBitmapAdd *action; + BlockDirtyBitmapState *state = DO_UPCAST(BlockDirtyBitmapState,
Indentation is still off.
Quoted 13 lines
blockdev.c +13
-0+ common, common); + + action = common->action->block_dirty_bitmap_add; +/* Should not be able to fail: IF the bitmap was added via .prepare(), +* then the node reference and bitmap name must have been valid. +*/ +if (state->prepared) { +qmp_block_dirty_bitmap_remove (action->node, action->name, &error_abort); + } +} + +static void block_dirty_bitmap_clear_prepare (BlkTransactionState *common, +Error **errp)
blockdev.c static void blockdev_backup_clean(BlkTransactionState *common) · line 1722
Quoted 10 lines
blockdev.c +10
-0+{ +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); +BlockDirtyBitmap *action; + + action = common->action->block_dirty_bitmap_clear; + state->bitmap =block_dirty_bitmap_lookup (action->node, + action->name, + &state->bs, + errp);
This will need fix-up for v3 (and following, probably) of your transactionless series (&state->aio_context as the fourth parameter).
Quoted 5 lines
blockdev.c +5
-0+if (!state->bitmap) { +return ; + } + +if (bdrv_dirty_bitmap_frozen (state->bitmap)) {
blockdev.c static void blockdev_backup_clean(BlkTransactionState *common) · line 1737
Quoted 10 lines
blockdev.c +10
-0+error_setg (errp,"Cannot modify a frozen bitmap" ); +return ; + }else if (!bdrv_dirty_bitmap_enabled (state->bitmap)) { +error_setg (errp,"Cannot clear a disabled bitmap" ); +return ; + } + +/* AioContext is released in .clean() */ + state->aio_context =bdrv_get_aio_context (state->bs); +aio_context_acquire (state->aio_context);
And this will probably need to be dropped for v4 of said series.
Other than that, looks good.
Max
Quoted 64 lines
blockdev.c +30
-0+} + +static void block_dirty_bitmap_clear_commit (BlkTransactionState *common) +{ +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); +bdrv_clear_dirty_bitmap (state->bitmap); +} + +static void block_dirty_bitmap_clear_clean (BlkTransactionState *common) +{ +BlockDirtyBitmapState *state =DO_UPCAST (BlockDirtyBitmapState, + common, common); + +if (state->aio_context) { +aio_context_release (state->aio_context); + } +} +static void abort_prepare (BlkTransactionState *common,Error **errp) {error_setg (errp,"Transaction aborted using Abort action" );@@ -1704,6 +1794,17 @@ static const BdrvActionOps actions[] = { .abort = internal_snapshot_abort, .clean = internal_snapshot_clean, }, + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_ADD] = { + .instance_size = sizeof(BlockDirtyBitmapState), + .prepare = block_dirty_bitmap_add_prepare, + .abort = block_dirty_bitmap_add_abort, + }, + [TRANSACTION_ACTION_KIND_BLOCK_DIRTY_BITMAP_CLEAR] = { + .instance_size = sizeof(BlockDirtyBitmapState), + .prepare = block_dirty_bitmap_clear_prepare, + .commit = block_dirty_bitmap_clear_commit, + .clean = block_dirty_bitmap_clear_clean, + } };/* qapi-schema.json +5
-1@@ -1332,6 +1332,8 @@ # abort since 1.6 # blockdev-snapshot-internal-sync since 1.7 # blockdev-backup since 2.3 +# block-dirty-bitmap-add since 2.3 +# block-dirty-bitmap-clear since 2.3 ## { 'union': 'TransactionAction', 'data': {@@ -1339,7 +1341,9 @@ 'drive-backup': 'DriveBackup', 'blockdev-backup': 'BlockdevBackup', 'abort': 'Abort', - 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal' + 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternal', + 'block-dirty-bitmap-add': 'BlockDirtyBitmapAdd', + 'block-dirty-bitmap-clear': 'BlockDirtyBitmap' } } ##
PATCH 2/11 iotests: add transactional incremental backup test
In reply to John Snow--- tests/qemu-iotests/124 | 49 ++++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/124.out | 4 ++-- 2 files changed, 51 insertions(+), 2 deletions(-)
tests/qemu-iotests/124 +49 -0
@@ -184,6 +184,55 @@ class TestIncrementalBackup(iotests.QMPTestCase): return True + def test_incremental_transaction(self): + '''Test: Verify backups made from transactionally created bitmaps. + + Create a bitmap "before" VM execution begins, then create a second + bitmap AFTER writes have already occurred. Use transactions to create + a full backup and synchronize both bitmaps to this backup. + Create an incremental backup through both bitmaps and verify that + both backups match the full backup. + ''' + bitmap0 = self.add_bitmap('bitmap0', 'drive0') + self.hmp_io_writes('drive0', (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + bitmap1 = self.add_bitmap('bitmap1', 'drive0') + + result = self.vm.qmp('transaction', actions=[ + { + 'type': 'block-dirty-bitmap-clear', + 'data': { 'node': 'drive0', + 'name': 'bitmap0' }, + }, + { + 'type': 'block-dirty-bitmap-clear', + 'data': { 'node': 'drive0', + 'name': 'bitmap1' }, + }, + { + 'type': 'drive-backup', + 'data': { 'device': 'drive0', + 'sync': 'full', + 'format': iotests.imgfmt, + 'target': self.full_bak }, + } + ]) + self.assert_qmp(result, 'return', {}) + self.wait_until_completed() + self.files.append(self.full_bak) + self.check_full_backup() + + self.hmp_io_writes('drive0', (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) + # Both bitmaps should be in sync and create fully valid + # incremental backups + res1 = self.create_incremental(bitmap0) + res2 = self.create_incremental(bitmap1) + self.assertTrue(res1 and res2) + + def test_incremental_failure(self): '''Test: Verify backups made after a failure are correct.
tests/qemu-iotests/124.out +2 -2
@@ -1,5 +1,5 @@ -.... +..... ---------------------------------------------------------------------- -Ran 4 tests +Ran 5 tests OK
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test
Quoted 6 lines · John Snow
Reviewed-by Max Reitz <[email protected]>Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 49 ++++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/124.out | 4 ++-- 2 files changed, 51 insertions(+), 2 deletions(-)
This tests seems to fail locally:
. . .
+======================================================================
+FAIL: test_incremental_failure (__main__.TestIncrementalBackup)
+Test: Verify backups made after a failure are correct.
+----------------------------------------------------------------------
+Traceback (most recent call last):
+ File "124", line 331, in test_incremental_failure
+ self.create_incremental()
+ File "124", line 167, in create_incremental
+ return self.wait_incremental(bitmap, validate)
+ File "124", line 179, in wait_incremental
+ self.assert_qmp_absent(event, 'data/error')
+ File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, in assert_qmp_absent
+ self.fail('path "%s" has value "%s"' % (path, str(result)))
+AssertionError: path "data/error" has value "Input/output error"
+
+======================================================================
+FAIL: test_transaction_failure (__main__.TestIncrementalBackup)
+Test: Verify backups made from a transaction that partially fails.
+----------------------------------------------------------------------
+Traceback (most recent call last):
+ File "124", line 448, in test_transaction_failure
+ self.wait_incremental(dr1bm0, 'drive1')
+ File "124", line 179, in wait_incremental
+ self.assert_qmp_absent(event, 'data/error')
+ File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, in assert_qmp_absent
+ self.fail('path "%s" has value "%s"' % (path, str(result)))
+AssertionError: path "data/error" has value "Input/output error"
+
----------------------------------------------------------------------
. . .
Complete stderr of ./check -qcow2 here:
And, as per the other two failures of tests 051 and 061 (that you mentioned on #qemu yesterday), Kevin Wolf on IRC said:
051 was fixed, but the output has changed _again_. That change is from armbru_'s commit 7ee6c1e18. We need to update the reference output.
As for 061, Max sent a patch, but I think we need to fix qemu rather than updating the reference output there
Because the error message has become considerably worse
To test, I applied these two series to yesterday's QEMU git master:
[PATCH 00/11] block: incremental backup transactions [PATCH v2 00/17] block: transactionless incremental backup
So, I'm here (28 commits ahead of commit 3539bbb on master) after applying the patch series:
$ git describe
v2.2.0-1190-g41b7f5fI need to try w/ today's git though, yet.
Signature · 1 line
/kashyap
Quoted 74 lines
tests/qemu-iotests/124 +49
-0@@ -184,6 +184,55 @@ class TestIncrementalBackup(iotests.QMPTestCase): return True + def test_incremental_transaction(self): + '''Test: Verify backups made from transactionally created bitmaps. + + Create a bitmap "before" VM execution begins, then create a second + bitmap AFTER writes have already occurred. Use transactions to create + a full backup and synchronize both bitmaps to this backup. + Create an incremental backup through both bitmaps and verify that + both backups match the full backup. + ''' + bitmap0 = self.add_bitmap('bitmap0', 'drive0') + self.hmp_io_writes('drive0', (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + bitmap1 = self.add_bitmap('bitmap1', 'drive0') + + result = self.vm.qmp('transaction', actions=[ + { + 'type': 'block-dirty-bitmap-clear', + 'data': { 'node': 'drive0', + 'name': 'bitmap0' }, + }, + { + 'type': 'block-dirty-bitmap-clear', + 'data': { 'node': 'drive0', + 'name': 'bitmap1' }, + }, + { + 'type': 'drive-backup', + 'data': { 'device': 'drive0', + 'sync': 'full', + 'format': iotests.imgfmt, + 'target': self.full_bak }, + } + ]) + self.assert_qmp(result, 'return', {}) + self.wait_until_completed() + self.files.append(self.full_bak) + self.check_full_backup() + + self.hmp_io_writes('drive0', (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) + # Both bitmaps should be in sync and create fully valid + # incremental backups + res1 = self.create_incremental(bitmap0) + res2 = self.create_incremental(bitmap1) + self.assertTrue(res1 and res2) + + def test_incremental_failure(self): '''Test: Verify backups made after a failure are correct.tests/qemu-iotests/124.out +2
-2@@ -1,5 +1,5 @@ -.... +..... ---------------------------------------------------------------------- -Ran 4 tests +Ran 5 tests OKSignature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test
Quoted 62 lines · Kashyap Chamarthy
Quoted 6 lines · John Snow
Reviewed-by Max Reitz <[email protected]>Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 49 ++++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/124.out | 4 ++-- 2 files changed, 51 insertions(+), 2 deletions(-)This tests seems to fail locally:
. . .
+======================================================================
+FAIL:test_incremental_failure(__main__.TestIncrementalBackup)
+Test: Verify backups made after a failure are correct.
+----------------------------------------------------------------------
+Traceback (most recent call last):
+ File "124", line 331, intest_incremental_failure
+self.create_incremental()
+ File "124", line 167, increate_incremental
+ returnself.wait_incremental(bitmap, validate)
+ File "124", line 179, inwait_incremental
+self.assert_qmp_absent(event, 'data/error')
+ File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, inassert_qmp_absent
+self.fail('path "%s" has value "%s"' % (path, str(result)))
+AssertionError: path "data/error" has value "Input/output error"
+
+======================================================================
+FAIL:test_transaction_failure(__main__.TestIncrementalBackup)
+Test: Verify backups made from a transaction that partially fails.
+----------------------------------------------------------------------
+Traceback (most recent call last):
+ File "124", line 448, intest_transaction_failure
+self.wait_incremental(dr1bm0, 'drive1')
+ File "124", line 179, inwait_incremental
+self.assert_qmp_absent(event, 'data/error')
+ File "/home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/iotests.py", line 282, inassert_qmp_absent
+self.fail('path "%s" has value "%s"' % (path, str(result)))
+AssertionError: path "data/error" has value "Input/output error"
+----------------------------------------------------------------------. . .
Complete stderr of
./check -qcow2here:And, as per the other two failures of tests 051 and 061 (that you mentioned on #qemu yesterday), Kevin Wolf on IRC said:
051 was fixed, but the output has changed _again_. That change is from armbru_'s commit
7ee6c1e18. We need to update the reference output.As for 061, Max sent a patch, but I think we need to fix qemu rather than updating the reference output there
Because the error message has become considerably worseTo test, I applied these two series to yesterday's QEMU git master:
[PATCH 00/11] block: incremental backup transactions [PATCH v2 00/17] block: transactionless incremental backup
Quoted 8 lines
So, I'm here (28 commits ahead of commit 3539bbb on master) after applying the patch series:
$ git describe v2.2.0-1190-g41b7f5fI need to try w/ today's git though, yet.
OK, I'll check this again after I make my changes to the base series this is based on. I promise I wasn't seeing a failure then ;)
If I can't reproduce I will be in touch to help diagnose what's going wrong.
Thanks,
--js
Re: [Qemu-devel] [PATCH 02/11] iotests: add transactional incremental backup test
Quoted 3 lines · John Snow
On 03/11/2015 08:11 AM, Kashyap Chamarthy wrote:
[. . .]
Quoted 11 lines
Quoted 11 lines
To test, I applied these two series to yesterday's QEMU git master:
[PATCH 00/11] block: incremental backup transactions [PATCH v2 00/17] block: transactionless incremental backup
So, I'm here (28 commits ahead of commit 3539bbb on master) after applying the patch series:
$ git describe v2.2.0-1190-g41b7f5f
Quoted 7 lines
Quoted 2 lines
I need to try w/ today's git though, yet.
OK, I'll check this again after I make my changes to the base series this is based on. I promise I wasn't seeing a failure then ;)
If I can't reproduce I will be in touch to help diagnose what's going wrong.
Following up from our IRC conversation (thanks!), testing from your 'git remote' (from github) works. Below is what I did, for completeness' sake:
$ git remote add jsnow https://github.com/jnsnow/qemu.git $ git fetch jsnow transactionless-incremental $ git checkout -b 3-inc-backup-testing jsnow/transactionless-incremental $ git pull jsnow incremental-transactions $ git describe v2.2.0-1553-ged69392
Test:
$ make clean
$ ./configure --target-list=x86_64-softmmu --enable-debug
$ make -j4
$ cd tests/qemu-iotests
$ ./check -v -qcow2 124
QEMU -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../x86_64-softmmu/qemu-system-x86_64
QEMU_IMG -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../qemu-img
QEMU_IO -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../qemu-io
QEMU_NBD -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/../../qemu-nbd
IMGFMT -- qcow2 (compat=1.1)
IMGPROTO -- file
PLATFORM -- Linux/x86_64 tesla 3.18.3-201.fc21.x86_64
TEST_DIR -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/scratch
SOCKET_SCM_HELPER -- /home/kashyapc/tinker-space/qemu-upstream/tests/qemu-iotests/socket_scm_helper 124 2s ...
Passed all 1 tests
Signature · 1 line
/kashyap
PATCH 3/11 block: add transactional callbacks feature
In reply to John SnowThe goal here is to add a new method to transactions that allows developers to specify a callback that will get invoked only once all jobs spawned by a transaction are completed, allowing developers the chance to perform actions conditionally pending complete success or complete failure.
In order to register the new callback to be invoked, a user must request a callback pointer and closure by calling new_transaction_wrapper, which creates a wrapper around a closure and callback that would originally have been passed to e.g. backup_start().
The function will return a function pointer and a new closure to be passed instead. The transaction system will effectively intercept these callbacks and execute the desired actions upon reception of all intercepted callbacks.
This means that the registered callbacks will be called after all other transaction actions that requested a callback have completed. The feature has no knowledge of jobs spawned without informing the BlkTransactionList.
For an example of how to use the feature, please skip ahead to:
'block: drive_backup transaction callback support' which serves as an example for how to hook in drive_backup (or any block job launched by transactions).
Note 1: Defining a callback method alone is not sufficient to have the new
method invoked. You must call new_transaction_wrapper AND ensure the callback it returns to you is used as the callback for the job.
Note 2: You can use this feature for any system that registers completions of
an action via a callback of the form (void *opaque, int ret), not just block job callbacks.
Note 3: new_blk_transaction has no users in this patch, but will in
the next patch where it will become static and local to blockdev.c.
--- blockdev.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 2 deletions(-)
blockdev.c +223 -2
@@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,/* New and old BlockDriverState structs for atomic group operations */ typedef struct BlkTransactionState BlkTransactionState ; +typedef struct BlkTransactionList BlkTransactionList ; +typedef struct BlkTransactionData BlkTransactionData ;/* Only prepare() may fail. In a single transaction, only one of commit() or abort() will be called, clean() will always be called if it present. */
@@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {void (*abort)(BlkTransactionState *common);/* Clean up resource in the end, can be NULL. */ void (*clean)(BlkTransactionState *common); +/* Execute this after +all+ jobs in the transaction finish */ +void (*cb)(BlkTransactionState *common); } BdrvActionOps;/*
@@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {struct BlkTransactionState {TransactionAction *action;const BdrvActionOps *ops; +BlkTransactionList *list; +void *opaque; +/* Allow external users (callbacks) to reference this obj past .clean() */ +int refcount; +/* All transactions in the current group */ QSIMPLEQ_ENTRY (BlkTransactionState) entry; +/* Transactions in the current group with callbacks */ +QSIMPLEQ_ENTRY (BlkTransactionState) list_entry; }; +struct BlkTransactionList { +int jobs;/* Effectively: A refcount */ +int status;/* Cumulative retcode */ + QSIMPLEQ_HEAD(actions, BlkTransactionState) actions; +}; + +typedef struct BlkTransactionData { +void *opaque;/* Data given to encapsulated callback */ +int ret;/* Return code given to encapsulated callback */ +}BlkTransactionData ; + +typedef struct BlkTransactionWrapper { +void *opaque;/* Data to be given to encapsulated callback */ +void (*callback)(void *opaque,int ret);/* Encapsulated callback */ +}BlkTransactionWrapper ; + +static BlkTransactionList *new_blk_transaction_list (void ) +{ +BlkTransactionList *btl =g_malloc0 (sizeof (*btl)); + +/* Implicit 'job' for qmp_transaction itself */ + btl->jobs =1 ; +QSIMPLEQ_INIT (&btl->actions); +return btl; +} + +static BlkTransactionState *blk_put_transaction_state (BlkTransactionState *bts) +{ + bts->refcount--; +if (bts->refcount ==0 ) { +g_free (bts); +return NULL ; + } +return bts; +} + +static void del_blk_transaction_list (BlkTransactionList *btl) +{ +BlkTransactionState *bts, *bts_next; + +/* The list should in normal cases be empty, +* but in case someone really just wants to kibosh the whole deal: */ + QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +g_free (bts->opaque); +blk_put_transaction_state (bts); + } + +g_free (btl); +} + +static void blk_run_transaction_callbacks (BlkTransactionList *btl) +{ +BlkTransactionState *bts, *bts_next; + + QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +if (bts->ops->cb) { + bts->ops->cb (bts); + } + +/* Free the BlkTransactionData */ +g_free (bts->opaque); + bts->opaque =NULL ; +blk_put_transaction_state (bts); + } + +QSIMPLEQ_INIT (&btl->actions); +} + +static BlkTransactionList *put_blk_transaction_list (BlkTransactionList *btl) +{ + btl->jobs--; +if (btl->jobs ==0 ) { +blk_run_transaction_callbacks (btl); +del_blk_transaction_list (btl); +return NULL ; + } +return btl; +} + +static void blk_transaction_complete (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; + +/* Add this action into the pile to be completed */ +QSIMPLEQ_INSERT_TAIL (&btl->actions, common, list_entry); + +/* Inform the list that we have a completion; +* possibly run all the pending actions. */ +put_blk_transaction_list (btl); +} + +/** +* Intercept a callback that was issued due to a transactional action. +*/ +static void transaction_callback (void *opaque,int ret) +{ +BlkTransactionState *common = opaque; +BlkTransactionWrapper *btw = common->opaque; + +/* Prepare data for ops->cb() */ +BlkTransactionData *btd =g_malloc0 (sizeof (*btd)); + btd->opaque = btw->opaque; + btd->ret = ret; + +/* Transaction state now tracks OUR data */ + common->opaque = btd; + +/* Keep track of the amalgamated return code */ + common->list->status |= ret; + +/* Deliver the intercepted callback FIRST */ + btw->callback (btw->opaque, ret); +blk_transaction_complete (common); +g_free (btw); +} + +typedef void (CallbackFn )(void *opaque,int ret); + +/* Temporary. Removed in the next patch. */ +CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +void (*callback)(void *,int ), +void **new_opaque); + +void undo_transaction_wrapper (BlkTransactionState *common); + +/** +* Create a new transactional callback wrapper. +* +* Given a callback and a closure, generate a new +* callback and closure that will invoke the +* given callback with the given closure. +* +* After all wrappers in the transactional group have +* been processed, each action's .cb() method will be +* invoked. +* +* @common The transactional state to set a callback for. +* @opaque A closure intended for the encapsulated callback. +* @callback The callback we are encapsulating. +* @new_opaque The closure to be used instead of @opaque. +* +* @return The callback to be used instead of @callback. +*/ +CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +CallbackFn *callback, +void **new_opaque) +{ +BlkTransactionWrapper *btw =g_malloc0 (sizeof (*btw)); +assert (new_opaque); + +/* Stash the original callback information */ + btw->opaque = opaque; + btw->callback = callback; + common->opaque = btw; + +/* The BTS will serve as our new closure */ + *new_opaque = common; + common->refcount++; + +/* Inform the transaction BTL to expect one more return */ + common->list->jobs++; + +/* Lastly, the actual callback function to handle the interception. */ +return transaction_callback; +} + +/** +* Undo any actions performed by the above call. +*/ +void undo_transaction_wrapper (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; +BlkTransactionState *bts; +BlkTransactionData *btd; + +/* Stage 0: Wrapper was never created: */ +if (common->opaque ==NULL && common->refcount ==1 ) { +return ; + } + +/* Stage 2: Job already completed or was canceled. +* Force an error in the callback data and just invoke the completion +* handler to perform appropriate cleanup for us. +*/ + QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) { +if (bts == common) { + btd = common->opaque; +/* Force error for callback */ + btd->ret =-1 ; + common->ops->cb (common); +QSIMPLEQ_REMOVE (&btl->actions, common, + BlkTransactionState, list_entry); + goto cleanup; + } + } +/* Stage 1: Callback created, but job never launched */ +put_blk_transaction_list (common->list); + cleanup: +g_free (common->opaque); +blk_put_transaction_state (common); +} +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;
@@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean, + .clean = drive_backup_clean }, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),
@@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp) {TransactionActionList *dev_entry = dev_list;BlkTransactionState *state, *next; +BlkTransactionList *btl;Error *local_err =NULL ; QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState) snap_bdrv_states;QSIMPLEQ_INIT (&snap_bdrv_states); + btl =new_blk_transaction_list ();/* drain all i/o before any operations */ bdrv_drain_all ();
@@ -1837,8 +2054,10 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)assert (ops->instance_size >0 ); state =g_malloc0 (ops->instance_size); + state->refcount =1 ; state->ops = ops; state->action = dev_info; + state->list = btl;QSIMPLEQ_INSERT_TAIL (&snap_bdrv_states, state, entry); state->ops->prepare (state, &local_err);
@@ -1869,8 +2088,10 @@ exit:if (state->ops->clean) { state->ops->clean (state); } -g_free (state); +blk_put_transaction_state (state); } + +put_blk_transaction_list (btl); }
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
Quoted 64 lines · John Snow
The goal here is to add a new method to transactions that allows developers to specify a callback that will get invoked only once all jobs spawned by a transaction are completed, allowing developers the chance to perform actions conditionally pending complete success or complete failure.
In order to register the new callback to be invoked, a user must request a callback pointer and closure by calling
new_transaction_wrapper, which creates a wrapper around a closure and callback that would originally have been passed to e.g.backup_start().The function will return a function pointer and a new closure to be passed instead. The transaction system will effectively intercept these callbacks and execute the desired actions upon reception of all intercepted callbacks.
This means that the registered callbacks will be called after all other transaction actions that requested a callback have completed. The feature has no knowledge of jobs spawned without informing the BlkTransactionList.
For an example of how to use the feature, please skip ahead to:
'block:drive_backuptransaction callback support' which serves as an example for how to hook indrive_backup(or any block job launched by transactions).Note 1: Defining a callback method alone is not sufficient to have the new
method invoked. You must callnew_transaction_wrapperAND ensure the callback it returns to you is used as the callback for the job.Note 2: You can use this feature for any system that registers completions of
an action via a callback of the form (void *opaque, int ret), not just block job callbacks.Note 3:
new_blk_transactionhas no users in this patch, but will in
the next patch where it will become static and local toblockdev.c.Signed-off-by John Snow <[email protected]>--- blockdev.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 2 deletions(-)blockdev.c +4
-0@@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,/* New and old BlockDriverState structs for atomic group operations */ typedef struct BlkTransactionState BlkTransactionState ; +typedef struct BlkTransactionList BlkTransactionList ; +typedef struct BlkTransactionData BlkTransactionData ;/* Only prepare() may fail. In a single transaction, only one of commit() or abort() will be called, clean() will always be called if it present. */ @@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {void (*abort)(BlkTransactionState *common);/* Clean up resource in the end, can be NULL. */ void (*clean)(BlkTransactionState *common); +/* Execute this after +all+ jobs in the transaction finish */ +void (*cb)(BlkTransactionState *common); } BdrvActionOps;/* @@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {struct BlkTransactionState {
blockdev.c typedef struct BdrvActionOps { · line 1245
Quoted 10 lines
blockdev.c +7
-0TransactionAction *action;const BdrvActionOps *ops; +BlkTransactionList *list; +void *opaque; +/* Allow external users (callbacks) to reference this obj past .clean() */ +int refcount; +/* All transactions in the current group */ QSIMPLEQ_ENTRY (BlkTransactionState) entry; +/* Transactions in the current group with callbacks */ +QSIMPLEQ_ENTRY (BlkTransactionState) list_entry;
"list_entry" seems very generic and it's hard for me to see a fundamental difference to just "entry". How about "cb_entry", or "cb_list_entry", or "cb_group_entry" or something like that?
blockdev.c typedef struct BdrvActionOps { · line 1249
Quoted 4 lines
blockdev.c +2
-0}; +struct BlkTransactionList { +int jobs;/* Effectively: A refcount */
Good to know, but I'd like the reason why it's called like this to be here anyway ("Number of jobs remaining" or I don't know).
blockdev.c typedef struct BdrvActionOps { · line 1250
Quoted 1 line
blockdev.c +1
-0+int status;/* Cumulative retcode */
The only places I currently find "retcode" in are linux-user/signal.c and tests/image-fuzzer/runner.py. How about the more common "return value" (or simply "cumulative status")?
("retcode" reads a bit like "retcon" to me, that's why I had to think about it for a second)
blockdev.c typedef struct BdrvActionOps { · line 1262
Quoted 12 lines
blockdev.c +12
-0+ QSIMPLEQ_HEAD(actions, BlkTransactionState) actions; +}; + +typedef struct BlkTransactionData { +void *opaque;/* Data given to encapsulated callback */ +int ret;/* Return code given to encapsulated callback */ +}BlkTransactionData ; + +typedef struct BlkTransactionWrapper { +void *opaque;/* Data to be given to encapsulated callback */ +void (*callback)(void *opaque,int ret);/* Encapsulated callback */ +}BlkTransactionWrapper ;
I find it pretty difficult to figure out what these objects are each for. Care to add comments for that, too?
blockdev.c typedef struct BdrvActionOps { · line 1266
Quoted 4 lines
blockdev.c +4
-0+ +static BlkTransactionList *new_blk_transaction_list (void ) +{ +BlkTransactionList *btl =g_malloc0 (sizeof (*btl));
Maybe use g_new0(BlkTransactionList, 1); (It's typesafe! And who doesn't love typesafety?).
(Optional, though, the foo = malloc(sizeof(*foo)) pattern is pretty wide-spread, and as far as I remember, Markus purposely did not replace it by foo = g_new(Foo, 1))
blockdev.c typedef struct BdrvActionOps { · line 1269
Quoted 3 lines
blockdev.c +3
-0+ +/* Implicit 'job' for qmp_transaction itself */ + btl->jobs =1 ;
Well, if it is a refcount, just call it that way...
(Update: Okay, I see why you didn't call it that way. Maybe the comment needs improvement, like "The transaction itself is a job, too, that needs to be completed before the callbacks are called")
blockdev.c typedef struct BdrvActionOps { · line 1278
Quoted 9 lines
blockdev.c +9
-0+QSIMPLEQ_INIT (&btl->actions); +return btl; +} + +static BlkTransactionState *blk_put_transaction_state (BlkTransactionState *bts) +{ + bts->refcount--; +if (bts->refcount ==0 ) { +g_free (bts);
How about removing it from the lists it's in? Doesn't appear to be necessary, but I'd find it cleaner.
blockdev.c typedef struct BdrvActionOps { · line 1289
Quoted 11 lines
blockdev.c +11
-0+return NULL ; + } +return bts; +} + +static void del_blk_transaction_list (BlkTransactionList *btl) +{ +BlkTransactionState *bts, *bts_next; + +/* The list should in normal cases be empty, +* but in case someone really just wants to kibosh the whole deal: */
Thank you for teaching me a new word.
blockdev.c typedef struct BdrvActionOps { · line 1291
Quoted 2 lines
blockdev.c +2
-0+ QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +g_free (bts->opaque);
Urp... Are you sure? :-/
I'd rather have some callback for destroying the object... Apparently this will (for now) be always useless overhead, because that callback is only calling g_free(), but it's an opaque pointer, so it's not really up to you to do anything with it other than passing it around.
Quoted 7 lines
blockdev.c +7
-0+blk_put_transaction_state (bts); + } + +g_free (btl); +} + +static void blk_run_transaction_callbacks (BlkTransactionList *btl)
blockdev.c typedef struct BdrvActionOps { · line 1308
Quoted 10 lines
blockdev.c +10
-0+{ +BlkTransactionState *bts, *bts_next; + + QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +if (bts->ops->cb) { + bts->ops->cb (bts); + } + +/* Free the BlkTransactionData */ +g_free (bts->opaque);
Again... Urp. If you know it's a BlkTransactionData object, please make it a BlkTransactionData pointer and don't call it "opaque" if it's not so opaque in the end.
blockdev.c typedef struct BdrvActionOps { · line 1319
Quoted 11 lines
blockdev.c +11
-0+ bts->opaque =NULL ; +blk_put_transaction_state (bts); + } + +QSIMPLEQ_INIT (&btl->actions); +} + +static BlkTransactionList *put_blk_transaction_list (BlkTransactionList *btl) +{ + btl->jobs--; +if (btl->jobs ==0 ) {
Okay, this is interesting, because on the one hand it makes it clear why you did not call it "refcount", while on the other hand this is clearly a pattern which looks very much like handling a refcount.
Maybe you could call the function differently, like "blk_transaction_list_job_completed()" or something. It's longer, but I think it reduces the change of people staring at this thinking "Why is 'jobs' actually a refcount?".
Quoted 19 lines
blockdev.c +19
-0+blk_run_transaction_callbacks (btl); +del_blk_transaction_list (btl); +return NULL ; + } +return btl; +} + +static void blk_transaction_complete (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; + +/* Add this action into the pile to be completed */ +QSIMPLEQ_INSERT_TAIL (&btl->actions, common, list_entry); + +/* Inform the list that we have a completion; +* possibly run all the pending actions. */ +put_blk_transaction_list (btl); +} +
blockdev.c typedef struct BdrvActionOps { · line 1348
Quoted 10 lines
blockdev.c +10
-0+/** +* Intercept a callback that was issued due to a transactional action. +*/ +static void transaction_callback (void *opaque,int ret) +{ +BlkTransactionState *common = opaque; +BlkTransactionWrapper *btw = common->opaque; + +/* Prepare data for ops->cb() */ +BlkTransactionData *btd =g_malloc0 (sizeof (*btd));
g_new0(BlkTransactionData, 1);
blockdev.c typedef struct BdrvActionOps { · line 1353
Quoted 5 lines
blockdev.c +5
-0+ btd->opaque = btw->opaque; + btd->ret = ret; + +/* Transaction state now tracks OUR data */ + common->opaque = btd;
Sorry, but I really have a hard time following this opaqueness... Again, if you can, please clarify what the objects are for, and it would be very nice to separate truly opaque objects from these internally used objects (which are managed by you and thus are to be managed by you (g_free()), because reusing void * pointers for different kinds of objects like this makes my brain go strawberry.
blockdev.c typedef struct BdrvActionOps { · line 1356
Quoted 3 lines
blockdev.c +3
-0+ +/* Keep track of the amalgamated return code */ + common->list->status |= ret;
Hm, I guess you're expecting ret to be -errno. In that case, you'd probably rather want "if (ret && (!common->list->status || common->list->status == -ENOSPC)) { common->list->status = ret; }" or something like that, because bit-OR-ing different -errno values will probably not turn out so great.
blockdev.c typedef struct BdrvActionOps { · line 1366
Quoted 10 lines
blockdev.c +10
-0+ +/* Deliver the intercepted callback FIRST */ + btw->callback (btw->opaque, ret); +blk_transaction_complete (common); +g_free (btw); +} + +typedef void (CallbackFn )(void *opaque,int ret); + +/* Temporary. Removed in the next patch. */
Actually, no. :-)
(remove in patch 7)
Why are you making them non-static in the first place? I see both functions mentioned in patch 3 and patch 7 only (except for context in patch 6).
Quoted 21 lines
blockdev.c +21
-0+CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +void (*callback)(void *,int ), +void **new_opaque); + +void undo_transaction_wrapper (BlkTransactionState *common); + +/** +* Create a new transactional callback wrapper. +* +* Given a callback and a closure, generate a new +* callback and closure that will invoke the +* given callback with the given closure. +* +* After all wrappers in the transactional group have +* been processed, each action's .cb() method will be +* invoked. +* +* @common The transactional state to set a callback for. +* @opaque A closure intended for the encapsulated callback. +* @callback The callback we are encapsulating.
blockdev.c typedef struct BdrvActionOps { · line 1397
Quoted 10 lines
blockdev.c +10
-0+* @new_opaque The closure to be used instead of @opaque. +* +* @return The callback to be used instead of @callback. +*/ +CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +CallbackFn *callback, +void **new_opaque) +{ +BlkTransactionWrapper *btw =g_malloc0 (sizeof (*btw));
g_new0(BlkTransactionWrapper, 1);
Quoted 31 lines
blockdev.c +31
-0+assert (new_opaque); + +/* Stash the original callback information */ + btw->opaque = opaque; + btw->callback = callback; + common->opaque = btw; + +/* The BTS will serve as our new closure */ + *new_opaque = common; + common->refcount++; + +/* Inform the transaction BTL to expect one more return */ + common->list->jobs++; + +/* Lastly, the actual callback function to handle the interception. */ +return transaction_callback; +} + +/** +* Undo any actions performed by the above call. +*/ +void undo_transaction_wrapper (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; +BlkTransactionState *bts; +BlkTransactionData *btd; + +/* Stage 0: Wrapper was never created: */ +if (common->opaque ==NULL && common->refcount ==1 ) { +return ; + }
blockdev.c typedef struct BdrvActionOps { · line 1438
Quoted 10 lines
blockdev.c +10
-0+ +/* Stage 2: Job already completed or was canceled. +* Force an error in the callback data and just invoke the completion +* handler to perform appropriate cleanup for us. +*/ + QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) { +if (bts == common) { + btd = common->opaque; +/* Force error for callback */ + btd->ret =-1 ;
No -errno?
blockdev.c typedef struct BdrvActionOps { · line 1448
Quoted 10 lines
blockdev.c +10
-0+ common->ops->cb (common); +QSIMPLEQ_REMOVE (&btl->actions, common, + BlkTransactionState, list_entry); + goto cleanup; + } + } +/* Stage 1: Callback created, but job never launched */ +put_blk_transaction_list (common->list); + cleanup: +g_free (common->opaque);
urp
blockdev.c static const BdrvActionOps actions[] = { · line 1993
Quoted 12 lines
blockdev.c +4
-1+blk_put_transaction_state (common); +} +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;@@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean, + .clean = drive_backup_clean
Probably not intended, I guess.
Quoted 29 lines
blockdev.c +4
-0}, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),@@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp) {TransactionActionList *dev_entry = dev_list;BlkTransactionState *state, *next; +BlkTransactionList *btl;Error *local_err =NULL ; QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState) snap_bdrv_states;QSIMPLEQ_INIT (&snap_bdrv_states); + btl =new_blk_transaction_list ();/* drain all i/o before any operations */ bdrv_drain_all ();@@ -1837,8 +2054,10 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)assert (ops->instance_size >0 ); state =g_malloc0 (ops->instance_size); + state->refcount =1 ; state->ops = ops; state->action = dev_info; + state->list = btl;QSIMPLEQ_INSERT_TAIL (&snap_bdrv_states, state, entry); state->ops->prepare (state, &local_err);@@ -1869,8 +2088,10 @@ exit:if (state->ops->clean) {
blockdev.c exit: · line 2097
Quoted 10 lines
blockdev.c +3
-1state->ops->clean (state); } -g_free (state); +blk_put_transaction_state (state); } + +put_blk_transaction_list (btl); }
Sorry, as I said, I need more context on the objects and very much would like a separation between truly opaque pointers and not-so-opaque pointers, before I can really understand what's going on (or I put more effort into it, but since it's always more probable to belong to the majority, I guess I won't be the only one getting confused).
Maybe the next patches will clear it up, I don't know.
Max
Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
Quoted 94 lines · Max Reitz
Quoted 83 lines · John Snow
The goal here is to add a new method to transactions that allows developers to specify a callback that will get invoked only once all jobs spawned by a transaction are completed, allowing developers the chance to perform actions conditionally pending complete success or complete failure.
In order to register the new callback to be invoked, a user must request a callback pointer and closure by calling
new_transaction_wrapper, which creates a wrapper around a closure and callback that would originally have
been passed to e.g.backup_start().The function will return a function pointer and a new closure to be passed
instead. The transaction system will effectively intercept these callbacks
and execute the desired actions upon reception of all intercepted callbacks.This means that the registered callbacks will be called after all other transaction actions that requested a callback have completed. The feature has no knowledge of jobs spawned without informing the BlkTransactionList.
For an example of how to use the feature, please skip ahead to:
'block:drive_backuptransaction callback support' which serves as an example
for how to hook indrive_backup(or any block job launched by transactions).Note 1: Defining a callback method alone is not sufficient to have the new
method invoked. You must callnew_transaction_wrapperAND
ensure the
callback it returns to you is used as the callback for the job.Note 2: You can use this feature for any system that registers completions of
an action via a callback of the form (void *opaque, int ret),
not just
block job callbacks.Note 3:
new_blk_transactionhas no users in this patch, but will in
the next patch where it will become static and local toblockdev.c.Signed-off-by John Snow <[email protected]>--- blockdev.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 2 deletions(-)blockdev.c +2
-0@@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node, /* New and old BlockDriverState structs for atomic group operations */ typedef struct BlkTransactionState BlkTransactionState; +typedef struct BlkTransactionList BlkTransactionList; +typedef struct BlkTransactionData BlkTransactionData; /* Only prepare() may fail. In a single transaction, only one of
commit()or
abort()will be called,clean()will always be called if it
present. */blockdev.c +5
-0@@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {void (*abort)(BlkTransactionState *common);/* Clean up resource in the end, can be NULL. */ void (*clean)(BlkTransactionState *common); +/* Execute this after +all+ jobs in the transaction finish */ +void (*cb)(BlkTransactionState *common); } BdrvActionOps;/* @@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {struct BlkTransactionState {TransactionAction *action;const BdrvActionOps *ops; +BlkTransactionList *list; +void *opaque; +/* Allow external users (callbacks) to reference this obj past .clean() */
blockdev.c typedef struct BdrvActionOps { · line 1245
Quoted 5 lines
blockdev.c +4
-0+int refcount; +/* All transactions in the current group */ QSIMPLEQ_ENTRY (BlkTransactionState) entry; +/* Transactions in the current group with callbacks */ +QSIMPLEQ_ENTRY (BlkTransactionState) list_entry;"
list_entry" seems very generic and it's hard for me to see a fundamental difference to just "entry". How about "cb_entry", or "cb_list_entry", or "cb_group_entry" or something like that?
Quoted 7 lines
blockdev.c typedef struct BdrvActionOps { · line 1249
Quoted 3 lines
};blockdev.c +2
-0+struct BlkTransactionList { +int jobs;/* Effectively: A refcount */ Good to know, but I'd like the reason why it's called like this to be here anyway ("Number of jobs remaining" or I don't know).
Yes, it's basically a refcount where the references are held by jobs.
Quoted 9 lines
Quoted 1 line
+ int status; /* Cumulative retcode */
The only places I currently find "retcode" in are
linux-user/signal.candtests/image-fuzzer/runner.py. How about the more common "return value" (or simply "cumulative status")?("retcode" reads a bit like "retcon" to me, that's why I had to think about it for a second)
Sure.
Quoted 7 lines
Quoted 7 lines
blockdev.c +7
-0+ QSIMPLEQ_HEAD(actions, BlkTransactionState) actions; +}; + +typedef struct BlkTransactionData { +void *opaque;/* Data given to encapsulated callback */ +int ret;/* Return code given to encapsulated callback */ +}BlkTransactionData ;
Quoted 9 lines
blockdev.c typedef struct BdrvActionOps { · line 1262
Quoted 5 lines
blockdev.c +5
-0+ +typedef struct BlkTransactionWrapper { +void *opaque;/* Data to be given to encapsulated callback */ +void (*callback)(void *opaque,int ret);/* Encapsulated callback */ +}BlkTransactionWrapper ;I find it pretty difficult to figure out what these objects are each for. Care to add comments for that, too?
Will do.
Quoted 16 lines
blockdev.c typedef struct BdrvActionOps { · line 1266
Quoted 4 lines
blockdev.c +4
-0+ +static BlkTransactionList *new_blk_transaction_list (void ) +{ +BlkTransactionList *btl =g_malloc0 (sizeof (*btl));Maybe use
g_new0(BlkTransactionList, 1); (It's typesafe! And who doesn't love typesafety?).(Optional, though, the foo = malloc(sizeof(*foo)) pattern is pretty wide-spread, and as far as I remember, Markus purposely did not replace it by foo =
g_new(Foo, 1))blockdev.c typedef struct BdrvActionOps { · line 1269
Quoted 3 lines
blockdev.c +3
-0+ +/* Implicit 'job' for qmp_transaction itself */ + btl->jobs =1 ;
Quoted 6 lines
Well, if it is a refcount, just call it that way...
(Update: Okay, I see why you didn't call it that way. Maybe the comment needs improvement, like "The transaction itself is a job, too, that needs to be completed before the callbacks are called")
That's exactly the trick in play, here.
Quoted 52 lines
blockdev.c typedef struct BdrvActionOps { · line 1278
Quoted 10 lines
blockdev.c +5
-0+QSIMPLEQ_INIT (&btl->actions); +return btl; +} + +static BlkTransactionState*blk_put_transaction_state(BlkTransactionState *bts)
blockdev.c +4
-0+{ + bts->refcount--; +if (bts->refcount ==0 ) { +g_free (bts);How about removing it from the lists it's in? Doesn't appear to be necessary, but I'd find it cleaner.
Quoted 11 lines
blockdev.c +11
-0+return NULL ; + } +return bts; +} + +static void del_blk_transaction_list (BlkTransactionList *btl) +{ +BlkTransactionState *bts, *bts_next; + +/* The list should in normal cases be empty, +* but in case someone really just wants to kibosh the whole Quoted 1 line
deal: */
Thank you for teaching me a new word.
blockdev.c
Quoted 2 lines
blockdev.c +2
-0+ QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +g_free (bts->opaque);Urp... Are you sure? :-/
I'd rather have some callback for destroying the object... Apparently this will (for now) be always useless overhead, because that callback is only calling
g_free(), but it's an opaque pointer, so it's not really up to you to do anything with it other than passing it around.Quoted 13 lines
blockdev.c +13
-0+blk_put_transaction_state (bts); + } + +g_free (btl); +} + +static void blk_run_transaction_callbacks (BlkTransactionList *btl) +{ +BlkTransactionState *bts, *bts_next; + + QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +if (bts->ops->cb) { + bts->ops->cb (bts);
Quoted 9 lines
blockdev.c typedef struct BdrvActionOps { · line 1308
Quoted 4 lines
blockdev.c +4
-0+ } + +/* Free the BlkTransactionData */ +g_free (bts->opaque);Again... Urp. If you know it's a BlkTransactionData object, please make it a BlkTransactionData pointer and don't call it "opaque" if it's not so opaque in the end.
A good point. I use this "opaque" pointer to actually hold two very specific and knowable things; it's just that those things change during the lifetime of the object.
For whatever reason, I decided it was nicer to not have two pointers that are never used simultaneously.
I can either make two dedicated fields, or just introduce it as an union. The state of the object otherwise dictates which union field to access.
Because I am a complicated individual, I am thinking fondly of the union right now.
Quoted 17 lines
blockdev.c typedef struct BdrvActionOps { · line 1319
Quoted 12 lines
blockdev.c +8
-0+ bts->opaque =NULL ; +blk_put_transaction_state (bts); + } + +QSIMPLEQ_INIT (&btl->actions); +} + +static BlkTransactionList*put_blk_transaction_list(BlkTransactionList *btl)
blockdev.c +3
-0+{ + btl->jobs--; +if (btl->jobs ==0 ) {Okay, this is interesting, because on the one hand it makes it clear why you did not call it "refcount", while on the other hand this is clearly a pattern which looks very much like handling a refcount.
Quoted 5 lines
Maybe you could call the function differently, like "
blk_transaction_list_job_completed()" or something. It's longer, but I think it reduces the change of people staring at this thinking "Why is 'jobs' actually a refcount?".
OK, I'm up for suggestions on naming. I didn't really (deep in my heart) expect v1 to be perfect.
I think your main problem here is "jobs" as an integral refcount. Maybe "num_jobs" or "jobs_remaining" etc would make it clearer.
And then, sure, "blk_transaction_list_job_completed" would be a fine alternative to "put."
Quoted 38 lines
Quoted 19 lines
blockdev.c +19
-0+blk_run_transaction_callbacks (btl); +del_blk_transaction_list (btl); +return NULL ; + } +return btl; +} + +static void blk_transaction_complete (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; + +/* Add this action into the pile to be completed */ +QSIMPLEQ_INSERT_TAIL (&btl->actions, common, list_entry); + +/* Inform the list that we have a completion; +* possibly run all the pending actions. */ +put_blk_transaction_list (btl); +} +blockdev.c typedef struct BdrvActionOps { · line 1348
Quoted 10 lines
blockdev.c +10
-0+/** +* Intercept a callback that was issued due to a transactional action. +*/ +static void transaction_callback (void *opaque,int ret) +{ +BlkTransactionState *common = opaque; +BlkTransactionWrapper *btw = common->opaque; + +/* Prepare data for ops->cb() */ +BlkTransactionData *btd =g_malloc0 (sizeof (*btd));
g_new0(BlkTransactionData, 1);blockdev.c typedef struct BdrvActionOps { · line 1353
Quoted 5 lines
blockdev.c +5
-0+ btd->opaque = btw->opaque; + btd->ret = ret; + +/* Transaction state now tracks OUR data */ + common->opaque = btd;
Quoted 7 lines
Sorry, but I really have a hard time following this opaqueness... Again, if you can, please clarify what the objects are for, and it would be very nice to separate truly opaque objects from these internally used objects (which are managed by you and thus are to be managed by you (
g_free()), because reusing void * pointers for different kinds of objects like this makes my brain go strawberry.
I'll shoot for Raspberry in v2.
Quoted 10 lines
blockdev.c typedef struct BdrvActionOps { · line 1356
Quoted 3 lines
blockdev.c +3
-0+ +/* Keep track of the amalgamated return code */ + common->list->status |= ret;Hm, I guess you're expecting ret to be -errno. In that case, you'd probably rather want "if (ret && (!common->list->status ||
common->list->status==-ENOSPC)) {common->list->status= ret; }" or something like that, because bit-OR-ing different -errno values will probably not turn out so great.
I am only super interested in keeping a zero-or-nonzero cumulative status -- the actual error code winds up not being so important. The mechanisms here allow the delivery of both the "cumulative" and the individual return code to the transactional callback, so error messages etc. can fill in the blanks if they need to.
Quoted 13 lines
blockdev.c typedef struct BdrvActionOps { · line 1366
Quoted 10 lines
blockdev.c +10
-0+ +/* Deliver the intercepted callback FIRST */ + btw->callback (btw->opaque, ret); +blk_transaction_complete (common); +g_free (btw); +} + +typedef void (CallbackFn )(void *opaque,int ret); + +/* Temporary. Removed in the next patch. */ Actually, no. :-)
Quoted 6 lines
(remove in patch 7)
Why are you making them non-static in the first place? I see both functions mentioned in patch 3 and patch 7 only (except for context in patch 6).
Won't compile otherwise, because they have no users as of this patch. I am trying to keep the size of these patches down and in a sane order.
Once a user is added, we can re-add the static attribute.
Quoted 71 lines
Quoted 21 lines
blockdev.c +21
-0+CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +void (*callback)(void *,int ), +void **new_opaque); + +void undo_transaction_wrapper (BlkTransactionState *common); + +/** +* Create a new transactional callback wrapper. +* +* Given a callback and a closure, generate a new +* callback and closure that will invoke the +* given callback with the given closure. +* +* After all wrappers in the transactional group have +* been processed, each action's .cb() method will be +* invoked. +* +* @common The transactional state to set a callback for. +* @opaque A closure intended for the encapsulated callback. +* @callback The callback we are encapsulating. blockdev.c typedef struct BdrvActionOps { · line 1397
Quoted 10 lines
blockdev.c +10
-0+* @new_opaque The closure to be used instead of @opaque. +* +* @return The callback to be used instead of @callback. +*/ +CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +CallbackFn *callback, +void **new_opaque) +{ +BlkTransactionWrapper *btw =g_malloc0 (sizeof (*btw));
g_new0(BlkTransactionWrapper, 1);Quoted 37 lines
blockdev.c +15
-0+assert (new_opaque); + +/* Stash the original callback information */ + btw->opaque = opaque; + btw->callback = callback; + common->opaque = btw; + +/* The BTS will serve as our new closure */ + *new_opaque = common; + common->refcount++; + +/* Inform the transaction BTL to expect one more return */ + common->list->jobs++; + +/* Lastly, the actual callback function to handle the interception. */
blockdev.c +19
-0+return transaction_callback; +} + +/** +* Undo any actions performed by the above call. +*/ +void undo_transaction_wrapper (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; +BlkTransactionState *bts; +BlkTransactionData *btd; + +/* Stage 0: Wrapper was never created: */ +if (common->opaque ==NULL && common->refcount ==1 ) { +return ; + } + +/* Stage 2: Job already completed or was canceled. +* Force an error in the callback data and just invoke the completion
+ * handler to perform appropriate cleanup for us.
Quoted 9 lines
blockdev.c typedef struct BdrvActionOps { · line 1438
Quoted 6 lines
blockdev.c +6
-0+*/ + QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) { +if (bts == common) { + btd = common->opaque; +/* Force error for callback */ + btd->ret =-1 ;No -errno?
Hmm. I suppose I should emulate one. What errno should I emulate for "One of your siblings died, and now you also need to take a dirtnap?"
Quoted 11 lines
blockdev.c typedef struct BdrvActionOps { · line 1448
Quoted 10 lines
blockdev.c +10
-0+ common->ops->cb (common); +QSIMPLEQ_REMOVE (&btl->actions, common, + BlkTransactionState, list_entry); + goto cleanup; + } + } +/* Stage 1: Callback created, but job never launched */ +put_blk_transaction_list (common->list); + cleanup: +g_free (common->opaque);
Quoted 2 lines
urp
ok, ok! I'm sorry! ... kind of ...
Quoted 6 lines
Quoted 6 lines
blockdev.c +3
-0+blk_put_transaction_state (common); +} +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;
Quoted 9 lines
blockdev.c static const BdrvActionOps actions[] = { · line 1993
Quoted 6 lines
blockdev.c +1
-1@@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean, + .clean = drive_backup_cleanProbably not intended, I guess.
Did an earlier patch of mine goof this up? The way this patch leaves it looks correct.
Quoted 43 lines
Quoted 27 lines
blockdev.c +4
-0}, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),@@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp) {TransactionActionList *dev_entry = dev_list;BlkTransactionState *state, *next; +BlkTransactionList *btl;Error *local_err =NULL ; QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState) snap_bdrv_states;QSIMPLEQ_INIT (&snap_bdrv_states); + btl =new_blk_transaction_list ();/* drain all i/o before any operations */ bdrv_drain_all ();@@ -1837,8 +2054,10 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)assert (ops->instance_size >0 ); state =g_malloc0 (ops->instance_size); + state->refcount =1 ; state->ops = ops; state->action = dev_info; + state->list = btl;QSIMPLEQ_INSERT_TAIL (&snap_bdrv_states, state, entry); state->ops->prepare (state, &local_err);blockdev.c exit: · line 2095
Quoted 9 lines
blockdev.c +3
-1@@ -1869,8 +2088,10 @@ exit:if (state->ops->clean) { state->ops->clean (state); } -g_free (state); +blk_put_transaction_state (state); } + +put_blk_transaction_list (btl); }Sorry, as I said, I need more context on the objects and very much would like a separation between truly opaque pointers and not-so-opaque pointers, before I can really understand what's going on (or I put more effort into it, but since it's always more probable to belong to the majority, I guess I won't be the only one getting confused).
Quoted 3 lines
Maybe the next patches will clear it up, I don't know.
Max
I'll put some more elbow grease into explaining the flow.
Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
Quoted 8 lines · John Snow
Quoted 8 lines
blockdev.c typedef struct BdrvActionOps { · line 1366
Quoted 3 lines
blockdev.c +3
-0+typedef void (CallbackFn )(void *opaque,int ret); + +/* Temporary. Removed in the next patch. */ Actually, no. :-)
(remove in patch 7)
Quoted 7 lines
Quoted 4 lines
Why are you making them non-static in the first place? I see both functions mentioned in patch 3 and patch 7 only (except for context in patch 6).
Won't compile otherwise, because they have no users as of this patch. I am trying to keep the size of these patches down and in a sane order.
Another solution is to mark it static, but also add __attribute__((unused)) to shut the compiler up, so that you don't cause build failures, but also don't cause reviewers to wonder who is using it. Then when it finally gets used, a patch to remove the attribute is easier to see than a patch adding static.
Signature · 2 lines
Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
Quoted 15 lines · Eric Blake
Quoted 13 lines · John Snow
Quoted 12 lines
blockdev.c typedef struct BdrvActionOps { · line 1366
Quoted 3 lines
blockdev.c +3
-0+typedef void (CallbackFn )(void *opaque,int ret); + +/* Temporary. Removed in the next patch. */ Actually, no. :-)
(remove in patch 7)
Why are you making them non-static in the first place? I see both functions mentioned in patch 3 and patch 7 only (except for context in patch 6).
Quoted 9 lines
Quoted 2 lines
Won't compile otherwise, because they have no users as of this patch. I am trying to keep the size of these patches down and in a sane order.
Another solution is to mark it static, but also add __attribute__((unused)) to shut the compiler up, so that you don't cause build failures, but also don't cause reviewers to wonder who is using it. Then when it finally gets used, a patch to remove the attribute is easier to see than a patch adding static.
Good trick, thanks.
Re: [Qemu-devel] [PATCH 03/11] block: add transactional callbacks feature
In reply to John SnowQuoted 228 lines · John Snow
Quoted 96 lines · Max Reitz
Quoted 85 lines · John Snow
The goal here is to add a new method to transactions that allows developers to specify a callback that will get invoked only once all jobs spawned by a transaction are completed, allowing developers the chance to perform actions conditionally pending complete success or complete failure.
In order to register the new callback to be invoked, a user must request a callback pointer and closure by calling
new_transaction_wrapper, which creates a wrapper around a closure and callback that would originally have
been passed to e.g.backup_start().The function will return a function pointer and a new closure to be passed
instead. The transaction system will effectively intercept these callbacks
and execute the desired actions upon reception of all intercepted callbacks.This means that the registered callbacks will be called after all other transaction actions that requested a callback have completed. The feature has no knowledge of jobs spawned without informing the BlkTransactionList.
For an example of how to use the feature, please skip ahead to:
'block:drive_backuptransaction callback support' which serves as an example
for how to hook indrive_backup(or any block job launched by transactions).Note 1: Defining a callback method alone is not sufficient to have the new
method invoked. You must callnew_transaction_wrapperAND
ensure the
callback it returns to you is used as the callback for the
job.Note 2: You can use this feature for any system that registers completions of
an action via a callback of the form (void *opaque, int ret),
not just
block job callbacks.Note 3:
new_blk_transactionhas no users in this patch, but will in
the next patch where it will become static and local toblockdev.c.Signed-off-by John Snow <[email protected]>--- blockdev.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 2 deletions(-)blockdev.c +0
-0@@ -1207,6 +1207,8 @@ static BdrvDirtyBitmap *block_dirty_bitmap_lookup(const char *node,/* New and old BlockDriverState structs for atomic group operations */
typedef struct BlkTransactionState BlkTransactionState;blockdev.c +2
-0+typedef struct BlkTransactionList BlkTransactionList ; +typedef struct BlkTransactionData BlkTransactionData ;/* Only prepare() may fail. In a single transaction, only one of
commit()or
abort()will be called,clean()will always be called if it
present. */blockdev.c +5
-0@@ -1221,6 +1223,8 @@ typedef struct BdrvActionOps {void (*abort)(BlkTransactionState *common);/* Clean up resource in the end, can be NULL. */ void (*clean)(BlkTransactionState *common); +/* Execute this after +all+ jobs in the transaction finish */ +void (*cb)(BlkTransactionState *common); } BdrvActionOps;/* @@ -1231,9 +1235,220 @@ typedef struct BdrvActionOps {struct BlkTransactionState {TransactionAction *action;const BdrvActionOps *ops; +BlkTransactionList *list; +void *opaque; +/* Allow external users (callbacks) to reference this obj past .clean() */
blockdev.c typedef struct BdrvActionOps { · line 1245
Quoted 5 lines
blockdev.c +4
-0+int refcount; +/* All transactions in the current group */ QSIMPLEQ_ENTRY (BlkTransactionState) entry; +/* Transactions in the current group with callbacks */ +QSIMPLEQ_ENTRY (BlkTransactionState) list_entry;"
list_entry" seems very generic and it's hard for me to see a fundamental difference to just "entry". How about "cb_entry", or "cb_list_entry", or "cb_group_entry" or something like that?Quoted 7 lines
blockdev.c typedef struct BdrvActionOps { · line 1249
Quoted 3 lines
};blockdev.c +2
-0+struct BlkTransactionList { +int jobs;/* Effectively: A refcount */ Good to know, but I'd like the reason why it's called like this to be here anyway ("Number of jobs remaining" or I don't know).
Yes, it's basically a refcount where the references are held by jobs.
Quoted 9 lines
Quoted 1 line
+ int status; /* Cumulative retcode */
The only places I currently find "retcode" in are
linux-user/signal.candtests/image-fuzzer/runner.py. How about the more common "return value" (or simply "cumulative status")?("retcode" reads a bit like "retcon" to me, that's why I had to think about it for a second)
Sure.
Quoted 8 lines
Quoted 8 lines
blockdev.c +8
-0+ QSIMPLEQ_HEAD(actions, BlkTransactionState) actions; +}; + +typedef struct BlkTransactionData { +void *opaque;/* Data given to encapsulated callback */ +int ret;/* Return code given to encapsulated callback */ +}BlkTransactionData ; +Quoted 9 lines
Quoted 5 lines
blockdev.c +3
-0+typedef struct BlkTransactionWrapper { + void *opaque; /* Data to be given to encapsulated callback */ + void (*callback)(void *opaque, int ret); /* Encapsulatedcallback */
+} BlkTransactionWrapper;I find it pretty difficult to figure out what these objects are each for. Care to add comments for that, too?
Will do.
Quoted 16 lines
blockdev.c typedef struct BdrvActionOps { · line 1266
Quoted 4 lines
blockdev.c +4
-0+ +static BlkTransactionList *new_blk_transaction_list (void ) +{ +BlkTransactionList *btl =g_malloc0 (sizeof (*btl));Maybe use
g_new0(BlkTransactionList, 1); (It's typesafe! And who doesn't love typesafety?).(Optional, though, the foo = malloc(sizeof(*foo)) pattern is pretty wide-spread, and as far as I remember, Markus purposely did not replace it by foo =
g_new(Foo, 1))blockdev.c typedef struct BdrvActionOps { · line 1269
Quoted 3 lines
blockdev.c +3
-0+ +/* Implicit 'job' for qmp_transaction itself */ + btl->jobs =1 ;Quoted 6 lines
Well, if it is a refcount, just call it that way...
(Update: Okay, I see why you didn't call it that way. Maybe the comment needs improvement, like "The transaction itself is a job, too, that needs to be completed before the callbacks are called")
That's exactly the trick in play, here.
Quoted 52 lines
blockdev.c typedef struct BdrvActionOps { · line 1278
Quoted 10 lines
+
QSIMPLEQ_INIT(&btl->actions);blockdev.c +4
-0+return btl; +} + +static BlkTransactionState*blk_put_transaction_state(BlkTransactionState *bts)
blockdev.c +4
-0+{ + bts->refcount--; +if (bts->refcount ==0 ) { +g_free (bts);How about removing it from the lists it's in? Doesn't appear to be necessary, but I'd find it cleaner.
Quoted 11 lines
blockdev.c +11
-0+return NULL ; + } +return bts; +} + +static void del_blk_transaction_list (BlkTransactionList *btl) +{ +BlkTransactionState *bts, *bts_next; + +/* The list should in normal cases be empty, +* but in case someone really just wants to kibosh the whole Quoted 1 line
deal: */
Thank you for teaching me a new word.
blockdev.c
Quoted 2 lines
blockdev.c +2
-0+ QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +g_free (bts->opaque);Urp... Are you sure? :-/
I'd rather have some callback for destroying the object... Apparently this will (for now) be always useless overhead, because that callback is only calling
g_free(), but it's an opaque pointer, so it's not really up to you to do anything with it other than passing it around.Quoted 13 lines
blockdev.c +13
-0+blk_put_transaction_state (bts); + } + +g_free (btl); +} + +static void blk_run_transaction_callbacks (BlkTransactionList *btl) +{ +BlkTransactionState *bts, *bts_next; + + QSIMPLEQ_FOREACH_SAFE(bts, &btl->actions, list_entry, bts_next) { +if (bts->ops->cb) { + bts->ops->cb (bts);Quoted 9 lines
blockdev.c typedef struct BdrvActionOps { · line 1308
Quoted 4 lines
blockdev.c +4
-0+ } + +/* Free the BlkTransactionData */ +g_free (bts->opaque);Again... Urp. If you know it's a BlkTransactionData object, please make it a BlkTransactionData pointer and don't call it "opaque" if it's not so opaque in the end.
Quoted 7 lines
A good point. I use this "opaque" pointer to actually hold two very specific and knowable things; it's just that those things change during the lifetime of the object.
For whatever reason, I decided it was nicer to not have two pointers that are never used simultaneously.
I can either make two dedicated fields, or just introduce it as an union. The state of the object otherwise dictates which union field to access.
Because I am a complicated individual, I am thinking fondly of the union right now.
Good. I like unions. Other people don't. But I do. Especially anonymous unions, but I guess we don't have the luxury of C11 in qemu...
Quoted 18 lines
Quoted 18 lines
blockdev.c typedef struct BdrvActionOps { · line 1319
Quoted 13 lines
blockdev.c +8
-0+ bts->opaque =NULL ; +blk_put_transaction_state (bts); + } + +QSIMPLEQ_INIT (&btl->actions); +} + +static BlkTransactionList*put_blk_transaction_list(BlkTransactionList *btl)
blockdev.c +3
-0+{ + btl->jobs--; +if (btl->jobs ==0 ) {Okay, this is interesting, because on the one hand it makes it clear why you did not call it "refcount", while on the other hand this is clearly a pattern which looks very much like handling a refcount.
Quoted 7 lines
Quoted 5 lines
Maybe you could call the function differently, like "
blk_transaction_list_job_completed()" or something. It's longer, but I think it reduces the change of people staring at this thinking "Why is 'jobs' actually a refcount?".OK, I'm up for suggestions on naming. I didn't really (deep in my heart) expect v1 to be perfect.
I know that feeling... And it's completely fine. That's what the reviewing process is for.
Quoted 1 line
I think your main problem here is "jobs" as an integral refcount. Maybe "
num_jobs" or "jobs_remaining" etc would make it clearer.
My main problem is that the function is named exactly like a function that decreases a refcount and the content of the function is exactly like a function that decreases a refcount, but the refcount isn't called 'refcount', but 'jobs'.
Therefore, if you're trying to tie the refcount to a non-abstract concept (the number of jobs left to complete) by representing it as such, I'd like the function to represent that concept to (a job has been completed).
Quoted 48 lines
And then, sure, "
blk_transaction_list_job_completed" would be a fine alternative to "put."Quoted 38 lines
Quoted 19 lines
+
blk_run_transaction_callbacks(btl);blockdev.c +18
-0+del_blk_transaction_list (btl); +return NULL ; + } +return btl; +} + +static void blk_transaction_complete (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; + +/* Add this action into the pile to be completed */ +QSIMPLEQ_INSERT_TAIL (&btl->actions, common, list_entry); + +/* Inform the list that we have a completion; +* possibly run all the pending actions. */ +put_blk_transaction_list (btl); +} +blockdev.c typedef struct BdrvActionOps { · line 1348
Quoted 10 lines
blockdev.c +10
-0+/** +* Intercept a callback that was issued due to a transactional action. +*/ +static void transaction_callback (void *opaque,int ret) +{ +BlkTransactionState *common = opaque; +BlkTransactionWrapper *btw = common->opaque; + +/* Prepare data for ops->cb() */ +BlkTransactionData *btd =g_malloc0 (sizeof (*btd));
g_new0(BlkTransactionData, 1);blockdev.c typedef struct BdrvActionOps { · line 1353
Quoted 5 lines
blockdev.c +5
-0+ btd->opaque = btw->opaque; + btd->ret = ret; + +/* Transaction state now tracks OUR data */ + common->opaque = btd;Quoted 7 lines
Sorry, but I really have a hard time following this opaqueness... Again, if you can, please clarify what the objects are for, and it would be very nice to separate truly opaque objects from these internally used objects (which are managed by you and thus are to be managed by you (
g_free()), because reusing void * pointers for different kinds of objects like this makes my brain go strawberry.
Quoted 1 line
I'll shoot for Raspberry in v2.
Please don't try to add whipped cream on top.
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
blockdev.c +3
-0+ +/* Keep track of the amalgamated return code */ + common->list->status |= ret;
Quoted 8 lines
Quoted 6 lines
Hm, I guess you're expecting ret to be -errno. In that case, you'd probably rather want "if (ret && (!common->list->status ||
common->list->status==-ENOSPC)) {common->list->status= ret; }" or something like that, because bit-OR-ing different -errno values will probably not turn out so great.I am only super interested in keeping a zero-or-nonzero cumulative status -- the actual error code winds up not being so important.
Maybe, but most places in qemu try to follow the -errno convention. I'd only deviate from it if there is a compelling reason to. If you want to keep it short, just make it "if (ret) { common->list->status = ret; }".
As long as you don't intend to use bitmasks for the return values, this will give you at least the same amount of information: Some error occurred. Also, it'll even tell you the kind of at least on error that occurred (which may even be meaningful, if it's ENOSPC or something like that), so I don't think it's worse (other than requiring three lines instead of one).
Quoted 15 lines
The mechanisms here allow the delivery of both the "cumulative" and the individual return code to the transactional callback, so error messages etc. can fill in the blanks if they need to.
Quoted 13 lines
blockdev.c typedef struct BdrvActionOps { · line 1366
Quoted 10 lines
blockdev.c +10
-0+ +/* Deliver the intercepted callback FIRST */ + btw->callback (btw->opaque, ret); +blk_transaction_complete (common); +g_free (btw); +} + +typedef void (CallbackFn )(void *opaque,int ret); + +/* Temporary. Removed in the next patch. */ Actually, no. :-)
Quoted 8 lines
Quoted 6 lines
(remove in patch 7)
Why are you making them non-static in the first place? I see both functions mentioned in patch 3 and patch 7 only (except for context in patch 6).
Won't compile otherwise, because they have no users as of this patch. I am trying to keep the size of these patches down and in a sane order.
Ah, hm, right...
Quoted 1 line
Once a user is added, we can re-add the static attribute.
Well, there's __attribute__((used)), but I see, yep.
(__attribute__((used)) would be cleaner because it doesn't litter the global namespace; on the other hand, if there are no conflict, in practice just not making it static is probably the better choice (I don't think any compiler that doesn't understand __attribute__((used)) will build qemu at all, but you never know what people try))
Quoted 81 lines
Quoted 71 lines
Quoted 21 lines
blockdev.c +21
-0+CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +void (*callback)(void *,int ), +void **new_opaque); + +void undo_transaction_wrapper (BlkTransactionState *common); + +/** +* Create a new transactional callback wrapper. +* +* Given a callback and a closure, generate a new +* callback and closure that will invoke the +* given callback with the given closure. +* +* After all wrappers in the transactional group have +* been processed, each action's .cb() method will be +* invoked. +* +* @common The transactional state to set a callback for. +* @opaque A closure intended for the encapsulated callback. +* @callback The callback we are encapsulating. blockdev.c typedef struct BdrvActionOps { · line 1397
Quoted 10 lines
blockdev.c +10
-0+* @new_opaque The closure to be used instead of @opaque. +* +* @return The callback to be used instead of @callback. +*/ +CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +void *opaque, +CallbackFn *callback, +void **new_opaque) +{ +BlkTransactionWrapper *btw =g_malloc0 (sizeof (*btw));
g_new0(BlkTransactionWrapper, 1);Quoted 37 lines
blockdev.c +15
-0+assert (new_opaque); + +/* Stash the original callback information */ + btw->opaque = opaque; + btw->callback = callback; + common->opaque = btw; + +/* The BTS will serve as our new closure */ + *new_opaque = common; + common->refcount++; + +/* Inform the transaction BTL to expect one more return */ + common->list->jobs++; + +/* Lastly, the actual callback function to handle the interception. */
blockdev.c +19
-0+return transaction_callback; +} + +/** +* Undo any actions performed by the above call. +*/ +void undo_transaction_wrapper (BlkTransactionState *common) +{ +BlkTransactionList *btl = common->list; +BlkTransactionState *bts; +BlkTransactionData *btd; + +/* Stage 0: Wrapper was never created: */ +if (common->opaque ==NULL && common->refcount ==1 ) { +return ; + } + +/* Stage 2: Job already completed or was canceled. +* Force an error in the callback data and just invoke the completion
+ * handler to perform appropriate cleanup for us.Quoted 9 lines
blockdev.c typedef struct BdrvActionOps { · line 1438
Quoted 6 lines
blockdev.c +6
-0+*/ + QSIMPLEQ_FOREACH(bts, &btl->actions, list_entry) { +if (bts == common) { + btd = common->opaque; +/* Force error for callback */ + btd->ret =-1 ;No -errno?
Quoted 1 line
Hmm. I suppose I should emulate one. What errno should I emulate for "One of your siblings died, and now you also need to take a dirtnap?"
That's always a hard question, isn't it? :-)
I'd really like an error code like "EEVERYTHINGWENTWRONG" or "EGOSHIDONTKNOW". I'm always consulting errno(3) at this point... ... Well, there's ECANCELED, maybe that's okay.
Quoted 33 lines
Quoted 12 lines
blockdev.c typedef struct BdrvActionOps { · line 1448
Quoted 11 lines
+
common->ops->cb(common);blockdev.c +9
-0+QSIMPLEQ_REMOVE (&btl->actions, common, + BlkTransactionState, list_entry); + goto cleanup; + } + } +/* Stage 1: Callback created, but job never launched */ +put_blk_transaction_list (common->list); + cleanup: +g_free (common->opaque);Quoted 2 lines
urp
ok, ok! I'm sorry! ... kind of ...
Quoted 6 lines
Quoted 6 lines
blockdev.c +3
-0+blk_put_transaction_state (common); +} +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;Quoted 9 lines
blockdev.c static const BdrvActionOps actions[] = { · line 1993
Quoted 6 lines
blockdev.c +1
-1@@ -1775,7 +1990,7 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean, + .clean = drive_backup_cleanProbably not intended, I guess.
Quoted 1 line
Did an earlier patch of mine goof this up? The way this patch leaves it looks correct.
Both are correct. However, generally qemu likes to keep commas at the end of initializers because it makes it easier to add new fields later on.
Quoted 44 lines
Quoted 44 lines
Quoted 28 lines
blockdev.c +4
-0}, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),@@ -1815,10 +2030,12 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp) {TransactionActionList *dev_entry = dev_list;BlkTransactionState *state, *next; +BlkTransactionList *btl;Error *local_err =NULL ; QSIMPLEQ_HEAD(snap_bdrv_states, BlkTransactionState) snap_bdrv_states;QSIMPLEQ_INIT (&snap_bdrv_states); + btl =new_blk_transaction_list ();/* drain all i/o before any operations */ bdrv_drain_all ();@@ -1837,8 +2054,10 @@ void qmp_transaction(TransactionActionList *dev_list, Error **errp)assert (ops->instance_size >0 ); state =g_malloc0 (ops->instance_size); + state->refcount =1 ; state->ops = ops; state->action = dev_info; + state->list = btl;QSIMPLEQ_INSERT_TAIL (&snap_bdrv_states, state, entry); state->ops->prepare (state, &local_err);blockdev.c exit: · line 2095
Quoted 9 lines
blockdev.c +3
-1@@ -1869,8 +2088,10 @@ exit:if (state->ops->clean) { state->ops->clean (state); } -g_free (state); +blk_put_transaction_state (state); } + +put_blk_transaction_list (btl); }Sorry, as I said, I need more context on the objects and very much would like a separation between truly opaque pointers and not-so-opaque pointers, before I can really understand what's going on (or I put more effort into it, but since it's always more probable to belong to the majority, I guess I won't be the only one getting confused).
Quoted 5 lines
Quoted 3 lines
Maybe the next patches will clear it up, I don't know.
Max
I'll put some more elbow grease into explaining the flow.
Thanks!
Max
PATCH 4/11 block: add refcount to Job object
In reply to John SnowIf we want to get at the job after the life of the job, we'll need a refcount for this object.
This may occur for example if we wish to inspect the actions taken by a particular job after a transactional group of jobs runs, and further actions are required.
--- blockjob.c | 18 ++++++++++++++++-- include/block/blockjob.h | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-)
blockjob.c +16 -2
@@ -35,6 +35,19 @@#include "qemu/timer.h" #include "qapi-event.h" +void block_job_incref (BlockJob *job) +{ + job->refcount++; +} + +void block_job_decref (BlockJob *job) +{ + job->refcount--; +if (job->refcount ==0 ) { +g_free (job); + } +} +void *block_job_create (const BlockJobDriver *driver,BlockDriverState *bs,int64_t speed,BlockCompletionFunc *cb,void *opaque,Error **errp)
@@ -57,6 +70,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, job->cb = cb; job->opaque = opaque; job->busy = true; + job->refcount =1 ; bs->job = job;/* Only set speed when necessary to avoid NotSupported error */
@@ -68,7 +82,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, bs->job =NULL ;bdrv_op_unblock_all (bs, job->blocker);error_free (job->blocker); -g_free (job); +block_job_decref (job);error_propagate (errp, local_err);return NULL ; }
@@ -85,7 +99,7 @@ void block_job_completed(BlockJob *job, int ret) bs->job =NULL ;bdrv_op_unblock_all (bs, job->blocker);error_free (job->blocker); -g_free (job); +block_job_decref (job); }void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp)
include/block/blockjob.h +21 -0
@@ -116,6 +116,9 @@ struct BlockJob {/** The opaque value that is passed to the completion function. */ void *opaque; + +/** A reference count, allowing for post-job actions in e.g. transactions */ +int refcount; };/**
@@ -141,6 +144,24 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, void *opaque, Error **errp);/** +* block_job_incref: +* @job: The job to pick up a handle to +* +* Increment the refcount on @job, to be able to use it asynchronously +* from the job it is being used for. Put down the reference when done +* with @block_job_unref +*/ +void block_job_incref (BlockJob *job); + +/** +* block_job_decref: +* @job: The job to unreference and delete. +* +* Decrement the refcount on @job, and delete it if there are no users. +*/ +void block_job_decref (BlockJob *job); + +/** * block_job_sleep_ns: * @job: The job that calls the function. * @clock: The clock to sleep on.
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 04/11] block: add refcount to Job object
Quoted 78 lines · John Snow
If we want to get at the job after the life of the job, we'll need a refcount for this object.
This may occur for example if we wish to inspect the actions taken by a particular job after a transactional group of jobs runs, and further actions are required.
Signed-off-by John Snow <[email protected]>--- blockjob.c | 18 ++++++++++++++++-- include/block/blockjob.h | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-)blockjob.c +16
-2@@ -35,6 +35,19 @@#include "qemu/timer.h" #include "qapi-event.h" +void block_job_incref (BlockJob *job) +{ + job->refcount++; +} + +void block_job_decref (BlockJob *job) +{ + job->refcount--; +if (job->refcount ==0 ) { +g_free (job); + } +} +void *block_job_create (const BlockJobDriver *driver,BlockDriverState *bs,int64_t speed,BlockCompletionFunc *cb,void *opaque,Error **errp)@@ -57,6 +70,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, job->cb = cb; job->opaque = opaque; job->busy = true; + job->refcount =1 ; bs->job = job;/* Only set speed when necessary to avoid NotSupported error */ @@ -68,7 +82,7 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, bs->job =NULL ;bdrv_op_unblock_all (bs, job->blocker);error_free (job->blocker); -g_free (job); +block_job_decref (job);error_propagate (errp, local_err);return NULL ; }@@ -85,7 +99,7 @@ void block_job_completed(BlockJob *job, int ret) bs->job =NULL ;bdrv_op_unblock_all (bs, job->blocker);error_free (job->blocker); -g_free (job); +block_job_decref (job); }void block_job_set_speed (BlockJob *job,int64_t speed,Error **errp)include/block/blockjob.h +3
-0@@ -116,6 +116,9 @@ struct BlockJob {/** The opaque value that is passed to the completion function. */ void *opaque; + +/** A reference count, allowing for post-job actions in e.g. transactions */ +int refcount; };/**
include/block/blockjob.h void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, · line 152
Quoted 9 lines
include/block/blockjob.h +6
-0@@ -141,6 +144,24 @@ void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, void *opaque, Error **errp);/** +* block_job_incref: +* @job: The job to pick up a handle to +* +* Increment the refcount on @job, to be able to use it asynchronously +* from the job it is being used for. Put down the reference when done +* with @block_job_unref
Missing a full stop?
With that added:
include/block/blockjob.h void *block_job_create(const BlockJobDriver *driver, BlockDriverState *bs, · line 160
Quoted 8 lines
include/block/blockjob.h +8
-0+*/ +void block_job_incref (BlockJob *job); + +/** +* block_job_decref: +* @job: The job to unreference and delete. +* +* Decrement the refcount on @job, and delete it if there are no users.
(and also with that changed to "if it becomes/reaches zero", if you'd like, because somehow the "users" sounds unrelated to a party holding a refcount, when in fact it's supposed to be exactly the same thing; up to you)
Quoted 7 lines
include/block/blockjob.h +4
-0+*/ +void block_job_decref (BlockJob *job); + +/** * block_job_sleep_ns: * @job: The job that calls the function. * @clock: The clock to sleep on.
PATCH 5/11 block: add delayed bitmap successor cleanup
In reply to John SnowAllow bitmap successors to carry reference counts.
We can in a later patch use this ability to clean up the dirty bitmap according to both the individual job's success and the success of all jobs in the transaction group.
The code for cleaning up a bitmap is also moved from backup_run to backup_complete.
--- block.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++---- block/backup.c | 23 ++++++--------- include/block/block.h | 11 ++++--- 3 files changed, 87 insertions(+), 26 deletions(-)
block.c +73 -6
@@ -51,6 +51,12 @@#include <windows.h> #endif +typedef enum BitmapSuccessorAction { +SUCCESSOR_ACTION_UNDEFINED =0 , +SUCCESSOR_ACTION_ABDICATE , +SUCCESSOR_ACTION_RECLAIM +}BitmapSuccessorAction ; +/** * A BdrvDirtyBitmap can be in three possible states: * (1) successor is false and disabled is false: full r/w mode
@@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {char *name;/* Optional non-empty unique ID */ int64_t size;/* Size of the bitmap (Number of sectors) */ bool disabled;/* Bitmap is read-only */ +int successor_refcount;/* Number of active handles to the successor */ +BitmapSuccessorAction act;/* Action to take on successor upon release */ QLIST_ENTRY (BdrvDirtyBitmap) list; };
@@ -5508,6 +5516,7 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,/* Install the successor and freeze the parent */ bitmap->successor = child; + bitmap->successor_refcount =1 ;return 0 ; }
@@ -5515,9 +5524,9 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,* 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) +static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate (BlockDriverState *bs, +BdrvDirtyBitmap *bitmap, +Error **errp) {char *name;BdrvDirtyBitmap *successor = bitmap->successor;
@@ -5542,9 +5551,9 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,* 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) +static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +Error **errp) {BdrvDirtyBitmap *successor = parent->successor;
@@ -5563,6 +5572,64 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,return parent; } +static BdrvDirtyBitmap *bdrv_free_bitmap_successor (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +Error **errp) +{ +if (parent->successor_refcount) { +error_setg (errp,"Cannot free the successor for bitmap '%s', " +"because the refcount is non-zero." , parent->name); +return NULL ; + } + +switch (parent->act) { +case SUCCESSOR_ACTION_UNDEFINED : +error_setg (errp,"Cannot free the successor for bitmap '%s', " +"because the successor action has not yet been set." , + parent->name); +return NULL ; +case SUCCESSOR_ACTION_RECLAIM : +return bdrv_reclaim_dirty_bitmap (bs, parent, errp); +case SUCCESSOR_ACTION_ABDICATE : +return bdrv_dirty_bitmap_abdicate (bs, parent, errp); +default : +error_setg (errp, +"Unrecognized successor action (%d), " +"cannot free successor for bitmap '%s'" , + parent->act, parent->name); +return NULL ; + } +} + +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +int ret, +Error **errp) +{ +assert (bdrv_dirty_bitmap_frozen (parent)); +assert (parent->successor); + +if (ret) { + parent->act =SUCCESSOR_ACTION_RECLAIM ; + }else if (parent->act !=SUCCESSOR_ACTION_RECLAIM ) { + parent->act =SUCCESSOR_ACTION_ABDICATE ; + } + + parent->successor_refcount--; +if (parent->successor_refcount ==0 ) { +return bdrv_free_bitmap_successor (bs, parent, errp); + } +return parent; +} + +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent) +{ +assert (bdrv_dirty_bitmap_frozen (parent)); +assert (parent->successor); + + parent->successor_refcount++; +} +static void dirty_bitmap_truncate (BdrvDirtyBitmap *bitmap,uint64_t size) {/* Should only be frozen during a block backup job, which should have
block/backup.c +9 -14
@@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void *opaque)bdrv_unref (s->target); +if (s->sync_bitmap) { +BdrvDirtyBitmap *bm; + bm =bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, data->ret,NULL ); +assert (bm); + } +block_job_completed (job, data->ret);g_free (data); }
@@ -419,19 +425,6 @@ leave:qemu_co_rwlock_wrlock (&job->flush_rwlock);qemu_co_rwlock_unlock (&job->flush_rwlock); -if (job->sync_bitmap) { -BdrvDirtyBitmap *bm; -if (ret <0 ) { -/* Merge the successor back into the parent, delete nothing. */ - bm =bdrv_reclaim_dirty_bitmap (bs, job->sync_bitmap,NULL ); -assert (bm); -bdrv_enable_dirty_bitmap (job->sync_bitmap); - }else { -/* Everything is fine, delete this bitmap and install the backup. */ - bm =bdrv_dirty_bitmap_abdicate (bs, job->sync_bitmap,NULL ); -assert (bm); - } - }hbitmap_free (job->bitmap);bdrv_iostatus_disable (target);
@@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, error:if (sync_bitmap) { -bdrv_reclaim_dirty_bitmap (bs, sync_bitmap,NULL ); +BdrvDirtyBitmap *ret; + ret =bdrv_dirty_bitmap_decref (bs, sync_bitmap,-1 ,NULL ); +assert (ret); } }
include/block/block.h +5 -6
@@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);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_dirty_bitmap_decref (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +int ret, +Error **errp); +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent);BdrvDirtyBitmap *bdrv_find_dirty_bitmap (BlockDriverState *bs,const char *name);void bdrv_dirty_bitmap_make_anon (BdrvDirtyBitmap *bitmap);
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
Quoted 21 lines · John Snow
Allow bitmap successors to carry reference counts.
We can in a later patch use this ability to clean up the dirty bitmap according to both the individual job's success and the success of all jobs in the transaction group.
The code for cleaning up a bitmap is also moved from
backup_runtobackup_complete.Signed-off-by John Snow <[email protected]>--- block.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++---- block/backup.c | 23 ++++++--------- include/block/block.h | 11 ++++--- 3 files changed, 87 insertions(+), 26 deletions(-)block.c +0
-0
block.c line 58
Quoted 8 lines
block.c +5
-0@@ -51,6 +51,12 @@#include <windows.h> #endif +typedef enum BitmapSuccessorAction { +SUCCESSOR_ACTION_UNDEFINED =0 , +SUCCESSOR_ACTION_ABDICATE , +SUCCESSOR_ACTION_RECLAIM +}BitmapSuccessorAction ;
Maybe making this a QAPI enum can come in handy later. I don't know in which QMP commands one would use it, but if I could predict the future, I'd make trillions (or even billions) at the stock market.
(It's just that it already looks so much alike to a QAPI enum that I can't help but to think of making it one)
Quoted 48 lines
block.c +10
-6+/** * A BdrvDirtyBitmap can be in three possible states: * (1) successor is false and disabled is false: full r/w mode @@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {char *name;/* Optional non-empty unique ID */ int64_t size;/* Size of the bitmap (Number of sectors) */ bool disabled;/* Bitmap is read-only */ +int successor_refcount;/* Number of active handles to the successor */ +BitmapSuccessorAction act;/* Action to take on successor upon release */ QLIST_ENTRY (BdrvDirtyBitmap) list; };@@ -5508,6 +5516,7 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,/* Install the successor and freeze the parent */ bitmap->successor = child; + bitmap->successor_refcount =1 ;return 0 ; }@@ -5515,9 +5524,9 @@ int bdrv_dirty_bitmap_create_successor(BlockDriverState *bs,* 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) +static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate (BlockDriverState *bs, +BdrvDirtyBitmap *bitmap, +Error **errp) {char *name;BdrvDirtyBitmap *successor = bitmap->successor;@@ -5542,9 +5551,9 @@ BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate(BlockDriverState *bs,* 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) +static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +Error **errp) {BdrvDirtyBitmap *successor = parent->successor;
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5579
Quoted 8 lines
block.c +5
-0@@ -5563,6 +5572,64 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs,return parent; } +static BdrvDirtyBitmap *bdrv_free_bitmap_successor (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +Error **errp) +{ +if (parent->successor_refcount) {
I don't know whether you're intending to call this function from anywhere outside of bdrv_dirty_bitmap_decref(), but if you don't, please just make this an assert(!parent->successor_refcount).
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5581
Quoted 2 lines
block.c +2
-0+error_setg (errp,"Cannot free the successor for bitmap '%s', " +"because the refcount is non-zero." , parent->name);
Hm, again with the full stops? *g*
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5588
Quoted 7 lines
block.c +7
-0+return NULL ; + } + +switch (parent->act) { +case SUCCESSOR_ACTION_UNDEFINED : + error_setg(errp, "Cannot free the successor for bitmap '%s', " + "because the successor action has not yet been set.",
Indeed, again with the full stops. ;-)
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5590
Quoted 2 lines
block.c +2
-0+ parent->name); +return NULL ;
So you're (for now) only calling this function from bdrv_dirty_bitmap_decref(), and that function always makes sure that parent->act is set to SUCCESSOR_ACTION_{RECLAIM,ABDICATE}. Why not add an assert(parent->act != SUCCESSOR_ACTION_UNDEFINED) before the switch? (If that causes problems in regards to compiler warnings or something (not having all enum values covered in the switch), just add an assert(0); under "case SUCCESSOR_ACTION_UNDEFINED:".)
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5600
Quoted 10 lines
block.c +10
-0+case SUCCESSOR_ACTION_RECLAIM : +return bdrv_reclaim_dirty_bitmap (bs, parent, errp); +case SUCCESSOR_ACTION_ABDICATE : +return bdrv_dirty_bitmap_abdicate (bs, parent, errp); +default : +error_setg (errp, +"Unrecognized successor action (%d), " +"cannot free successor for bitmap '%s'" , + parent->act, parent->name); +return NULL ;
This can be made an assert(0), a g_assert_not_reached(), or simply an abort() (with the latter probably being the preferred way).
So I think that all the error_setg() calls are actually cases that should never happen. Better make them abort() and drop the Error parameter (because *_decref() and *_free() functions normally (for good reason) don't have Error parameters...).
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5604
Quoted 4 lines
block.c +4
-0+ } +} + +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,
I don't know whether I'm that content with the name chosen, because you're actually decrementing the refcount of the successor; but since the successor is basically a clone of the original bitmap (and I mean in the Star Trek sense, that it's a teleported clone and the original is intended to be destroyed so the successor can replace it), decrementing the refcount of the successor basically is equal to decrementing the refcount of the bitmap itself (as long as there is a successor, which you are asserting; maybe you want to add a comment about that to include/block/block.h, that one can only use this on frozen bitmaps?).
Quoted 6 lines
block.c +6
-0+BdrvDirtyBitmap *parent, + int ret, + Error **errp) +{ +assert (bdrv_dirty_bitmap_frozen (parent )); +assert (parent->successor);
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5620
Quoted 10 lines
block.c +10
-0+ +if (ret) { + parent->act =SUCCESSOR_ACTION_RECLAIM ; + }else if (parent->act !=SUCCESSOR_ACTION_RECLAIM ) { + parent->act =SUCCESSOR_ACTION_ABDICATE ; + } + + parent->successor_refcount--; +if (parent->successor_refcount ==0 ) { +return bdrv_free_bitmap_successor (bs, parent, errp);
If you drop the Error parameter from bdrv_free_bitmap_successor(), you can drop it from this function, too.
Quoted 20 lines
block.c +12
-0+ } +return parent; +} + +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent) +{ +assert (bdrv_dirty_bitmap_frozen (parent)); +assert (parent->successor); + + parent->successor_refcount++; +} +static void dirty_bitmap_truncate (BdrvDirtyBitmap *bitmap,uint64_t size) {/* Should only be frozen during a block backup job, which should have block/backup.c +0
-0
block/backup.c static void backup_complete(BlockJob *job, void *opaque) · line 246
Quoted 7 lines
block/backup.c +4
-0@@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void *opaque)bdrv_unref (s->target); +if (s->sync_bitmap) { +BdrvDirtyBitmap *bm; + bm =bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, data->ret,NULL ); +assert (bm);
You can use &error_abort as the Error object and drop the assert(); or, if you are dropping the Error parameter, there is no need to check the return value at all, because it will always be non-NULL (there won't be any code path in the function returning NULL at all). Maybe you can even drop the return value, too.
I just looked through the series: Actually, you're never using the Error parameter for bdrv_dirty_bitmap_decref() at all. Seems to me like you really should drop it (and maybe the return value along with it).
Quoted 6 lines
block/backup.c +2
-0+ } +block_job_completed (job, data->ret);g_free (data); }
block/backup.c leave: · line 428
Quoted 10 lines
block/backup.c +0
-7@@ -419,19 +425,6 @@ leave:qemu_co_rwlock_wrlock (&job->flush_rwlock);qemu_co_rwlock_unlock (&job->flush_rwlock); -if (job->sync_bitmap) { -BdrvDirtyBitmap *bm; -if (ret <0 ) { -/* Merge the successor back into the parent, delete nothing. */ - bm =bdrv_reclaim_dirty_bitmap (bs, job->sync_bitmap,NULL ); -assert (bm); -bdrv_enable_dirty_bitmap (job->sync_bitmap);
Hm, what is that function call doing here? It feels like it shouldn't have been part of your transactionless series (because other than this, there is no caller of bdrv_{en,dis}able_dirty_bitmap() at all).
(You're silently removing it here; removing it is fine, but I guess it shouldn't have been there in the first place)
Quoted 31 lines
block/backup.c +3
-7- }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);@@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, error:if (sync_bitmap) { -bdrv_reclaim_dirty_bitmap (bs, sync_bitmap,NULL ); +BdrvDirtyBitmap *ret; + ret =bdrv_dirty_bitmap_decref (bs, sync_bitmap,-1 ,NULL ); +assert (ret); } }include/block/block.h +0
-4@@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);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,
include/block/block.h void bdrv_dirty_bitmap_truncate(BlockDriverState *bs); · line 468
Quoted 10 lines
include/block/block.h +5
-2- BdrvDirtyBitmap *bitmap, - Error **errp); +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +int ret, +Error **errp); +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent);BdrvDirtyBitmap *bdrv_find_dirty_bitmap (BlockDriverState *bs,const char *name);void bdrv_dirty_bitmap_make_anon (BdrvDirtyBitmap *bitmap);
I'm happy with this patch if you drop the Error parameter from bdrv_dirty_bitmap_decref() (possibly dropping the return value, too) and turn all the error cases into assertions.
Max
Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
Quoted 35 lines · Max Reitz
Quoted 22 lines · John Snow
Allow bitmap successors to carry reference counts.
We can in a later patch use this ability to clean up the dirty bitmap according to both the individual job's success and the success of all jobs in the transaction group.
The code for cleaning up a bitmap is also moved from
backup_runtobackup_complete.Signed-off-by John Snow <[email protected]>--- block.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++---- block/backup.c | 23 ++++++--------- include/block/block.h | 11 ++++--- 3 files changed, 87 insertions(+), 26 deletions(-)block.c +0
-0block.c line 57
Quoted 7 lines
block.c +5
-0@@ -51,6 +51,12 @@#include <windows.h> #endif +typedef enum BitmapSuccessorAction { +SUCCESSOR_ACTION_UNDEFINED =0 , +SUCCESSOR_ACTION_ABDICATE , +SUCCESSOR_ACTION_RECLAIM +}BitmapSuccessorAction ;Maybe making this a QAPI enum can come in handy later. I don't know in which QMP commands one would use it, but if I could predict the future, I'd make trillions (or even billions) at the stock market.
Quoted 3 lines
(It's just that it already looks so much alike to a QAPI enum that I can't help but to think of making it one)
I don't see a reason to expose it, so I won't. We can always add it in later.
Quoted 57 lines
Quoted 57 lines
block.c +1
-0+/** * A BdrvDirtyBitmap can be in three possible states: * (1) successor is false and disabled is false: full r/w mode @@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {char *name;/* Optional non-empty unique ID */ int64_t size;/* Size of the bitmap (Number of sectors) */
bool disabled; /* Bitmap is read-only */
+ intsuccessor_refcount; /* Number of active handles to the
successor */
+ BitmapSuccessorAction act; /* Action to take on successor upon
release */QLIST_ENTRY(BdrvDirtyBitmap) list;};
block.c +4
-3@@ -5508,6 +5516,7 @@ intbdrv_dirty_bitmap_create_successor (BlockDriverState *bs,/* Install the successor and freeze the parent */ bitmap->successor = child; + bitmap->successor_refcount =1 ;return 0 ; }@@ -5515,9 +5524,9 @@ intbdrv_dirty_bitmap_create_successor(BlockDriverState *bs, * 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) +static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate (BlockDriverState *bs, +BdrvDirtyBitmap *bitmap, +Error **errp) {char *name; BdrvDirtyBitmap *successor = bitmap->successor;block.c +3
-3@@ -5542,9 +5551,9 @@ BdrvDirtyBitmap*bdrv_dirty_bitmap_abdicate(BlockDriverState *bs, * 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) +static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +Error **errp) {BdrvDirtyBitmap *successor = parent->successor;block.c +2
-0@@ -5563,6 +5572,64 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap (BlockDriverState *bs, return parent; } +static BdrvDirtyBitmap *bdrv_free_bitmap_successor(BlockDriverState *bs, + BdrvDirtyBitmap
Quoted 9 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5579
Quoted 4 lines
block.c +3
-0*parent, + Error **errp) +{ +if (parent->successor_refcount) {I don't know whether you're intending to call this function from anywhere outside of
bdrv_dirty_bitmap_decref(), but if you don't, please just make this anassert(!parent->successor_refcount).
OK.
Quoted 5 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5581
Quoted 2 lines
block.c +2
-0+error_setg (errp,"Cannot free the successor for bitmap '%s', " +"because the refcount is non-zero." , parent->name);Hm, again with the full stops? *g*
Can we make checkpatch check for this? I am serious. I can't stop myself from doing this.\n
Quoted 13 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5588
Quoted 7 lines
block.c +7
-0+return NULL ; + } + +switch (parent->act) { +case SUCCESSOR_ACTION_UNDEFINED : + error_setg(errp, "Cannot free the successor for bitmap '%s', " + "because the successor action has not yet been set.",Indeed, again with the full stops. ;-)
block.c BdrvDirtyBitmap · line 5591
Quoted 2 lines
block.c +2
-0+ parent->name); +return NULL ;
Quoted 8 lines
So you're (for now) only calling this function from
bdrv_dirty_bitmap_decref(), and that function always makes sure thatparent->actis set to SUCCESSOR_ACTION_{RECLAIM,ABDICATE}. Why not add anassert(parent->act != SUCCESSOR_ACTION_UNDEFINED)before the switch? (If that causes problems in regards to compiler warnings or something (not having all enum values covered in the switch), just add an assert(0); under "caseSUCCESSOR_ACTION_UNDEFINED:".)
OK, sure.
Quoted 11 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5600
Quoted 10 lines
block.c +10
-0+case SUCCESSOR_ACTION_RECLAIM : +return bdrv_reclaim_dirty_bitmap (bs, parent, errp); +case SUCCESSOR_ACTION_ABDICATE : +return bdrv_dirty_bitmap_abdicate (bs, parent, errp); +default : +error_setg (errp, +"Unrecognized successor action (%d), " +"cannot free successor for bitmap '%s'" , + parent->act, parent->name); +return NULL ;
Quoted 8 lines
This can be made an assert(0), a
g_assert_not_reached(), or simply anabort()(with the latter probably being the preferred way).So I think that all the
error_setg()calls are actually cases that should never happen. Better make themabort()and drop the Error parameter (because*_decref()and*_free()functions normally (for good reason) don't have Error parameters...).
Uhh, yeah. I'll harden these functions to be meaner about failing.
Quoted 6 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5604
Quoted 4 lines
block.c +4
-0+ } +} + +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,I don't know whether I'm that content with the name chosen, because
Quoted 9 lines
you're actually decrementing the refcount of the successor; but since the successor is basically a clone of the original bitmap (and I mean in the Star Trek sense, that it's a teleported clone and the original is intended to be destroyed so the successor can replace it), decrementing the refcount of the successor basically is equal to decrementing the refcount of the bitmap itself (as long as there is a successor, which you are asserting; maybe you want to add a comment about that to
include/block/block.h, that one can only use this on frozen bitmaps?).
Actual struggle I had when naming it myself. Went with the simpler answer that was less accurate. I might just augment with a comment explaining what exactly is being decremented.
Quoted 11 lines
Quoted 11 lines
block.c +11
-0+BdrvDirtyBitmap *parent, + int ret, + Error **errp) +{ +assert (bdrv_dirty_bitmap_frozen (parent )); +assert (parent->successor); + +if (ret) { + parent->act =SUCCESSOR_ACTION_RECLAIM ; + }else if (parent->act !=SUCCESSOR_ACTION_RECLAIM ) { + parent->act =SUCCESSOR_ACTION_ABDICATE ;
Quoted 9 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5620
Quoted 5 lines
block.c +5
-0+ } + + parent->successor_refcount--; +if (parent->successor_refcount ==0 ) { +return bdrv_free_bitmap_successor (bs, parent, errp);If you drop the Error parameter from
bdrv_free_bitmap_successor(), you can drop it from this function, too.
Noted.
Quoted 36 lines
Quoted 22 lines
block.c +12
-0+ } +return parent; +} + +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent) +{ +assert (bdrv_dirty_bitmap_frozen (parent)); +assert (parent->successor); + + parent->successor_refcount++; +} +static void dirty_bitmap_truncate (BdrvDirtyBitmap *bitmap,uint64_t size){ /* Should only be frozen during a block backup job, which shouldhave
block/backup.c +0
-0block/backup.c static void backup_complete(BlockJob *job, void · line 246
Quoted 7 lines
block/backup.c +4
-0@@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void *opaque)bdrv_unref (s->target); +if (s->sync_bitmap) { +BdrvDirtyBitmap *bm; + bm =bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, data->ret,NULL ); +assert (bm);You can use
&error_abortas the Error object and drop theassert(); or, if you are dropping the Error parameter, there is no need to check the return value at all, because it will always be non-NULL (there won't be any code path in the function returning NULL at all). Maybe you can even drop the return value, too.
Quoted 4 lines
I just looked through the series: Actually, you're never using the Error parameter for
bdrv_dirty_bitmap_decref()at all. Seems to me like you really should drop it (and maybe the return value along with it).
Return code is there for future usages, I suppose. I wanted to carry it around as a "generic" thing. In this one usage, we don't wind up needing it, but I wanted to make an allowance for it in this API.
I'll trim down the soft errors.
Quoted 12 lines
Quoted 12 lines
block/backup.c +2
-4+ } +block_job_completed (job, data->ret);g_free (data); }@@ -419,19 +425,6 @@ leave: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
Quoted 9 lines
block/backup.c leave: · line 428
Quoted 4 lines
nothing. */
block/backup.c +0
-3- bm =bdrv_reclaim_dirty_bitmap (bs, job->sync_bitmap,NULL ); -assert (bm); -bdrv_enable_dirty_bitmap (job->sync_bitmap);Hm, what is that function call doing here? It feels like it shouldn't have been part of your transactionless series (because other than this, there is no caller of bdrv_{en,dis}able_dirty_bitmap() at all).
Originally it was to re-enable a bitmap after it was frozen for the operation, but that concept has long since been ditched, so it's just a NOP right now, basically.
I'll try to pull it out of the original series.
Quoted 50 lines
(You're silently removing it here; removing it is fine, but I guess it shouldn't have been there in the first place)
Quoted 41 lines
block/backup.c +0
-2- }else { -/* Everything is fine, delete this bitmap and install the backup. */
block/backup.c +3
-5- bm =bdrv_dirty_bitmap_abdicate (bs, job->sync_bitmap,NULL ); -assert (bm); - } - }hbitmap_free (job->bitmap);bdrv_iostatus_disable (target);@@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs,BlockDriverState *target, error: if (sync_bitmap) { -bdrv_reclaim_dirty_bitmap (bs ,sync_bitmap ,NULL ); + BdrvDirtyBitmap *ret; + ret =bdrv_dirty_bitmap_decref (bs, sync_bitmap,-1 ,NULL ); +assert (ret); } }include/block/block.h +5
-6@@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);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_dirty_bitmap_decref (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +int ret, +Error **errp); +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent);BdrvDirtyBitmap *bdrv_find_dirty_bitmap (BlockDriverState *bs,const char *name);Quoted 1 line
void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);I'm happy with this patch if you drop the Error parameter from
bdrv_dirty_bitmap_decref()(possibly dropping the return value, too) and turn all the error cases into assertions.
Quoted 2 lines
Max
Okie-dokey.
--js
Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
In reply to Max ReitzQuoted 95 lines · Max Reitz
Quoted 22 lines · John Snow
Allow bitmap successors to carry reference counts.
We can in a later patch use this ability to clean up the dirty bitmap according to both the individual job's success and the success of all jobs in the transaction group.
The code for cleaning up a bitmap is also moved from
backup_runtobackup_complete.Signed-off-by John Snow <[email protected]>--- block.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++---- block/backup.c | 23 ++++++--------- include/block/block.h | 11 ++++--- 3 files changed, 87 insertions(+), 26 deletions(-)block.c +0
-0block.c line 57
Quoted 7 lines
block.c +5
-0@@ -51,6 +51,12 @@#include <windows.h> #endif +typedef enum BitmapSuccessorAction { +SUCCESSOR_ACTION_UNDEFINED =0 , +SUCCESSOR_ACTION_ABDICATE , +SUCCESSOR_ACTION_RECLAIM +}BitmapSuccessorAction ;Maybe making this a QAPI enum can come in handy later. I don't know in which QMP commands one would use it, but if I could predict the future, I'd make trillions (or even billions) at the stock market.
(It's just that it already looks so much alike to a QAPI enum that I can't help but to think of making it one)
Quoted 57 lines
block.c +1
-0+/** * A BdrvDirtyBitmap can be in three possible states: * (1) successor is false and disabled is false: full r/w mode @@ -65,6 +71,8 @@ struct BdrvDirtyBitmap {char *name;/* Optional non-empty unique ID */ int64_t size;/* Size of the bitmap (Number of sectors) */
bool disabled; /* Bitmap is read-only */
+ intsuccessor_refcount; /* Number of active handles to the
successor */
+ BitmapSuccessorAction act; /* Action to take on successor upon
release */QLIST_ENTRY(BdrvDirtyBitmap) list;};
block.c +4
-3@@ -5508,6 +5516,7 @@ intbdrv_dirty_bitmap_create_successor (BlockDriverState *bs,/* Install the successor and freeze the parent */ bitmap->successor = child; + bitmap->successor_refcount =1 ;return 0 ; }@@ -5515,9 +5524,9 @@ intbdrv_dirty_bitmap_create_successor(BlockDriverState *bs, * 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) +static BdrvDirtyBitmap *bdrv_dirty_bitmap_abdicate (BlockDriverState *bs, +BdrvDirtyBitmap *bitmap, +Error **errp) {char *name; BdrvDirtyBitmap *successor = bitmap->successor;block.c +3
-3@@ -5542,9 +5551,9 @@ BdrvDirtyBitmap*bdrv_dirty_bitmap_abdicate(BlockDriverState *bs, * 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) +static BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +Error **errp) {BdrvDirtyBitmap *successor = parent->successor;block.c +2
-0@@ -5563,6 +5572,64 @@ BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap (BlockDriverState *bs, return parent; } +static BdrvDirtyBitmap *bdrv_free_bitmap_successor(BlockDriverState *bs, + BdrvDirtyBitmap
Quoted 9 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5579
Quoted 4 lines
block.c +3
-0*parent, + Error **errp) +{ +if (parent->successor_refcount) {I don't know whether you're intending to call this function from anywhere outside of
bdrv_dirty_bitmap_decref(), but if you don't, please just make this anassert(!parent->successor_refcount).
No, deleted.
Quoted 5 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5581
Quoted 2 lines
block.c +2
-0+error_setg (errp,"Cannot free the successor for bitmap '%s', " +"because the refcount is non-zero." , parent->name);Hm, again with the full stops? *g*
Erased error message altogether.
Quoted 10 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5588
Quoted 7 lines
block.c +7
-0+return NULL ; + } + +switch (parent->act) { +case SUCCESSOR_ACTION_UNDEFINED : + error_setg(errp, "Cannot free the successor for bitmap '%s', " + "because the successor action has not yet been set.",Indeed, again with the full stops. ;-)
And this one.
Quoted 11 lines
block.c BdrvDirtyBitmap · line 5591
Quoted 2 lines
block.c +2
-0+ parent->name); +return NULL ;So you're (for now) only calling this function from
bdrv_dirty_bitmap_decref(), and that function always makes sure thatparent->actis set to SUCCESSOR_ACTION_{RECLAIM,ABDICATE}. Why not add anassert(parent->act != SUCCESSOR_ACTION_UNDEFINED)before the switch? (If that causes problems in regards to compiler warnings or something (not having all enum values covered in the switch), just add an assert(0); under "caseSUCCESSOR_ACTION_UNDEFINED:".)
Will use g_assert_not_reached() because I like the way it reads.
Quoted 11 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5600
Quoted 10 lines
block.c +10
-0+case SUCCESSOR_ACTION_RECLAIM : +return bdrv_reclaim_dirty_bitmap (bs, parent, errp); +case SUCCESSOR_ACTION_ABDICATE : +return bdrv_dirty_bitmap_abdicate (bs, parent, errp); +default : +error_setg (errp, +"Unrecognized successor action (%d), " +"cannot free successor for bitmap '%s'" , + parent->act, parent->name); +return NULL ;
Quoted 8 lines
This can be made an assert(0), a
g_assert_not_reached(), or simply anabort()(with the latter probably being the preferred way).So I think that all the
error_setg()calls are actually cases that should never happen. Better make themabort()and drop the Error parameter (because*_decref()and*_free()functions normally (for good reason) don't have Error parameters...).
The problem all stems from when I made abdicate and reclaim callable interfaces in the original patch series, so they carried errp pointers and I let them flow outwards from there.
So, the transactionless set is still on-list, so I could clean those interfaces too, but I suppose I wanted to see how this series was received before I squashed too many things back into the v1 ... I'd rather leave that series be for obvious reasons.
Quoted 6 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5604
Quoted 4 lines
block.c +4
-0+ } +} + +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,I don't know whether I'm that content with the name chosen, because
Quoted 9 lines
you're actually decrementing the refcount of the successor; but since the successor is basically a clone of the original bitmap (and I mean in the Star Trek sense, that it's a teleported clone and the original is intended to be destroyed so the successor can replace it), decrementing the refcount of the successor basically is equal to decrementing the refcount of the bitmap itself (as long as there is a successor, which you are asserting; maybe you want to add a comment about that to
include/block/block.h, that one can only use this on frozen bitmaps?).
I could get clever with the name and call it bdrv_frozen_bitmap_decref, which hopefully still shows membership to the bdrv_dirty_bitmap class of functions but clarifies its usage sufficiently.
Quoted 11 lines
Quoted 11 lines
block.c +11
-0+BdrvDirtyBitmap *parent, + int ret, + Error **errp) +{ +assert (bdrv_dirty_bitmap_frozen (parent )); +assert (parent->successor); + +if (ret) { + parent->act =SUCCESSOR_ACTION_RECLAIM ; + }else if (parent->act !=SUCCESSOR_ACTION_RECLAIM ) { + parent->act =SUCCESSOR_ACTION_ABDICATE ;
Quoted 9 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5620
Quoted 5 lines
block.c +5
-0+ } + + parent->successor_refcount--; +if (parent->successor_refcount ==0 ) { +return bdrv_free_bitmap_successor (bs, parent, errp);If you drop the Error parameter from
bdrv_free_bitmap_successor(), you can drop it from this function, too.
Done.
Quoted 36 lines
Quoted 22 lines
block.c +12
-0+ } +return parent; +} + +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent) +{ +assert (bdrv_dirty_bitmap_frozen (parent)); +assert (parent->successor); + + parent->successor_refcount++; +} +static void dirty_bitmap_truncate (BdrvDirtyBitmap *bitmap,uint64_t size){ /* Should only be frozen during a block backup job, which shouldhave
block/backup.c +0
-0block/backup.c static void backup_complete(BlockJob *job, void · line 246
Quoted 7 lines
block/backup.c +4
-0@@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void *opaque)bdrv_unref (s->target); +if (s->sync_bitmap) { +BdrvDirtyBitmap *bm; + bm =bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, data->ret,NULL ); +assert (bm);You can use
&error_abortas the Error object and drop theassert(); or, if you are dropping the Error parameter, there is no need to check the return value at all, because it will always be non-NULL (there won't be any code path in the function returning NULL at all). Maybe you can even drop the return value, too.
Quoted 4 lines
I just looked through the series: Actually, you're never using the Error parameter for
bdrv_dirty_bitmap_decref()at all. Seems to me like you really should drop it (and maybe the return value along with it).
I actually use this parameter to return the "new bitmap" (if any) after the decrement operation. I wanted to leave the window open for merge optimizations, so I tell the caller which bitmap remains after the operation.
I will cull the errp, but will likely leave the return code.
Quoted 21 lines
Quoted 13 lines
block/backup.c +2
-4+ } +block_job_completed (job, data->ret);g_free (data); }@@ -419,19 +425,6 @@ leave: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. */
block/backup.c leave: · line 428
Quoted 3 lines
block/backup.c +0
-3- bm =bdrv_reclaim_dirty_bitmap (bs, job->sync_bitmap,NULL ); -assert (bm); -bdrv_enable_dirty_bitmap (job->sync_bitmap);Hm, what is that function call doing here? It feels like it shouldn't have been part of your transactionless series (because other than this, there is no caller of bdrv_{en,dis}able_dirty_bitmap() at all).
Quoted 3 lines
(You're silently removing it here; removing it is fine, but I guess it shouldn't have been there in the first place)
Fixed in v4 for the transactionless series.
Quoted 49 lines
Quoted 41 lines
block/backup.c +0
-2- }else { -/* Everything is fine, delete this bitmap and install the backup. */
block/backup.c +3
-5- bm =bdrv_dirty_bitmap_abdicate (bs, job->sync_bitmap,NULL ); -assert (bm); - } - }hbitmap_free (job->bitmap);bdrv_iostatus_disable (target);@@ -535,6 +528,8 @@ void backup_start(BlockDriverState *bs,BlockDriverState *target, error: if (sync_bitmap) { -bdrv_reclaim_dirty_bitmap (bs ,sync_bitmap ,NULL ); + BdrvDirtyBitmap *ret; + ret =bdrv_dirty_bitmap_decref (bs, sync_bitmap,-1 ,NULL ); +assert (ret); } }include/block/block.h +5
-6@@ -458,12 +458,11 @@ void bdrv_dirty_bitmap_truncate(BlockDriverState *bs);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_dirty_bitmap_decref (BlockDriverState *bs, +BdrvDirtyBitmap *parent, +int ret, +Error **errp); +void bdrv_dirty_bitmap_incref (BdrvDirtyBitmap *parent);BdrvDirtyBitmap *bdrv_find_dirty_bitmap (BlockDriverState *bs,const char *name);Quoted 1 line
void bdrv_dirty_bitmap_make_anon(BdrvDirtyBitmap *bitmap);I'm happy with this patch if you drop the Error parameter from
bdrv_dirty_bitmap_decref()(possibly dropping the return value, too) and turn all the error cases into assertions.Max
Re: [Qemu-devel] [PATCH 05/11] block: add delayed bitmap successor cleanup
Quoted 4 lines · John Snow
Quoted 1 line · Max Reitz
On 2015-03-04 at 23:15, John Snow wrote:
[snip]
Quoted 16 lines
Quoted 6 lines
block.c BdrvDirtyBitmap *bdrv_reclaim_dirty_bitmap(BlockDriverState *bs, · line 5604
Quoted 4 lines
block.c +4
-0+ } +} + +BdrvDirtyBitmap *bdrv_dirty_bitmap_decref(BlockDriverState *bs,I don't know whether I'm that content with the name chosen, because
Quoted 9 lines
you're actually decrementing the refcount of the successor; but since the successor is basically a clone of the original bitmap (and I mean in the Star Trek sense, that it's a teleported clone and the original is intended to be destroyed so the successor can replace it), decrementing the refcount of the successor basically is equal to decrementing the refcount of the bitmap itself (as long as there is a successor, which you are asserting; maybe you want to add a comment about that to
include/block/block.h, that one can only use this on frozen bitmaps?).
Quoted 1 line
I could get clever with the name and call it
bdrv_frozen_bitmap_decref, which hopefully still shows membership to thebdrv_dirty_bitmapclass of functions but clarifies its usage sufficiently.
Sounds good to me.
[snip]
Quoted 19 lines
Quoted 19 lines
block/backup.c static void backup_complete(BlockJob *job, void · line 246
Quoted 12 lines
block/backup.c +4
-0@@ -240,6 +240,12 @@ static void backup_complete(BlockJob *job, void *opaque)bdrv_unref (s->target); +if (s->sync_bitmap) { +BdrvDirtyBitmap *bm; + bm =bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, data->ret,NULL ); +assert (bm);You can use
&error_abortas the Error object and drop theassert(); or, if you are dropping the Error parameter, there is no need to check the return value at all, because it will always be non-NULL (there won't be any code path in the function returning NULL at all). Maybe you can even drop the return value, too.
Quoted 8 lines
Quoted 4 lines
I just looked through the series: Actually, you're never using the Error parameter for
bdrv_dirty_bitmap_decref()at all. Seems to me like you really should drop it (and maybe the return value along with it).I actually use this parameter to return the "new bitmap" (if any) after the decrement operation. I wanted to leave the window open for merge optimizations, so I tell the caller which bitmap remains after the operation.
I will cull the errp, but will likely leave the return code.
OK.
Max
PATCH 6/11 qmp: Add an implementation wrapper for qmp_drive_backup
In reply to John SnowWe'd like to be able to specify the callback given to backup_start manually in the case of transactions, so split apart qmp_drive_backup into an implementation and a wrapper.
Switch drive_backup_prepare to use the new wrapper, but don't overload the callback and closure yet.
This is kind of gross, but we need to forward-declare the wrapper for the drive_backup transaction to be able to use. I could try moving the transaction blocks lower instead, but I wanted (for v1, at least) to minimize code movement so that the core changes were easiest to see.
If anyone has suggestions on organization, please suggest them.
--- blockdev.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 19 deletions(-)
blockdev.c +58 -19
@@ -1449,6 +1449,19 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void _drive_backup (const char *device,const char *target, +bool has_format,const char *format, +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, +BlockCompletionFunc *cb,void *opaque, +Error **errp); +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;
@@ -1768,15 +1781,16 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp) state->aio_context =bdrv_get_aio_context (bs);aio_context_acquire (state->aio_context); -qmp_drive_backup (backup->device, backup->target, - backup->has_format, backup->format, - 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); +_drive_backup (backup->device, backup->target, + backup->has_format, backup->format, + 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, +NULL ,NULL , + &local_err);if (local_err) {error_propagate (errp, local_err);return ;
@@ -2717,15 +2731,18 @@ out:aio_context_release (aio_context); } -void qmp_drive_backup (const char *device,const char *target, -bool has_format,const char *format, -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) +static void _drive_backup (const char *device,const char *target, +bool has_format,const char *format, +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, +BlockCompletionFunc *cb,void *opaque, +Error **errp) {BlockDriverState *bs;BlockDriverState *target_bs;
@@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const char *target, } } +if (cb ==NULL ) { + cb = block_job_cb; + } +if (opaque ==NULL ) { + opaque = bs; + }backup_start (bs, target_bs, speed, sync, bmap, on_source_error, on_target_error, - block_job_cb, bs, &local_err); + cb, opaque, &local_err);if (local_err !=NULL ) {bdrv_unref (target_bs);error_propagate (errp, local_err);
@@ -2850,6 +2873,22 @@ out:aio_context_release (aio_context); } +void qmp_drive_backup (const char *device,const char *target, +bool has_format,const char *format, +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) +{ +_drive_backup (device, target, has_format, format, sync, has_mode, mode, + has_speed, speed, has_bitmap, bitmap, has_on_source_error, + on_source_error, has_on_target_error, on_target_error, +NULL ,NULL , errp); +} +BlockDeviceInfoList *qmp_query_named_block_nodes (Error **errp) {return bdrv_named_nodes_list ();
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
Quoted 24 lines · John Snow
We'd like to be able to specify the callback given to
backup_startmanually in the case of transactions, so split apartqmp_drive_backupinto an implementation and a wrapper.Switch
drive_backup_prepareto use the new wrapper, but don't overload the callback and closure yet.This is kind of gross, but we need to forward-declare the wrapper for the
drive_backuptransaction to be able to use. I could try moving the transaction blocks lower instead, but I wanted (for v1, at least) to minimize code movement so that the core changes were easiest to see.If anyone has suggestions on organization, please suggest them.
Signed-off-by John Snow <[email protected]>--- blockdev.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 19 deletions(-)blockdev.c +0
-0
blockdev.c void undo_transaction_wrapper(BlkTransactionState *common) · line 1452
Quoted 4 lines
blockdev.c +1
-0@@ -1449,6 +1449,19 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void _drive_backup(const char *device, const char *target,
Please don't use leading underscores. (Please.)
Just call it do_drive_backup() or something.
Other than that, I'm okay with a forward-declaration; if you want to move it, but I'd be fine with moving the code, too.
With _drive_backup() being called do_drive_backup():
Quoted 109 lines
blockdev.c +57
-19+ bool has_format, const char *format, + 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, + BlockCompletionFunc *cb, void *opaque, + Error **errp); +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;@@ -1768,15 +1781,16 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp) state->aio_context =bdrv_get_aio_context (bs);aio_context_acquire (state->aio_context); -qmp_drive_backup (backup->device, backup->target, - backup->has_format, backup->format, - 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); +_drive_backup (backup->device, backup->target, + backup->has_format, backup->format, + 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, +NULL ,NULL , + &local_err);if (local_err) {error_propagate (errp, local_err);return ;@@ -2717,15 +2731,18 @@ out:aio_context_release (aio_context); } -void qmp_drive_backup (const char *device,const char *target, -bool has_format,const char *format, -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) +static void _drive_backup (const char *device,const char *target, +bool has_format,const char *format, +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, +BlockCompletionFunc *cb,void *opaque, +Error **errp) {BlockDriverState *bs;BlockDriverState *target_bs;@@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const char *target, } } +if (cb ==NULL ) { + cb = block_job_cb; + } +if (opaque ==NULL ) { + opaque = bs; + }backup_start (bs, target_bs, speed, sync, bmap, on_source_error, on_target_error, - block_job_cb, bs, &local_err); + cb, opaque, &local_err);if (local_err !=NULL ) {bdrv_unref (target_bs);error_propagate (errp, local_err);@@ -2850,6 +2873,22 @@ out:aio_context_release (aio_context); } +void qmp_drive_backup (const char *device,const char *target, +bool has_format,const char *format, +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) +{ +_drive_backup (device, target, has_format, format, sync, has_mode, mode, + has_speed, speed, has_bitmap, bitmap, has_on_source_error, + on_source_error, has_on_target_error, on_target_error, +NULL ,NULL , errp); +} +BlockDeviceInfoList *qmp_query_named_block_nodes (Error **errp) {return bdrv_named_nodes_list ();
Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
Quoted 33 lines · Max Reitz
Quoted 25 lines · John Snow
We'd like to be able to specify the callback given to
backup_startmanually in the case of transactions, so split apartqmp_drive_backupinto an implementation and a wrapper.Switch
drive_backup_prepareto use the new wrapper, but don't overload the callback and closure yet.This is kind of gross, but we need to forward-declare the wrapper for the
drive_backuptransaction to be able to use. I could try moving the transaction blocks lower instead, but I wanted (for v1, at least) to minimize code movement so that the core changes were easiest to see.If anyone has suggestions on organization, please suggest them.
Signed-off-by John Snow <[email protected]>--- blockdev.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 19 deletions(-)blockdev.c +0
-0blockdev.c void · line 1452
Quoted 4 lines
blockdev.c +1
-0@@ -1449,6 +1449,19 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void _drive_backup(const char *device, const char *target,Please don't use leading underscores. (Please.)
Quoted 9 lines
Just call it
do_drive_backup()or something.Other than that, I'm okay with a forward-declaration; if you want to move it, but I'd be fine with moving the code, too.
With
_drive_backup()being calleddo_drive_backup():Reviewed-by Max Reitz <[email protected]>
Moving the code around might be better, it just makes for a much noisier patch. I made my decisions for v1 to keep code disturbed the least.
Once more critiques roll in I will decide what to do.
I'll replace with "do_drive_backup" unless you can think of a nice three-letter word to replace "do" so that I don't even have to perturb the alignment context ;)
Quoted 116 lines
Quoted 115 lines
blockdev.c +22
-9+ bool has_format, const char *format, + 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, + BlockCompletionFunc *cb, void *opaque, + Error **errp); +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;@@ -1768,15 +1781,16 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) state->aio_context = bdrv_get_aio_context(bs);aio_context_acquire (state->aio_context); -qmp_drive_backup (backup->device, backup->target, - backup->has_format, backup->format, - 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); +_drive_backup (backup->device, backup->target, + backup->has_format, backup->format, + 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, +NULL ,NULL , + &local_err);if (local_err) {error_propagate(errp, local_err); return;blockdev.c +12
-9@@ -2717,15 +2731,18 @@ out:aio_context_release (aio_context); } -void qmp_drive_backup (const char *device,const char *target, -bool has_format,const char *format, -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) +static void _drive_backup(const char *device, const char *target, +bool has_format,const char *format, +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, +BlockCompletionFunc *cb,void *opaque, +Error **errp) {BlockDriverState *bs;BlockDriverState *target_bs;blockdev.c +23
-1@@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const char *target, } } +if (cb ==NULL ) { + cb = block_job_cb; + } +if (opaque ==NULL ) { + opaque = bs; + }backup_start (bs, target_bs, speed, sync, bmap, on_source_error, on_target_error, - block_job_cb, bs, &local_err); + cb, opaque, &local_err);if (local_err !=NULL ) {bdrv_unref (target_bs);error_propagate (errp, local_err);@@ -2850,6 +2873,22 @@ out:aio_context_release (aio_context); } +void qmp_drive_backup (const char *device,const char *target, +bool has_format,const char *format, +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) +{ +_drive_backup (device, target, has_format, format, sync, has_mode, mode, + has_speed, speed, has_bitmap, bitmap, has_on_source_error, + on_source_error, has_on_target_error, on_target_error, +NULL ,NULL , errp); +} +BlockDeviceInfoList *qmp_query_named_block_nodes(Error **errp) { return bdrv_named_nodes_list();
Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
Quoted 43 lines · John Snow
Quoted 40 lines · Max Reitz
Quoted 25 lines · John Snow
We'd like to be able to specify the callback given to
backup_startmanually in the case of transactions, so split apartqmp_drive_backupinto an implementation and a wrapper.Switch
drive_backup_prepareto use the new wrapper, but don't overload the callback and closure yet.This is kind of gross, but we need to forward-declare the wrapper for the
drive_backuptransaction to be able to use. I could try moving the transaction blocks lower instead, but I wanted (for v1, at least) to minimize code movement so that the core changes were easiest to see.If anyone has suggestions on organization, please suggest them.
Signed-off-by John Snow <[email protected]>--- blockdev.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 19 deletions(-)blockdev.c +0
-0blockdev.c void · line 1452
Quoted 4 lines
blockdev.c +1
-0@@ -1449,6 +1449,19 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void _drive_backup(const char *device, const char *target,Please don't use leading underscores. (Please.)
Just call it
do_drive_backup()or something.Other than that, I'm okay with a forward-declaration; if you want to move it, but I'd be fine with moving the code, too.
With
_drive_backup()being calleddo_drive_backup():
Quoted 8 lines
Quoted 2 lines
Reviewed-by Max Reitz <[email protected]>Moving the code around might be better, it just makes for a much noisier patch. I made my decisions for v1 to keep code disturbed the least.
Once more critiques roll in I will decide what to do.
I'll replace with "
do_drive_backup" unless you can think of a nice three-letter word to replace "do" so that I don't even have to perturb the alignment context ;)
Well, you're still prepending "static", so I think you won't get around perturbing the context :-)
Max
Quoted 120 lines
Quoted 119 lines
Quoted 118 lines
blockdev.c +20
-9+ bool has_format, const char *format, + 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, + BlockCompletionFunc *cb, void *opaque, + Error **errp); +/* internal snapshot private data */ typedef struct InternalSnapshotState { BlkTransactionState common;@@ -1768,15 +1781,16 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) state->aio_context = bdrv_get_aio_context(bs);aio_context_acquire (state->aio_context); -qmp_drive_backup (backup->device, backup->target, - backup->has_format, backup->format, - 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); + _drive_backup(backup->device, backup->target, + backup->has_format, backup->format, + 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,blockdev.c +14
-9+NULL ,NULL , + &local_err);if (local_err) {error_propagate (errp, local_err);return ;@@ -2717,15 +2731,18 @@ out:aio_context_release (aio_context); } -void qmp_drive_backup (const char *device,const char *target, -bool has_format,const char *format, -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) +static void _drive_backup(const char *device, const char *target, +bool has_format,const char *format, +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, +BlockCompletionFunc *cb,void *opaque, +Error **errp) {BlockDriverState *bs;BlockDriverState *target_bs;blockdev.c +20
-1@@ -2837,9 +2854,15 @@ void qmp_drive_backup(const char *device, const char *target, } } +if (cb ==NULL ) { + cb = block_job_cb; + } +if (opaque ==NULL ) { + opaque = bs; + }backup_start (bs, target_bs, speed, sync, bmap, on_source_error, on_target_error, - block_job_cb, bs, &local_err); + cb, opaque, &local_err);if (local_err !=NULL ) {bdrv_unref (target_bs);error_propagate (errp, local_err);@@ -2850,6 +2873,22 @@ out:aio_context_release (aio_context); } +void qmp_drive_backup (const char *device,const char *target, +bool has_format,const char *format, +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) +{ + _drive_backup(device, target, has_format, format, sync, has_mode, mode, + has_speed, speed, has_bitmap, bitmap, has_on_source_error, + on_source_error, has_on_target_error,
on_target_error,blockdev.c +3
-0+ NULL, NULL, errp); +} +BlockDeviceInfoList *qmp_query_named_block_nodes (Error **errp) {return bdrv_named_nodes_list ();
Re: [Qemu-devel] [PATCH 06/11] qmp: Add an implementation wrapper for qmp_drive_backup
In reply to John SnowQuoted 5 lines · John Snow
Quoted 5 lines
Other than that, I'm okay with a forward-declaration; if you want to move it, but I'd be fine with moving the code, too.
With
_drive_backup()being calleddo_drive_backup():
Quoted 7 lines
Quoted 2 lines
Reviewed-by Max Reitz <[email protected]>Moving the code around might be better, it just makes for a much noisier patch. I made my decisions for v1 to keep code disturbed the least.
Once more critiques roll in I will decide what to do.
I like avoiding forward declarations for non-recursive static functions (aka topological sorting); but if you do code motion, do it in a separate patch from the other changes.
Signature · 2 lines
Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
PATCH 7/11 block: drive_backup transaction callback support
In reply to John SnowThis patch actually implements the transactional callback system for the drive_backup transaction.
(1) We manually pick up a reference to the bitmap if present to allow its cleanup to be delayed until after all drive_backup jobs launched by the transaction have fully completed.
(2) We create a functional closure that envelops the original drive_backup callback, to be able to intercept the completion status and return code for the job.
(3) We add the drive_backup_cb method for the drive_backup action, which unpacks the completion information and invokes the final cleanup.
(4) backup_transaction_complete will perform the final cleanup on the backup job.
(5) In the case of transaction cancellation, drive_backup_cb is still responsible for cleaning up the mess we may have already made.
--- block/backup.c | 9 +++++++ blockdev.c | 64 ++++++++++++++++++++++++++++++++++++++--------- include/block/block_int.h | 8 ++++++ 3 files changed, 69 insertions(+), 12 deletions(-)
block/backup.c +9 -0
@@ -233,6 +233,15 @@ typedef struct {int ret; } BackupCompleteData; +void backup_transaction_complete (BlockJob *job,int ret) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); + +if (s->sync_bitmap) { +bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, ret,NULL ); + } +} +static void backup_complete (BlockJob *job,void *opaque) {BackupBlockJob *s =container_of (job, BackupBlockJob, common);
blockdev.c +52 -12
130 lines not on this page · raw message
include/block/block_int.h +8 -0
@@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, BlockCompletionFunc *cb, void *opaque, Error **errp); +/* +* backup_transaction_complete +* @job The BackupJob that was associated with a transaction +* @ret Amalgamated return code for the entire transaction +*/ +void backup_transaction_complete (BlockJob *job,int ret); + +void blk_dev_change_media_cb (BlockBackend *blk,bool load);bool blk_dev_has_removable_media (BlockBackend *blk);void blk_dev_eject_request (BlockBackend *blk,bool force);
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
Quoted 197 lines · John Snow
This patch actually implements the transactional callback system for the
drive_backuptransaction.(1) We manually pick up a reference to the bitmap if present to allow its cleanup to be delayed until after all
drive_backupjobs launched by the transaction have fully completed.(2) We create a functional closure that envelops the original
drive_backupcallback, to be able to intercept the completion status and return code for the job.(3) We add the
drive_backup_cbmethod for thedrive_backupaction, which unpacks the completion information and invokes the final cleanup.(4)
backup_transaction_completewill perform the final cleanup on the backup job.(5) In the case of transaction cancellation,
drive_backup_cbis still responsible for cleaning up the mess we may have already made.Signed-off-by John Snow <[email protected]>--- block/backup.c | 9 +++++++ blockdev.c | 64 ++++++++++++++++++++++++++++++++++++++--------- include/block/block_int.h | 8 ++++++ 3 files changed, 69 insertions(+), 12 deletions(-)block/backup.c +9
-0@@ -233,6 +233,15 @@ typedef struct {int ret; } BackupCompleteData; +void backup_transaction_complete (BlockJob *job,int ret) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); + +if (s->sync_bitmap) { +bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, ret,NULL ); + } +} +static void backup_complete (BlockJob *job,void *opaque) {BackupBlockJob *s =container_of (job, BackupBlockJob, common);blockdev.c +52
-12@@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque, int ret)typedef void (CallbackFn )(void *opaque,int ret); -/* Temporary. Removed in the next patch. */ -CallbackFn *new_transaction_wrapper (BlkTransactionState *common, -void *opaque, -void (*callback)(void *,int ), -void **new_opaque); - -void undo_transaction_wrapper (BlkTransactionState *common); -/** * Create a new transactional callback wrapper. * @@ -1389,7 +1381,7 @@ void undo_transaction_wrapper(BlkTransactionState *common);* * @return The callback to be used instead of @callback. */ -CallbackFn *new_transaction_wrapper (BlkTransactionState *common, +static CallbackFn *new_transaction_wrapper (BlkTransactionState *common,void *opaque,CallbackFn *callback,void **new_opaque)@@ -1416,7 +1408,7 @@ CallbackFn *new_transaction_wrapper(BlkTransactionState *common,/** * Undo any actions performed by the above call. */ -void undo_transaction_wrapper (BlkTransactionState *common) +static void undo_transaction_wrapper (BlkTransactionState *common) {BlkTransactionList *btl = common->list;BlkTransactionState *bts;@@ -1449,6 +1441,7 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void block_job_cb (void *opaque,int ret); static void _drive_backup(const char *device, const char *target, bool has_format, const char *format, enum MirrorSyncMode sync,@@ -1767,6 +1760,9 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)BlockDriverState *bs;DriveBackup *backup;Error *local_err =NULL ; +CallbackFn *cb; +void *opaque; +BdrvDirtyBitmap *bmap =NULL ;assert (common->action->kind ==TRANSACTION_ACTION_KIND_DRIVE_BACKUP ); backup = common->action->drive_backup;@@ -1777,6 +1773,19 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)return ; } +/* BackupBlockJob is opaque to us, so look up the bitmap ourselves */ +if (backup->has_bitmap) { + bmap =bdrv_find_dirty_bitmap (bs, backup->bitmap); +if (!bmap) { +error_setg (errp,"Bitmap '%s' could not be found" , backup->bitmap); +return ; + } + } + +/* Create our transactional callback wrapper, +and register that we'd like to call .cb() later. */ + cb =new_transaction_wrapper (common, bs, block_job_cb, &opaque); +/* AioContext is released in .clean() */ state->aio_context =bdrv_get_aio_context (bs);aio_context_acquire (state->aio_context);@@ -1789,7 +1798,7 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp) backup->has_bitmap, backup->bitmap, backup->has_on_source_error, backup->on_source_error, backup->has_on_target_error, backup->on_target_error, -NULL ,NULL , + cb, opaque, &local_err);if (local_err) {error_propagate (errp, local_err);@@ -1798,6 +1807,11 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp) state->bs = bs; state->job = state->bs->job; +/* Keep the job alive until .cb(), too. */ +block_job_incref (state->job); +if (bmap) { +bdrv_dirty_bitmap_incref (bmap); + } }static void drive_backup_abort (BlkTransactionState *common)@@ -1809,6 +1823,10 @@ static void drive_backup_abort(BlkTransactionState *common)if (bs && bs->job && bs->job == state->job) {block_job_cancel_sync (bs->job); } + +/* Undo any callback actions we may have done. Putting down references +* obtained during prepare() is handled by cb(). */ +undo_transaction_wrapper (common); }static void drive_backup_clean (BlkTransactionState *common)@@ -1820,6 +1838,27 @@ static void drive_backup_clean(BlkTransactionState *common) } } +static void drive_backup_cb (BlkTransactionState *common) +{ +BlkTransactionData *btd = common->opaque; +BlockDriverState *bs = btd->opaque; +DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common); + +assert (state->bs == bs); +if (bs->job) { +assert (state->job == bs->job); + } + + state->aio_context =bdrv_get_aio_context (bs); +aio_context_acquire (state->aio_context); + +/* Note: We also have the individual job's return code in btd->ret */ +backup_transaction_complete (state->job, common->list->status); +block_job_decref (state->job); + +aio_context_release (state->aio_context); +} + typedef struct BlockdevBackupState { BlkTransactionState common; BlockDriverState *bs;@@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean + .clean = drive_backup_clean, + .cb = drive_backup_cb }, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),include/block/block_int.h +0
-0
include/block/block_int.h void backup_start(BlockDriverState *bs, BlockDriverState *target, · line 624
Quoted 6 lines
include/block/block_int.h +3
-0@@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, BlockCompletionFunc *cb, void *opaque, Error **errp); +/* +* backup_transaction_complete +* @job The BackupJob that was associated with a transaction
s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no structure named "BackupJob", but this looks like there might be one)
include/block/block_int.h void backup_start(BlockDriverState *bs, BlockDriverState *target, · line 625
Quoted 1 line
include/block/block_int.h +1
-0+* @ret Amalgamated return code for the entire transaction
Hm. The call to this function you're introducing in this patch will probably stay the only one so there won't be anyone who'll have to worry about what this means, but if there was, they probably won't reach a conclusive result.
I know what it means because I've seen patch 3 (right now it means "everything OR-ed together so it's 0 for success or some non-zero (maybe positive, maybe negative, depending on whether you have an even or an odd number of errors, and depending on whether the jobs return negative values for errors or not) for error"), but I wouldn't be able to infer it from this. At the least you should add that 0 means success and everything else means error (if you take my suggestion for patch 3, it would be 0 for success and -errno for error, where that error number is one of the errors encountered).
Other than that, looks good (as far as I can tell with my still limited insights into patch 3).
Max
Quoted 7 lines
include/block/block_int.h +4
-0+*/ +void backup_transaction_complete (BlockJob *job,int ret); + +void blk_dev_change_media_cb (BlockBackend *blk,bool load);bool blk_dev_has_removable_media (BlockBackend *blk);void blk_dev_eject_request (BlockBackend *blk,bool force);
Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
Quoted 234 lines · Max Reitz
Quoted 206 lines · John Snow
This patch actually implements the transactional callback system for the
drive_backuptransaction.(1) We manually pick up a reference to the bitmap if present to allow its cleanup to be delayed until after all
drive_backupjobs launched by the transaction have fully completed.(2) We create a functional closure that envelops the original
drive_backup
callback, to be able to intercept the completion status and
return code
for the job.(3) We add the
drive_backup_cbmethod for thedrive_backupaction, which unpacks the completion information and invokes the final cleanup.(4)
backup_transaction_completewill perform the final cleanup on the backup job.(5) In the case of transaction cancellation,
drive_backup_cbis still responsible for cleaning up the mess we may have already made.Signed-off-by John Snow <[email protected]>--- block/backup.c | 9 +++++++ blockdev.c | 64 ++++++++++++++++++++++++++++++++++++++--------- include/block/block_int.h | 8 ++++++ 3 files changed, 69 insertions(+), 12 deletions(-)block/backup.c +9
-0@@ -233,6 +233,15 @@ typedef struct {int ret; } BackupCompleteData; +void backup_transaction_complete (BlockJob *job,int ret) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); + +if (s->sync_bitmap) { +bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, ret,NULL ); + } +} +static void backup_complete (BlockJob *job,void *opaque) {BackupBlockJob *s =container_of (job, BackupBlockJob, common);blockdev.c +1
-9@@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque, int ret)typedef void (CallbackFn )(void *opaque,int ret); -/* Temporary. Removed in the next patch. */ -CallbackFn *new_transaction_wrapper (BlkTransactionState *common, -void *opaque, -void (*callback)(void *,int ), -void **new_opaque); - -void undo_transaction_wrapper (BlkTransactionState *common); -/** * Create a new transactional callback wrapper. * @@ -1389,7 +1381,7 @@ voidundo_transaction_wrapper(BlkTransactionState *common); * * @return The callback to be used instead of @callback. */ -CallbackFn *new_transaction_wrapper(BlkTransactionState *common, +static CallbackFn *new_transaction_wrapper(BlkTransactionState *common, void *opaque, CallbackFn *callback,void **new_opaque)
blockdev.c +1
-1@@ -1416,7 +1408,7 @@ CallbackFn *new_transaction_wrapper(BlkTransactionState *common, /** * Undo any actions performed by the above call. */ -void undo_transaction_wrapper(BlkTransactionState *common) +static void undo_transaction_wrapper(BlkTransactionState *common) { BlkTransactionList *btl = common->list;BlkTransactionState *bts;blockdev.c +4
-0@@ -1449,6 +1441,7 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void block_job_cb (void *opaque,int ret); static void _drive_backup(const char *device, const char *target, bool has_format, const char *format, enum MirrorSyncMode sync,@@ -1767,6 +1760,9 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)BlockDriverState *bs;DriveBackup *backup;Error *local_err =NULL ; +CallbackFn *cb; +void *opaque; +BdrvDirtyBitmap *bmap =NULL ;assert (common->action->kind ==TRANSACTION_ACTION_KIND_DRIVE_BACKUP );backup = common->action->drive_backup;blockdev.c +1
-0@@ -1777,6 +1773,19 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) return; } +/* BackupBlockJob is opaque to us, so look up the bitmap ourselves */
blockdev.c +4
-0+if (backup->has_bitmap) { + bmap =bdrv_find_dirty_bitmap (bs, backup->bitmap); +if (!bmap) { + error_setg(errp, "Bitmap '%s' could not be found",
backup->bitmap);blockdev.c +9
-1+return ; + } + } + +/* Create our transactional callback wrapper, +and register that we'd like to call .cb() later. */ + cb =new_transaction_wrapper (common, bs, block_job_cb, &opaque); +/* AioContext is released in .clean() */ state->aio_context =bdrv_get_aio_context (bs);aio_context_acquire (state->aio_context);@@ -1789,7 +1798,7 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) backup->has_bitmap, backup->bitmap, backup->has_on_source_error, backup->on_source_error, backup->has_on_target_error, backup->on_target_error, -NULL ,NULL , + cb, opaque, &local_err);if (local_err) {error_propagate(errp, local_err);blockdev.c +7
-0@@ -1798,6 +1807,11 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) state->bs = bs; state->job = state->bs->job; +/* Keep the job alive until .cb(), too. */ +block_job_incref (state->job); +if (bmap) { +bdrv_dirty_bitmap_incref (bmap); + } }static void drive_backup_abort (BlkTransactionState *common)@@ -1809,6 +1823,10 @@ static void drive_backup_abort(BlkTransactionState *common)if (bs && bs->job && bs->job == state->job) {block_job_cancel_sync (bs->job); } + +/* Undo any callback actions we may have done. Putting down references
blockdev.c +17
-0+* obtained during prepare() is handled by cb(). */ +undo_transaction_wrapper (common); }static void drive_backup_clean (BlkTransactionState *common)@@ -1820,6 +1838,27 @@ static void drive_backup_clean(BlkTransactionState *common) } } +static void drive_backup_cb (BlkTransactionState *common) +{ +BlkTransactionData *btd = common->opaque; +BlockDriverState *bs = btd->opaque; +DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common); + +assert (state->bs == bs); +if (bs->job) { +assert (state->job == bs->job); + } + + state->aio_context =bdrv_get_aio_context (bs); +aio_context_acquire (state->aio_context); + +/* Note: We also have the individual job's return code in
btd->ret*/blockdev.c +8
-1+backup_transaction_complete (state->job, common->list->status); +block_job_decref (state->job); + +aio_context_release (state->aio_context); +} + typedef struct BlockdevBackupState { BlkTransactionState common; BlockDriverState *bs;@@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean + .clean = drive_backup_clean, + .cb = drive_backup_cb }, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),include/block/block_int.h +0
-0include/block/block_int.h void backup_start(BlockDriverState *bs, · line 624
Quoted 6 lines
include/block/block_int.h +3
-0@@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, BlockCompletionFunc *cb, void *opaque, Error **errp); +/* +* backup_transaction_complete +* @job The BackupJob that was associated with a transaction s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no structure named "BackupJob", but this looks like there might be one)
include/block/block_int.h void backup_start(BlockDriverState *bs, BlockDriverState *target, · line 625
Quoted 1 line
include/block/block_int.h +1
-0+* @ret Amalgamated return code for the entire transaction Hm. The call to this function you're introducing in this patch will probably stay the only one so there won't be anyone who'll have to worry about what this means, but if there was, they probably won't reach a conclusive result.
I know what it means because I've seen patch 3 (right now it means "everything OR-ed together so it's 0 for success or some non-zero (maybe positive, maybe negative, depending on whether you have an even or an odd number of errors, and depending on whether the jobs return negative values for errors or not) for error"), but I wouldn't be able to infer it from this. At the least you should add that 0 means success and everything else means error (if you take my suggestion for patch 3, it would be 0 for success and -errno for error, where that error number is one of the errors encountered).
Quoted 5 lines
Other than that, looks good (as far as I can tell with my still limited insights into patch 3).
Max
It's my opinion that this patch should give you insight into patch #3, instead of the other way around. This patch is useful for demonstrating the general flow, because I kept all drive_backup specific concerns strictly separated from patch #3.
I'll write a more comprehensive document for the docs/ folder soon, but the general shape of the idea is "The cleanup actions defined in the .cb() method will be executed after every job in the transactional group has completed."
But there's some fine print:
"'every job' refers only to jobs that have the capability to register a post-transaction callback, which currently means only drive_backup."
The general approach to this is, mechanically:
(1) Extend the life of objects of interest with reference counts, including Jobs, Bitmaps, and BlkTransactionStates.
(2) "steal" the callback from a BlockJob and, when invoked, update our management information for the transaction group in BlkTransactionList.
(3) Once all jobs we have sent out return, execute the .cb() methods as indicated in the BlkTransactionList.
So, if you were adding a callback to a different QMP transaction:
- Look at new_transaction_wrapper; you'll use this to bump up various references used internally for this whole system, and it'll keep qmp_transaction from being able to delete the list and state objects.
This function will give to you (as a gimmick) a new callback and
opaque data structure pointer for you to give to the job that you are starting. I obfuscate this just a bit to make it clear that you should be using this function every time to help manage the transactional state.
- Now, the job will run on its own happy way, When it finishes, it will call transaction_callback, which is the function that "intercepts" the callbacks, and dispatches the original enveloped callbacks. It ditches the original data we used to know how to call the original callback, and begins storing completion information for jobs instead.
- transaction_callback will call blk_transaction_complete to store completion info for one job. In turn, it calls put_blk_transaction_list to decrement the pending jobs count (the "jobs" refcount) and once that hits zero ...
- All of the callback mechanisms associated with each BlockTransactionState are run via blk_run_transaction_callbacks.
- This is where drive_backup_cb is going to get invoked again, and then we will splinter back out into backup-specific concerns, with Jobs and Bitmaps and what not.
From the point of view of the "transaction action developer," the interface to the new feature is found via the "new_transaction_wrapper" function, But some care must be taken to make sure this callback actually gets used once it has been requested. The whole thing can be aborted with the "undo_transaction_wrapper" function.
All of the other functions that got added to blockdev.c in Patch #3 are there to assist these two "interface" functions in doing what they claim to.
Everything in patch 4 and 5 just adds more reference count flexibility to things that are only of interest to drive_backup.
Patch 6 allows us to inject our special callback wrapper into the qmp_drive_backup function.
Clear as mud?
--js
Quoted 9 lines
Quoted 7 lines
include/block/block_int.h +4
-0+*/ +void backup_transaction_complete (BlockJob *job,int ret); + +void blk_dev_change_media_cb (BlockBackend *blk,bool load);bool blk_dev_has_removable_media (BlockBackend *blk);void blk_dev_eject_request (BlockBackend *blk,bool force);
Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
Quoted 241 lines · John Snow
Quoted 238 lines · Max Reitz
Quoted 210 lines · John Snow
This patch actually implements the transactional callback system for the
drive_backuptransaction.(1) We manually pick up a reference to the bitmap if present to allow its cleanup to be delayed until after all
drive_backupjobs
launched
by the transaction have fully completed.(2) We create a functional closure that envelops the original
drive_backup
callback, to be able to intercept the completion status and
return code
for the job.(3) We add the
drive_backup_cbmethod for thedrive_backupaction, which unpacks the completion information and invokes the final cleanup.(4)
backup_transaction_completewill perform the final cleanup on the backup job.(5) In the case of transaction cancellation,
drive_backup_cbis still responsible for cleaning up the mess we may have already made.Signed-off-by John Snow <[email protected]>--- block/backup.c | 9 +++++++ blockdev.c | 64 ++++++++++++++++++++++++++++++++++++++--------- include/block/block_int.h | 8 ++++++ 3 files changed, 69 insertions(+), 12 deletions(-)block/backup.c +9
-0@@ -233,6 +233,15 @@ typedef struct {int ret; } BackupCompleteData; +void backup_transaction_complete (BlockJob *job,int ret) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); + +if (s->sync_bitmap) { +bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, ret,NULL ); + } +} +static void backup_complete (BlockJob *job,void *opaque) {BackupBlockJob *s =container_of (job, BackupBlockJob, common);blockdev.c +1
-9@@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque, int ret)typedef void (CallbackFn )(void *opaque,int ret); -/* Temporary. Removed in the next patch. */ -CallbackFn *new_transaction_wrapper (BlkTransactionState *common, -void *opaque, -void (*callback)(void *,int ), -void **new_opaque); - -void undo_transaction_wrapper (BlkTransactionState *common); -/** * Create a new transactional callback wrapper. * @@ -1389,7 +1381,7 @@ voidundo_transaction_wrapper(BlkTransactionState *common); * * @return The callback to be used instead of @callback. */ -CallbackFn *new_transaction_wrapper(BlkTransactionState *common, +static CallbackFn *new_transaction_wrapper(BlkTransactionState *common, void *opaque,CallbackFn *callback, void **new_opaque)
blockdev.c +1
-1@@ -1416,7 +1408,7 @@ CallbackFn *new_transaction_wrapper(BlkTransactionState *common, /** * Undo any actions performed by the above call. */ -void undo_transaction_wrapper(BlkTransactionState *common) +static void undo_transaction_wrapper(BlkTransactionState *common) { BlkTransactionList *btl = common->list;BlkTransactionState *bts;blockdev.c +4
-0@@ -1449,6 +1441,7 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void block_job_cb (void *opaque,int ret); static void _drive_backup(const char *device, const char *target, bool has_format, const char *format, enum MirrorSyncMode sync,@@ -1767,6 +1760,9 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)BlockDriverState *bs;DriveBackup *backup;Error *local_err =NULL ; +CallbackFn *cb; +void *opaque; +BdrvDirtyBitmap *bmap =NULL ;assert (common->action->kind ==TRANSACTION_ACTION_KIND_DRIVE_BACKUP );backup = common->action->drive_backup;blockdev.c +1
-0@@ -1777,6 +1773,19 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) return; } +/* BackupBlockJob is opaque to us, so look up the bitmap ourselves */
blockdev.c +4
-0+if (backup->has_bitmap) { + bmap =bdrv_find_dirty_bitmap (bs, backup->bitmap); +if (!bmap) { + error_setg(errp, "Bitmap '%s' could not be found",
backup->bitmap);blockdev.c +9
-1+return ; + } + } + +/* Create our transactional callback wrapper, +and register that we'd like to call .cb() later. */ + cb =new_transaction_wrapper (common, bs, block_job_cb, &opaque); +/* AioContext is released in .clean() */ state->aio_context =bdrv_get_aio_context (bs);aio_context_acquire (state->aio_context);@@ -1789,7 +1798,7 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) backup->has_bitmap, backup->bitmap, backup->has_on_source_error, backup->on_source_error, backup->has_on_target_error, backup->on_target_error, - NULL, NULL, + cb, opaque,&local_err); if (local_err) { error_propagate(errp, local_err);blockdev.c +7
-0@@ -1798,6 +1807,11 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) state->bs = bs; state->job = state->bs->job; +/* Keep the job alive until .cb(), too. */ +block_job_incref (state->job); +if (bmap) { +bdrv_dirty_bitmap_incref (bmap); + } }static void drive_backup_abort (BlkTransactionState *common)@@ -1809,6 +1823,10 @@ static void drive_backup_abort(BlkTransactionState *common)if (bs && bs->job && bs->job == state->job) {block_job_cancel_sync (bs->job); } + +/* Undo any callback actions we may have done. Putting down references
blockdev.c +17
-0+* obtained during prepare() is handled by cb(). */ +undo_transaction_wrapper (common); }static void drive_backup_clean (BlkTransactionState *common)@@ -1820,6 +1838,27 @@ static void drive_backup_clean(BlkTransactionState *common) } } +static void drive_backup_cb (BlkTransactionState *common) +{ +BlkTransactionData *btd = common->opaque; +BlockDriverState *bs = btd->opaque; +DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common); + +assert (state->bs == bs); +if (bs->job) { +assert (state->job == bs->job); + } + + state->aio_context =bdrv_get_aio_context (bs); +aio_context_acquire (state->aio_context); + +/* Note: We also have the individual job's return code in
btd->ret*/blockdev.c +8
-1+backup_transaction_complete (state->job, common->list->status); +block_job_decref (state->job); + +aio_context_release (state->aio_context); +} + typedef struct BlockdevBackupState { BlkTransactionState common; BlockDriverState *bs;@@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean + .clean = drive_backup_clean, + .cb = drive_backup_cb }, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),include/block/block_int.h +0
-0include/block/block_int.h void backup_start(BlockDriverState *bs, · line 624
Quoted 6 lines
include/block/block_int.h +3
-0@@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, BlockCompletionFunc *cb, void *opaque, Error **errp); +/* +* backup_transaction_complete +* @job The BackupJob that was associated with a transaction s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no structure named "BackupJob", but this looks like there might be one)
include/block/block_int.h void backup_start(BlockDriverState *bs, BlockDriverState *target, · line 625
Quoted 1 line
include/block/block_int.h +1
-0+* @ret Amalgamated return code for the entire transaction Hm. The call to this function you're introducing in this patch will probably stay the only one so there won't be anyone who'll have to worry about what this means, but if there was, they probably won't reach a conclusive result.
I know what it means because I've seen patch 3 (right now it means "everything OR-ed together so it's 0 for success or some non-zero (maybe positive, maybe negative, depending on whether you have an even or an odd number of errors, and depending on whether the jobs return negative values for errors or not) for error"), but I wouldn't be able to infer it from this. At the least you should add that 0 means success and everything else means error (if you take my suggestion for patch 3, it would be 0 for success and -errno for error, where that error number is one of the errors encountered).
Quoted 7 lines
Quoted 5 lines
Other than that, looks good (as far as I can tell with my still limited insights into patch 3).
Max
It's my opinion that this patch should give you insight into patch #3, instead of the other way around. This patch is useful for demonstrating the general flow, because I kept all
drive_backupspecific concerns strictly separated from patch #3.
It only truly helps me understand patch 3 under the assumption that this patch is correct. For reviewing, I cannot really take that assumption. ;-)
I mean, it does help me with the interfaces patch 3 offers, but it still doesn't help me with the objects introduced in patch 3, for instance, because those are all contained in patch 3 itself. And if I cannot verify patch 3 on its own, I cannot really verify that what this patch does is correct in regards to the interfaces offered there.
Quoted 1 line
I'll write a more comprehensive document for the docs/ folder soon, but the general shape of the idea is "The cleanup actions defined in the .cb() method will be executed after every job in the transactional group has completed."
Right, again, assuming your implementation is correct, but questioning that is part of the natural reviewing process. :-)
Quoted 24 lines
But there's some fine print:
"'every job' refers only to jobs that have the capability to register a post-transaction callback, which currently means only
drive_backup."The general approach to this is, mechanically:
(1) Extend the life of objects of interest with reference counts, including Jobs, Bitmaps, and BlkTransactionStates.
(2) "steal" the callback from a BlockJob and, when invoked, update our management information for the transaction group in BlkTransactionList.
(3) Once all jobs we have sent out return, execute the .cb() methods as indicated in the BlkTransactionList.
So, if you were adding a callback to a different QMP transaction:
- Look atnew_transaction_wrapper; you'll use this to bump up various
references used internally for this whole system, and it'll keepqmp_transactionfrom being able to delete the list and state objects.This function will give to you (as a gimmick) a new callback and
opaque data structure pointer for you to give to the job that you are starting. I obfuscate this just a bit to make it clear that you should be using this function every time to help manage the transactional state.- Now, the job will run on its own happy way, When it finishes, it
will calltransaction_callback, which is the function that "intercepts" the callbacks, and dispatches the original enveloped callbacks. It ditches the original data we used to know how to call the original callback, and begins storing completion information for jobs instead.
Quoted 8 lines
-
transaction_callbackwill callblk_transaction_completeto store
completion info for one job. In turn, it callsput_blk_transaction_listto decrement the pending jobs count (the "jobs" refcount) and once that hits zero ...- All of the callback mechanisms associated with each BlockTransactionState are run via
blk_run_transaction_callbacks.- This is where
drive_backup_cbis going to get invoked again, and
then we will splinter back out into backup-specific concerns, with Jobs and Bitmaps and what not.
Yes, I can see the control flow; my main problem lies in the three different structs introduced in patch 3. I'll tell you why I'm confused by them:
First, there is BlkTransactionList. Sounds to me like it should be a list of transactions. Judging from its members, it is rather a list of operations ("actions") for a single transaction, however. Apparently, there is a double-use for the word "transaction": I'd consider a transaction to be the thing started by the QMP command which is a group of atomic operations. However, that one structure is called BlkTransactionState, but it apparently this is only the state of a single operation in the transaction. Pre-existing, but doesn't make it better. You added comments for its list entry members, with the first one stating "All transactions in the current group" and the second being a subset of that. However, again, I consider these to be operations and not transactions. The group is the transaction.
So we have that adding to my confusion. Furthermore, I think we don't need to list entry members (entry and list_entry) in BlkTransactionState. I think it should be fine to drop the snap_bdrv_states list in qmp_transaction() and just add all BlkTransactionStates to the BlkTransactionList (or however you name it if you rename it) in the preparation while () loop there. I don't think we need to add them only in blk_transaction_complete() (and we should drop that from there if it's been done in qmp_transaction() already, of course). Then we can really just use that list generally as *the* list of operations in a transaction.
Second, there is BlkTransactionData. From the name I cannot judge at all how it is supposed to differ from BlkTransactionState, because, well, it contains some data about a transaction... Maybe, because I consider BlkTransactionState to be misnamed, BlkTransactionData actually contains data about the transaction and not about a single operation? But from looking at the members, I have no idea. It has "opaque" and "ret"... I don't know how that is vital data to the transaction itself (and I don't think it is, it is rather data for the completion callback, I guess?), it only looks like some sort of pattern I have seen for AIO callbacks and the like.
Third, there is BlkTransactionWrapper. I suppose it wraps a transaction...? Or maybe at least an operation, because apparently I can no longer be sure whether a transaction is a transaction or an operation... But there is no transaction or even a single operation in there. It's just a structure describing a callback. I don't know how that is a "transaction wrapper"...
So, I don't understand any of the names given to the objects, and while I might understand what they are used for by looking at them, I have a *really* hard time understanding the code that uses them, because if I don't stare at their definitions all the time, I will immediately forget what they contain and what they are used for because I don't understand the names at all (and there is nothing which explicitly tells me what they are used for either). The fact that all of them are starting with BlkTransaction makes mixing them up even easier.
Quoted 10 lines
From the point of view of the "transaction action developer," the interface to the new feature is found via the "
new_transaction_wrapper" function, But some care must be taken to make sure this callback actually gets used once it has been requested. The whole thing can be aborted with the "undo_transaction_wrapper" function.All of the other functions that got added to
blockdev.cin Patch #3 are there to assist these two "interface" functions in doing what they claim to.Everything in patch 4 and 5 just adds more reference count flexibility to things that are only of interest to
drive_backup.Patch 6 allows us to inject our special callback wrapper into the
qmp_drive_backupfunction.Clear as mud?
Control flow and concept, clear. Implementation, still very muddy, sorry...
Max
Quoted 11 lines
--js
Quoted 9 lines
Quoted 7 lines
include/block/block_int.h +4
-0+*/ +void backup_transaction_complete (BlockJob *job,int ret); + +void blk_dev_change_media_cb (BlockBackend *blk,bool load);bool blk_dev_has_removable_media (BlockBackend *blk);void blk_dev_eject_request (BlockBackend *blk,bool force);
Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
Quoted 263 lines · Max Reitz
Quoted 245 lines · John Snow
Quoted 242 lines · Max Reitz
Quoted 211 lines · John Snow
This patch actually implements the transactional callback system for the
drive_backuptransaction.(1) We manually pick up a reference to the bitmap if present to allow its cleanup to be delayed until after all
drive_backupjobs
launched
by the transaction have fully completed.(2) We create a functional closure that envelops the original
drive_backup
callback, to be able to intercept the completion status and
return code
for the job.(3) We add the
drive_backup_cbmethod for thedrive_backupaction, which
unpacks the completion information and invokes the final cleanup.(4)
backup_transaction_completewill perform the final cleanup on the backup job.(5) In the case of transaction cancellation,
drive_backup_cbis still responsible for cleaning up the mess we may have already made.Signed-off-by John Snow <[email protected]>--- block/backup.c | 9 +++++++ blockdev.c | 64 ++++++++++++++++++++++++++++++++++++++--------- include/block/block_int.h | 8 ++++++ 3 files changed, 69 insertions(+), 12 deletions(-)block/backup.c +9
-0@@ -233,6 +233,15 @@ typedef struct {int ret; } BackupCompleteData; +void backup_transaction_complete (BlockJob *job,int ret) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); + +if (s->sync_bitmap) { +bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, ret,NULL ); + } +} +static void backup_complete (BlockJob *job,void *opaque) {BackupBlockJob *s =container_of (job, BackupBlockJob, common);blockdev.c +1
-9@@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque, int ret)typedef void (CallbackFn )(void *opaque,int ret); -/* Temporary. Removed in the next patch. */ -CallbackFn *new_transaction_wrapper (BlkTransactionState *common, -void *opaque, -void (*callback)(void *,int ), -void **new_opaque); - -void undo_transaction_wrapper (BlkTransactionState *common); -/** * Create a new transactional callback wrapper. * @@ -1389,7 +1381,7 @@ voidundo_transaction_wrapper(BlkTransactionState *common); * * @return The callback to be used instead of @callback. */ -CallbackFn *new_transaction_wrapper(BlkTransactionState *common, +static CallbackFn *new_transaction_wrapper(BlkTransactionState *common, void *opaque,CallbackFn *callback, void **new_opaque)
blockdev.c +1
-1@@ -1416,7 +1408,7 @@ CallbackFn *new_transaction_wrapper(BlkTransactionState *common, /** * Undo any actions performed by the above call. */ -void undo_transaction_wrapper(BlkTransactionState *common) +static void undo_transaction_wrapper(BlkTransactionState *common) { BlkTransactionList *btl = common->list;BlkTransactionState *bts;blockdev.c +4
-0@@ -1449,6 +1441,7 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void block_job_cb (void *opaque,int ret); static void _drive_backup(const char *device, const char *target, bool has_format, const char *format, enum MirrorSyncMode sync,@@ -1767,6 +1760,9 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)BlockDriverState *bs;DriveBackup *backup;Error *local_err =NULL ; +CallbackFn *cb; +void *opaque; +BdrvDirtyBitmap *bmap =NULL ;assert (common->action->kind ==TRANSACTION_ACTION_KIND_DRIVE_BACKUP );backup = common->action->drive_backup;blockdev.c +1
-0@@ -1777,6 +1773,19 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) return; } +/* BackupBlockJob is opaque to us, so look up the bitmap ourselves */
blockdev.c +4
-0+if (backup->has_bitmap) { + bmap =bdrv_find_dirty_bitmap (bs, backup->bitmap); +if (!bmap) { + error_setg(errp, "Bitmap '%s' could not be found",
backup->bitmap);blockdev.c +9
-1+return ; + } + } + +/* Create our transactional callback wrapper, +and register that we'd like to call .cb() later. */ + cb =new_transaction_wrapper (common, bs, block_job_cb, &opaque); +/* AioContext is released in .clean() */ state->aio_context =bdrv_get_aio_context (bs);aio_context_acquire (state->aio_context);@@ -1789,7 +1798,7 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) backup->has_bitmap, backup->bitmap, backup->has_on_source_error, backup->on_source_error, backup->has_on_target_error, backup->on_target_error, - NULL, NULL, + cb, opaque,&local_err); if (local_err) { error_propagate(errp, local_err);blockdev.c +7
-0@@ -1798,6 +1807,11 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) state->bs = bs; state->job = state->bs->job; +/* Keep the job alive until .cb(), too. */ +block_job_incref (state->job); +if (bmap) { +bdrv_dirty_bitmap_incref (bmap); + } }static void drive_backup_abort (BlkTransactionState *common)@@ -1809,6 +1823,10 @@ static void drive_backup_abort(BlkTransactionState *common)if (bs && bs->job && bs->job == state->job) {block_job_cancel_sync (bs->job); } + +/* Undo any callback actions we may have done. Putting down references
blockdev.c +17
-0+* obtained during prepare() is handled by cb(). */ +undo_transaction_wrapper (common); }static void drive_backup_clean (BlkTransactionState *common)@@ -1820,6 +1838,27 @@ static void drive_backup_clean(BlkTransactionState *common) } } +static void drive_backup_cb (BlkTransactionState *common) +{ +BlkTransactionData *btd = common->opaque; +BlockDriverState *bs = btd->opaque; +DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common); + +assert (state->bs == bs); +if (bs->job) { +assert (state->job == bs->job); + } + + state->aio_context =bdrv_get_aio_context (bs); +aio_context_acquire (state->aio_context); + +/* Note: We also have the individual job's return code in
btd->ret*/blockdev.c +8
-1+backup_transaction_complete (state->job, common->list->status); +block_job_decref (state->job); + +aio_context_release (state->aio_context); +} + typedef struct BlockdevBackupState { BlkTransactionState common; BlockDriverState *bs;@@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean + .clean = drive_backup_clean, + .cb = drive_backup_cb }, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),include/block/block_int.h +0
-0include/block/block_int.h void backup_start(BlockDriverState *bs, · line 624
Quoted 6 lines
include/block/block_int.h +3
-0@@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, BlockCompletionFunc *cb, void *opaque, Error **errp); +/* +* backup_transaction_complete +* @job The BackupJob that was associated with a transaction s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no structure named "BackupJob", but this looks like there might be one)
include/block/block_int.h void backup_start(BlockDriverState *bs, BlockDriverState *target, · line 625
Quoted 1 line
include/block/block_int.h +1
-0+* @ret Amalgamated return code for the entire transaction Hm. The call to this function you're introducing in this patch will probably stay the only one so there won't be anyone who'll have to worry about what this means, but if there was, they probably won't reach a conclusive result.
I know what it means because I've seen patch 3 (right now it means "everything OR-ed together so it's 0 for success or some non-zero (maybe positive, maybe negative, depending on whether you have an even or an odd number of errors, and depending on whether the jobs return negative values for errors or not) for error"), but I wouldn't be able to infer it from this. At the least you should add that 0 means success and everything else means error (if you take my suggestion for patch 3, it would be 0 for success and -errno for error, where that error number is one of the errors encountered).
Other than that, looks good (as far as I can tell with my still limited insights into patch 3).
Quoted 7 lines
Quoted 2 lines
Max
It's my opinion that this patch should give you insight into patch #3, instead of the other way around. This patch is useful for demonstrating the general flow, because I kept all
drive_backupspecific concerns strictly separated from patch #3.It only truly helps me understand patch 3 under the assumption that this patch is correct. For reviewing, I cannot really take that assumption. ;-)
I mean, it does help me with the interfaces patch 3 offers, but it still doesn't help me with the objects introduced in patch 3, for instance, because those are all contained in patch 3 itself. And if I cannot verify patch 3 on its own, I cannot really verify that what this patch does is correct in regards to the interfaces offered there.
Quoted 8 lines
Quoted 4 lines
I'll write a more comprehensive document for the docs/ folder soon, but the general shape of the idea is "The cleanup actions defined in the .cb() method will be executed after every job in the transactional group has completed."
Right, again, assuming your implementation is correct, but questioning that is part of the natural reviewing process. :-)
Yes, that's fine! I am just trying to help you unravel this strange loop (ha ha) and/or try to understand what is not necessarily clear with the information as presented.
Quoted 43 lines
Quoted 43 lines
But there's some fine print:
"'every job' refers only to jobs that have the capability to register a post-transaction callback, which currently means only
drive_backup."The general approach to this is, mechanically:
(1) Extend the life of objects of interest with reference counts, including Jobs, Bitmaps, and BlkTransactionStates.
(2) "steal" the callback from a BlockJob and, when invoked, update our management information for the transaction group in BlkTransactionList.
(3) Once all jobs we have sent out return, execute the .cb() methods as indicated in the BlkTransactionList.
So, if you were adding a callback to a different QMP transaction:
- Look atnew_transaction_wrapper; you'll use this to bump up various references used internally for this whole system, and it'll keepqmp_transactionfrom being able to delete the list and state objects.This function will give to you (as a gimmick) a new callback and
opaque data structure pointer for you to give to the job that you are starting. I obfuscate this just a bit to make it clear that you should be using this function every time to help manage the transactional state.- Now, the job will run on its own happy way, When it finishes, it will call
transaction_callback, which is the function that "intercepts" the callbacks, and dispatches the original enveloped callbacks. It ditches the original data we used to know how to call the original callback, and begins storing completion information for jobs instead.-
transaction_callbackwill callblk_transaction_completeto store completion info for one job. In turn, it callsput_blk_transaction_listto decrement the pending jobs count (the "jobs" refcount) and once that hits zero ...- All of the callback mechanisms associated with each BlockTransactionState are run via
blk_run_transaction_callbacks.
Quoted 8 lines
Quoted 3 lines
- This is where
drive_backup_cbis going to get invoked again, and then we will splinter back out into backup-specific concerns, with Jobs and Bitmaps and what not.Yes, I can see the control flow; my main problem lies in the three different structs introduced in patch 3. I'll tell you why I'm confused by them:
Paydirt!
Quoted 4 lines
First, there is BlkTransactionList. Sounds to me like it should be a list of transactions. Judging from its members, it is rather a list of operations ("actions") for a single transaction, however. Apparently, there is a double-use for the word "transaction": I'd consider a
Quoted 9 lines
transaction to be the thing started by the QMP command which is a group of atomic operations. However, that one structure is called BlkTransactionState, but it apparently this is only the state of a single operation in the transaction. Pre-existing, but doesn't make it better. You added comments for its list entry members, with the first one stating "All transactions in the current group" and the second being a subset of that. However, again, I consider these to be operations and not transactions. The group is the transaction.
Ah, yes. Good points. We need to decide on consistent terminology.
Transaction: The entire group.
Action: One particular command within a transaction group. Action Op: One particular stage of one command (e.g. prepare, commit)
BlkTransactionState is an existing structure that does indeed refer instead to one particular action's state, not that of the entire transaction. This can be changed to be clearer. Better names might be BlkTransactionActionState or BlkActionState.
The BlkTransactionList here is indeed intended to be a list for the entire transaction, so it is actually a "list of actions" and not, perhaps as its name implies, a list of transactions. (We have no data structures with a scope this vast.)
The BlkTransactionList is an object that is created once per transaction group (once per qmp_transaction()) and keeps a count of how many jobs we've launched (and thus how many to expect to return to us.)
Once a job completes, it adds that *action's state* (BlkTransactionState) to its list. Once all jobs sent out have returned, it will iterate this list and execute their .cb() actions.
So perhaps an amendment would be:
typedef struct BlkTransactionList {
int pending_jobs;
int transaction_rc;
QLIST(...) completed_actions;
} BlkTransactionList;to make this part clearer.
Quoted 11 lines
So we have that adding to my confusion. Furthermore, I think we don't need to list entry members (entry and
list_entry) in BlkTransactionState. I think it should be fine to drop thesnap_bdrv_stateslist inqmp_transaction()and just add all BlkTransactionStates to the BlkTransactionList (or however you name it if you rename it) in the preparation while () loop there. I don't think we need to add them only inblk_transaction_complete()(and we should drop that from there if it's been done inqmp_transaction()already, of course). Then we can really just use that list generally as *the* list of operations in a transaction.
Yes, there's not necessarily a strong reason to keep two lists.
One of the existing reasons, though, is that not all transactions launch jobs or have been modified to work with this system yet, so we definitely don't want to consider all of these entries in THIS system just yet.
So one of the simplifications two lists makes is that we don't have to worry about "unsupported actions" making it into the list. Everything in *my* list can be processed without further thought.
We can also just add more state into the BlkTransactionState (the ActionState) to help us distinguish, and then we can get rid of both lists.
A state machine tag or something might be useful in that way.
Quoted 11 lines
Second, there is BlkTransactionData. From the name I cannot judge at all how it is supposed to differ from BlkTransactionState, because, well, it contains some data about a transaction... Maybe, because I consider BlkTransactionState to be misnamed, BlkTransactionData actually contains data about the transaction and not about a single operation? But from looking at the members, I have no idea. It has "opaque" and "ret"... I don't know how that is vital data to the transaction itself (and I don't think it is, it is rather data for the completion callback, I guess?), it only looks like some sort of pattern I have seen for AIO callbacks and the like.
BlkTransactionData was meant to contain completion info about a "job" that was launched by an action. I tried to avoid tying it explicitly to a BlockJob -- it can be any action with a callback, really.
Anyway, this structure contains "Data that was delivered to the original callback." In our case here, it's data that would've gone to block_job_complete.
So it contains an opaque pointer (A BDS, we secretly know) and a return code for that individual action's task.
Quoted 7 lines
Third, there is BlkTransactionWrapper. I suppose it wraps a transaction...? Or maybe at least an operation, because apparently I can no longer be sure whether a transaction is a transaction or an operation... But there is no transaction or even a single operation in there. It's just a structure describing a callback. I don't know how that is a "transaction wrapper"...
We have achieved maximum Strawberry.
The BlkTransactionWrapper was meant to wrap any data necessary to be able to deliver the original callback we're stealing.
So in our case, it's for *one action's callback* and in this one instance, it's being used to hold the data needed to manually invoke the block_job_complete callback.
So both BTD and BTW here contain callback related information for one particular action.
They are differentiated by "Wrapper" having a "Before we execute the original callback" context, and "Data" having a "We've already executed the original callback" context.
Can they be combined into one structure? Yes, as long as we keep a tag that lets us know what state we're in.
Can they be inlined into "BlkTransactionState"? Yes -- but I opted to keep it in a separate glob to not clutter up the BlkTransactionState with values only useful for an optional feature.
Is that wise? Maybe, maybe not.
I'm open to names. The most semantically accurate would be BlkTransactionActionCallbackData if we combined BTD/BTW to be one structure. It's a mouthful, though.
Quoted 9 lines
So, I don't understand any of the names given to the objects, and while I might understand what they are used for by looking at them, I have a *really* hard time understanding the code that uses them, because if I don't stare at their definitions all the time, I will immediately forget what they contain and what they are used for because I don't understand the names at all (and there is nothing which explicitly tells me what they are used for either). The fact that all of them are starting with BlkTransaction makes mixing them up even easier.
Understood.
Quoted 36 lines
Quoted 11 lines
From the point of view of the "transaction action developer," the interface to the new feature is found via the "
new_transaction_wrapper" function, But some care must be taken to make sure this callback actually gets used once it has been requested. The whole thing can be aborted with the "undo_transaction_wrapper" function.All of the other functions that got added to
blockdev.cin Patch #3 are there to assist these two "interface" functions in doing what they claim to.Quoted 8 lines
Everything in patch 4 and 5 just adds more reference count flexibility to things that are only of interest to
drive_backup.Patch 6 allows us to inject our special callback wrapper into the
qmp_drive_backupfunction.Clear as mud?
Control flow and concept, clear. Implementation, still very muddy, sorry...
Max
Quoted 11 lines
--js
Quoted 9 lines
Quoted 7 lines
include/block/block_int.h +4
-0+*/ +void backup_transaction_complete (BlockJob *job,int ret); + +void blk_dev_change_media_cb (BlockBackend *blk,bool load);bool blk_dev_has_removable_media (BlockBackend *blk);void blk_dev_eject_request (BlockBackend *blk,bool force);
Signature · 1 line
—js
Re: [Qemu-devel] [PATCH 07/11] block: drive_backup transaction callback support
Quoted 347 lines · John Snow
Quoted 265 lines · Max Reitz
Quoted 247 lines · John Snow
Quoted 244 lines · Max Reitz
Quoted 213 lines · John Snow
This patch actually implements the transactional callback system for the
drive_backuptransaction.(1) We manually pick up a reference to the bitmap if present to allow its cleanup to be delayed until after all
drive_backupjobs
launched
by the transaction have fully completed.(2) We create a functional closure that envelops the original
drive_backup
callback, to be able to intercept the completion status and
return code
for the job.(3) We add the
drive_backup_cbmethod for thedrive_backupaction, which
unpacks the completion information and invokes the final
cleanup.(4)
backup_transaction_completewill perform the final cleanup on the backup job.(5) In the case of transaction cancellation,
drive_backup_cbis still responsible for cleaning up the mess we may have already made.Signed-off-by John Snow <[email protected]>--- block/backup.c | 9 +++++++ blockdev.c | 64 ++++++++++++++++++++++++++++++++++++++--------- include/block/block_int.h | 8 ++++++ 3 files changed, 69 insertions(+), 12 deletions(-)block/backup.c +9
-0@@ -233,6 +233,15 @@ typedef struct {int ret; } BackupCompleteData; +void backup_transaction_complete (BlockJob *job,int ret) +{ +BackupBlockJob *s =container_of (job, BackupBlockJob, common); + +if (s->sync_bitmap) { +bdrv_dirty_bitmap_decref (job->bs, s->sync_bitmap, ret,NULL ); + } +} +static void backup_complete (BlockJob *job,void *opaque) {BackupBlockJob *s =container_of (job, BackupBlockJob, common);blockdev.c +1
-9@@ -1363,14 +1363,6 @@ static void transaction_callback(void *opaque, int ret)typedef void (CallbackFn )(void *opaque,int ret); -/* Temporary. Removed in the next patch. */ -CallbackFn *new_transaction_wrapper (BlkTransactionState *common, -void *opaque, -void (*callback)(void *,int ), -void **new_opaque); - -void undo_transaction_wrapper (BlkTransactionState *common); -/** * Create a new transactional callback wrapper. * @@ -1389,7 +1381,7 @@ voidundo_transaction_wrapper(BlkTransactionState *common); * * @return The callback to be used instead of @callback. */ -CallbackFn *new_transaction_wrapper(BlkTransactionState *common, +static CallbackFn *new_transaction_wrapper(BlkTransactionState *common, void *opaque,CallbackFn *callback, void **new_opaque)
blockdev.c +1
-1@@ -1416,7 +1408,7 @@ CallbackFn *new_transaction_wrapper(BlkTransactionState *common, /** * Undo any actions performed by the above call. */ -void undo_transaction_wrapper(BlkTransactionState *common) +static void undo_transaction_wrapper(BlkTransactionState *common) { BlkTransactionList *btl = common->list;BlkTransactionState *bts;blockdev.c +4
-0@@ -1449,6 +1441,7 @@ void undo_transaction_wrapper(BlkTransactionState *common)blk_put_transaction_state (common); } +static void block_job_cb (void *opaque,int ret); static void _drive_backup(const char *device, const char *target, bool has_format, const char *format, enum MirrorSyncMode sync,@@ -1767,6 +1760,9 @@ static void drive_backup_prepare(BlkTransactionState *common, Error **errp)BlockDriverState *bs;DriveBackup *backup;Error *local_err =NULL ; +CallbackFn *cb; +void *opaque; +BdrvDirtyBitmap *bmap =NULL ;assert (common->action->kind ==TRANSACTION_ACTION_KIND_DRIVE_BACKUP );backup = common->action->drive_backup;blockdev.c +1
-0@@ -1777,6 +1773,19 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) return; } +/* BackupBlockJob is opaque to us, so look up the bitmap ourselves */
blockdev.c +4
-0+if (backup->has_bitmap) { + bmap =bdrv_find_dirty_bitmap (bs, backup->bitmap); +if (!bmap) { + error_setg(errp, "Bitmap '%s' could not be found",
backup->bitmap);blockdev.c +9
-1+return ; + } + } + +/* Create our transactional callback wrapper, +and register that we'd like to call .cb() later. */ + cb =new_transaction_wrapper (common, bs, block_job_cb, &opaque); +/* AioContext is released in .clean() */ state->aio_context =bdrv_get_aio_context (bs);aio_context_acquire (state->aio_context);@@ -1789,7 +1798,7 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) backup->has_bitmap, backup->bitmap, backup->has_on_source_error, backup->on_source_error, backup->has_on_target_error, backup->on_target_error, - NULL, NULL, + cb, opaque,&local_err); if (local_err) { error_propagate(errp, local_err);blockdev.c +7
-0@@ -1798,6 +1807,11 @@ static voiddrive_backup_prepare (BlkTransactionState *common, Error **errp) state->bs = bs; state->job = state->bs->job; +/* Keep the job alive until .cb(), too. */ +block_job_incref (state->job); +if (bmap) { +bdrv_dirty_bitmap_incref (bmap); + } }static void drive_backup_abort (BlkTransactionState *common)@@ -1809,6 +1823,10 @@ static void drive_backup_abort(BlkTransactionState *common)if (bs && bs->job && bs->job == state->job) {block_job_cancel_sync (bs->job); } + +/* Undo any callback actions we may have done. Putting down references
blockdev.c +17
-0+* obtained during prepare() is handled by cb(). */ +undo_transaction_wrapper (common); }static void drive_backup_clean (BlkTransactionState *common)@@ -1820,6 +1838,27 @@ static void drive_backup_clean(BlkTransactionState *common) } } +static void drive_backup_cb (BlkTransactionState *common) +{ +BlkTransactionData *btd = common->opaque; +BlockDriverState *bs = btd->opaque; +DriveBackupState *state =DO_UPCAST (DriveBackupState, common, common); + +assert (state->bs == bs); +if (bs->job) { +assert (state->job == bs->job); + } + + state->aio_context =bdrv_get_aio_context (bs); +aio_context_acquire (state->aio_context); + +/* Note: We also have the individual job's return code in
btd->ret*/blockdev.c +8
-1+backup_transaction_complete (state->job, common->list->status); +block_job_decref (state->job); + +aio_context_release (state->aio_context); +} + typedef struct BlockdevBackupState { BlkTransactionState common; BlockDriverState *bs;@@ -2004,7 +2043,8 @@ static const BdrvActionOps actions[] = { .instance_size = sizeof(DriveBackupState), .prepare = drive_backup_prepare, .abort = drive_backup_abort, - .clean = drive_backup_clean + .clean = drive_backup_clean, + .cb = drive_backup_cb }, [TRANSACTION_ACTION_KIND_BLOCKDEV_BACKUP] = { .instance_size = sizeof(BlockdevBackupState),include/block/block_int.h +0
-0include/block/block_int.h void backup_start(BlockDriverState *bs, · line 624
Quoted 6 lines
include/block/block_int.h +3
-0@@ -619,6 +619,14 @@ void backup_start(BlockDriverState *bs, BlockDriverState *target, BlockCompletionFunc *cb, void *opaque, Error **errp); +/* +* backup_transaction_complete +* @job The BackupJob that was associated with a transaction s/BackupJob/backup block job/ or s/BackupJob/backup job/? (there is no structure named "BackupJob", but this looks like there might be one)
include/block/block_int.h void backup_start(BlockDriverState *bs, BlockDriverState *target, · line 625
Quoted 1 line
include/block/block_int.h +1
-0+* @ret Amalgamated return code for the entire transaction Hm. The call to this function you're introducing in this patch will probably stay the only one so there won't be anyone who'll have to worry about what this means, but if there was, they probably won't reach a conclusive result.
I know what it means because I've seen patch 3 (right now it means "everything OR-ed together so it's 0 for success or some non-zero (maybe positive, maybe negative, depending on whether you have an even or an odd number of errors, and depending on whether the jobs return negative values for errors or not) for error"), but I wouldn't be able to infer it from this. At the least you should add that 0 means success and everything else means error (if you take my suggestion for patch 3, it would be 0 for success and -errno for error, where that error number is one of the errors encountered).
Other than that, looks good (as far as I can tell with my still limited insights into patch 3).
Quoted 7 lines
Quoted 2 lines
Max
It's my opinion that this patch should give you insight into patch #3, instead of the other way around. This patch is useful for demonstrating the general flow, because I kept all
drive_backupspecific concerns strictly separated from patch #3.It only truly helps me understand patch 3 under the assumption that this patch is correct. For reviewing, I cannot really take that assumption. ;-)
I mean, it does help me with the interfaces patch 3 offers, but it still doesn't help me with the objects introduced in patch 3, for instance, because those are all contained in patch 3 itself. And if I cannot verify patch 3 on its own, I cannot really verify that what this patch does is correct in regards to the interfaces offered there.
Quoted 8 lines
Quoted 4 lines
I'll write a more comprehensive document for the docs/ folder soon, but the general shape of the idea is "The cleanup actions defined in the .cb() method will be executed after every job in the transactional group has completed."
Right, again, assuming your implementation is correct, but questioning that is part of the natural reviewing process. :-)
Yes, that's fine! I am just trying to help you unravel this strange loop (ha ha) and/or try to understand what is not necessarily clear with the information as presented.
Quoted 43 lines
Quoted 43 lines
But there's some fine print:
"'every job' refers only to jobs that have the capability to register a post-transaction callback, which currently means only
drive_backup."The general approach to this is, mechanically:
(1) Extend the life of objects of interest with reference counts, including Jobs, Bitmaps, and BlkTransactionStates.
(2) "steal" the callback from a BlockJob and, when invoked, update our management information for the transaction group in BlkTransactionList.
(3) Once all jobs we have sent out return, execute the .cb() methods as indicated in the BlkTransactionList.
So, if you were adding a callback to a different QMP transaction:
- Look atnew_transaction_wrapper; you'll use this to bump up various references used internally for this whole system, and it'll keepqmp_transactionfrom being able to delete the list and state objects.This function will give to you (as a gimmick) a new callback and
opaque data structure pointer for you to give to the job that you are starting. I obfuscate this just a bit to make it clear that you should be using this function every time to help manage the transactional state.- Now, the job will run on its own happy way, When it finishes, it will call
transaction_callback, which is the function that "intercepts" the callbacks, and dispatches the original enveloped callbacks. It ditches the original data we used to know how to call the original callback, and begins storing completion information for jobs instead.-
transaction_callbackwill callblk_transaction_completeto store completion info for one job. In turn, it callsput_blk_transaction_listto decrement the pending jobs count (the "jobs" refcount) and once that hits zero ...- All of the callback mechanisms associated with each BlockTransactionState are run via
blk_run_transaction_callbacks.Quoted 8 lines
Quoted 3 lines
- This is where
drive_backup_cbis going to get invoked again, and then we will splinter back out into backup-specific concerns, with Jobs and Bitmaps and what not.Yes, I can see the control flow; my main problem lies in the three different structs introduced in patch 3. I'll tell you why I'm confused by them:
Paydirt!
Quoted 4 lines
First, there is BlkTransactionList. Sounds to me like it should be a list of transactions. Judging from its members, it is rather a list of operations ("actions") for a single transaction, however. Apparently, there is a double-use for the word "transaction": I'd consider a
Quoted 9 lines
transaction to be the thing started by the QMP command which is a group of atomic operations. However, that one structure is called BlkTransactionState, but it apparently this is only the state of a single operation in the transaction. Pre-existing, but doesn't make it better. You added comments for its list entry members, with the first one stating "All transactions in the current group" and the second being a subset of that. However, again, I consider these to be operations and not transactions. The group is the transaction.
Quoted 5 lines
Ah, yes. Good points. We need to decide on consistent terminology.
Transaction: The entire group.
Action: One particular command within a transaction group. Action Op: One particular stage of one command (e.g. prepare, commit)
Sounds good.
Quoted 31 lines
BlkTransactionState is an existing structure that does indeed refer instead to one particular action's state, not that of the entire transaction. This can be changed to be clearer. Better names might be BlkTransactionActionState or BlkActionState.
The BlkTransactionList here is indeed intended to be a list for the entire transaction, so it is actually a "list of actions" and not, perhaps as its name implies, a list of transactions. (We have no data structures with a scope this vast.)
The BlkTransactionList is an object that is created once per transaction group (once per
qmp_transaction()) and keeps a count of how many jobs we've launched (and thus how many to expect to return to us.)Once a job completes, it adds that *action's state* (BlkTransactionState) to its list. Once all jobs sent out have returned, it will iterate this list and execute their .cb() actions.
So perhaps an amendment would be:
typedef struct BlkTransactionList { int pending_jobs; int transaction_rc; QLIST(...) completed_actions; } BlkTransactionList;to make this part clearer.
Quoted 11 lines
So we have that adding to my confusion. Furthermore, I think we don't need to list entry members (entry and
list_entry) in BlkTransactionState. I think it should be fine to drop thesnap_bdrv_stateslist inqmp_transaction()and just add all BlkTransactionStates to the BlkTransactionList (or however you name it if you rename it) in the preparation while () loop there. I don't think we need to add them only inblk_transaction_complete()(and we should drop that from there if it's been done inqmp_transaction()already, of course). Then we can really just use that list generally as *the* list of operations in a transaction.
Quoted 5 lines
Yes, there's not necessarily a strong reason to keep two lists.
One of the existing reasons, though, is that not all transactions launch jobs or have been modified to work with this system yet, so we definitely don't want to consider all of these entries in THIS system just yet.
So one of the simplifications two lists makes is that we don't have to worry about "unsupported actions" making it into the list. Everything in *my* list can be processed without further thought.
Hm, okay. I guess here I got confused because there are so many different callbacks... Okay. Makes sense.
Would it make sense to you to replace "Transactions in the current group with callbacks" by "Actions in the current transaction with (job) completion callbacks"? I think it might be helpful to specify exactly which callback this is about (because we do have a couple of callbacks here...).
Quoted 16 lines
We can also just add more state into the BlkTransactionState (the ActionState) to help us distinguish, and then we can get rid of both lists.
A state machine tag or something might be useful in that way.
Quoted 11 lines
Second, there is BlkTransactionData. From the name I cannot judge at all how it is supposed to differ from BlkTransactionState, because, well, it contains some data about a transaction... Maybe, because I consider BlkTransactionState to be misnamed, BlkTransactionData actually contains data about the transaction and not about a single operation? But from looking at the members, I have no idea. It has "opaque" and "ret"... I don't know how that is vital data to the transaction itself (and I don't think it is, it is rather data for the completion callback, I guess?), it only looks like some sort of pattern I have seen for AIO callbacks and the like.
Quoted 1 line
BlkTransactionData was meant to contain completion info about a "job" that was launched by an action. I tried to avoid tying it explicitly to a BlockJob -- it can be any action with a callback, really.
Hm. I'm in favor of just calling it "job". The problem is that the actions regarding block jobs only launch the block jobs; therefore, the action itself is completed by the time qmp_transaction() returns, but the block job (which has been launched by the action) is probably still running. Therefore, there is a difference between an action and a job.
It may help to really make a clear distinction there, like replace the comment "Execute this after +all+ jobs in the transaction finish" by "Executed after +all+ jobs launched by the transaction finish" (because the jobs aren't part of the transaction).
While it can be any action with a callback, it will be only block jobs for now, so once we add something else, we can think about the names once again. But I think that "job" is a generic enough description and will fit any long-term operation started by a transaction action which continues even beyond the transaction itself, so I think we should be fine even then.
Quoted 22 lines
Anyway, this structure contains "Data that was delivered to the original callback." In our case here, it's data that would've gone to
block_job_complete.So it contains an opaque pointer (A BDS, we secretly know) and a return code for that individual action's task.
Quoted 7 lines
Third, there is BlkTransactionWrapper. I suppose it wraps a transaction...? Or maybe at least an operation, because apparently I can no longer be sure whether a transaction is a transaction or an operation... But there is no transaction or even a single operation in there. It's just a structure describing a callback. I don't know how that is a "transaction wrapper"...
We have achieved maximum Strawberry.
The BlkTransactionWrapper was meant to wrap any data necessary to be able to deliver the original callback we're stealing.
So in our case, it's for *one action's callback* and in this one instance, it's being used to hold the data needed to manually invoke the
block_job_completecallback.So both BTD and BTW here contain callback related information for one particular action.
They are differentiated by "Wrapper" having a "Before we execute the original callback" context, and "Data" having a "We've already executed the original callback" context.
Quoted 7 lines
Can they be combined into one structure? Yes, as long as we keep a tag that lets us know what state we're in.
Can they be inlined into "BlkTransactionState"? Yes -- but I opted to keep it in a separate glob to not clutter up the BlkTransactionState with values only useful for an optional feature.
Is that wise? Maybe, maybe not.
I'm open to names. The most semantically accurate would be BlkTransactionActionCallbackData if we combined BTD/BTW to be one structure. It's a mouthful, though.
I was playing a bit dumb here: I did see that BlkTransaction{Data,Wrapper} described callbacks ("it is rather data for the completion callback, I guess?", "It's just a structure describing a callback"), but I mainly wanted to point out what I would infer from the names alone.
I'm all in favor of making the names overly long if need be. Maybe I'd call BTD BlkTransactionCallbackData, BlkTransactionCompletionCBData, BlkTransactionCBData, BlkTransactionCBParams, or just BlkTACBData/BlkTACBParams instead (it is a structure local to blockdev.c, so I'd be fine with abbreviations); BTW I'd call BlkTransactionCB/BlkTACB (because it contains the function pointer which actually is the callback itself).
Maybe merging both does make sense. The name you proposed looks good to me, but I'd abbreviate it (at least BlkTransactionActionCBData, or BlkTAActionCBData (which is ugly), or BlkTransactionOpCBData, or BlkTAOpCBData, or BlkTACBData (I think omitting the "Action" should be fine here, because the important thing is to have the "CB" in it to signify that this is not data describing the transaction but a callback), or even omit the "Data": BlkTransactionCB/BlkTACB. You are, after all, describing a complete callback with it (the function pointer and all the parameters)).
I don't know whether that helps. Naming is always hard, I know. Just make sure that data about a callback contains the word "callback" or "CB" and it should be fine, and add proper comments which describe why the structure is named the way it is. If you can explain it, it's probably a good name.
Max
Quoted 50 lines
Quoted 10 lines
So, I don't understand any of the names given to the objects, and while I might understand what they are used for by looking at them, I have a *really* hard time understanding the code that uses them, because if I don't stare at their definitions all the time, I will immediately forget what they contain and what they are used for because I don't understand the names at all (and there is nothing which explicitly tells me what they are used for either). The fact that all of them are starting with BlkTransaction makes mixing them up even easier.
Understood.
Quoted 36 lines
Quoted 11 lines
From the point of view of the "transaction action developer," the interface to the new feature is found via the "
new_transaction_wrapper" function, But some care must be taken to make sure this callback actually gets used once it has been requested. The whole thing can be aborted with the "undo_transaction_wrapper" function.All of the other functions that got added to
blockdev.cin Patch #3 are there to assist these two "interface" functions in doing what they claim to.Quoted 8 lines
Everything in patch 4 and 5 just adds more reference count flexibility to things that are only of interest to
drive_backup.Patch 6 allows us to inject our special callback wrapper into the
qmp_drive_backupfunction.Clear as mud?
Control flow and concept, clear. Implementation, still very muddy, sorry...
Max
Quoted 11 lines
--js
Quoted 9 lines
Quoted 7 lines
include/block/block_int.h +4
-0+*/ +void backup_transaction_complete (BlockJob *job,int ret); + +void blk_dev_change_media_cb (BlockBackend *blk,bool load);bool blk_dev_has_removable_media (BlockBackend *blk);void blk_dev_eject_request (BlockBackend *blk,bool force);
PATCH 8/11 iotests: add QMP event waiting queue
In reply to John SnowA 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.
--- tests/qemu-iotests/iotests.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+)
tests/qemu-iotests/iotests.py +38 -0
@@ -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', maxtries=3, 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 + for _ in range(maxtries): + event = self._qmp.pull_event(wait=True) + 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):
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 08/11] iotests: add QMP event waiting queue
Quoted 11 lines · John Snow
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.
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/iotests.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+)
PATCH 9/11 iotests: test 124 - drive object refactoring
In reply to John SnowThe original test was not particularly good about keeping the relationships between bitmaps, drives, and images very explicit, so this patch adds an explicit 'drive' dict that is used to keep these relationships explicit.
This is necessary in order to test two full backup chains simultaneously for two drives, which will happen in a forthcoming test that examines failure scenarios for incremental backups created for multiple drives in a single transaction.
Highlights:
- Each test case carries around a list of drives
- Each bitmap now acknowledges the full backup belonging to the drive as its "last target" if it hasn't made an incremental backup yet.
- Most functions generally try to accept a drive argument instead of target or format arguments.
- Filenames are now based on their formatting and id name.
--- tests/qemu-iotests/124 | 212 +++++++++++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 86 deletions(-)
tests/qemu-iotests/124 +126 -86
@@ -29,14 +29,18 @@ def io_write_patterns(img, patterns): iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) class Bitmap: - def __init__(self, name, node): + def __init__(self, name, drive): self.name = name - self.node = node + self.drive = drive self.pattern = os.path.join(iotests.test_dir.replace('%', '%%'), - '%s.backup.%%i.img' % name) + '%s.%s.backup.%%i.img' % (drive['id'], + name)) self.num = 0 self.backups = list() + def base_target(self): + return self.drive['backup'] + def new_target(self, num=None): if num is None: num = self.num
@@ -46,7 +50,9 @@ class Bitmap: return target def last_target(self): - return self.backups[-1] + if self.backups: + return self.backups[-1] + return self.base_target() def del_target(self): os.remove(self.backups.pop())
@@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase): def setUp(self): self.bitmaps = list() self.files = list() + self.drives = list() self.vm = iotests.VM() - self.test_img = os.path.join(iotests.test_dir, 'base.img') - self.full_bak = os.path.join(iotests.test_dir, 'backup.img') - self.foo_img = os.path.join(iotests.test_dir, 'foo.bar') - self.img_create(self.test_img, iotests.imgfmt) - self.vm.add_drive(self.test_img) + self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt) + # Create a base image with a distinctive patterning - io_write_patterns(self.test_img, (('0x41', 0, 512), - ('0xd5', '1M', '32k'), - ('0xdc', '32M', '124k'))) + 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): plist = list()
@@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.files.append(img) - def create_full_backup(self, drive='drive0'): - res = self.vm.qmp('drive-backup', device=drive, - sync='full', format=iotests.imgfmt, - target=self.full_bak) + def create_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + + res = self.vm.qmp('drive-backup', device=drive['id'], + sync='full', format=drive['fmt'], + target=drive['backup']) self.assert_qmp(res, 'return', {}) - self.wait_until_completed(drive) - self.check_full_backup() - self.files.append(self.full_bak) + self.wait_until_completed(drive['id']) + self.check_full_backup(drive) + self.files.append(drive['backup']) + return drive['backup'] - def check_full_backup(self): - self.assertTrue(iotests.compare_images(self.test_img, self.full_bak)) + def check_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + self.assertTrue(iotests.compare_images(drive['file'], drive['backup'])) - def add_bitmap(self, name, node='drive0'): - bitmap = Bitmap(name, node) + def add_bitmap(self, name, drive): + bitmap = Bitmap(name, drive) self.bitmaps.append(bitmap) - result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node, - name=bitmap.name) + result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], + name=name) self.assert_qmp(result, 'return', {}) return bitmap
@@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): if bitmap is None: bitmap = self.bitmaps[-1] - # If this is the first incremental backup for a bitmap, - # use the full backup as a backing image. Otherwise, use - # the last incremental backup. if parent is None: - if bitmap.num == 0: - parent = self.full_bak - else: - parent = bitmap.last_target() + parent = bitmap.last_target() target = bitmap.new_target(num) - self.img_create(target, iotests.imgfmt, parent=parent) + self.img_create(target, bitmap.drive['fmt'], parent=parent) - result = self.vm.qmp('drive-backup', device=bitmap.node, + result = self.vm.qmp('drive-backup', device=bitmap.drive['id'], sync='dirty-bitmap', bitmap=bitmap.name, - format=iotests.imgfmt, target=target, + format=bitmap.drive['fmt'], target=target, mode='existing') self.assert_qmp(result, 'return', {}) - event = self.wait_until_completed(bitmap.node, check_offset=validate, - allow_failures=(not validate)) - if 'error' in event['data']: + return self.wait_incremental(bitmap, validate) + + + def wait_incremental(self, bitmap=None, + validate=True, error='Input/output error'): + if bitmap is None: + bitmap = self.bitmaps[-1] + + event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", + match={'data': {'device': + bitmap.drive['id']}}) + if validate: + self.assert_qmp_absent(event, 'data/error') + return self.check_incremental(bitmap) + else: + self.assert_qmp(event, 'data/error', error) bitmap.del_target() return False - if validate: - return self.check_incremental(target) - def check_incremental(self, target=None): - if target is None: - target = self.bitmaps[-1].last_target() - self.assertTrue(iotests.compare_images(self.test_img, target)) + def check_incremental(self, bitmap=None): + if bitmap is None: + bitmap = self.bitmaps[-1] + self.assertTrue(iotests.compare_images(bitmap.drive['file'], + bitmap.last_target())) return True
@@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase): i.e.; after IO requests begin modifying the drive. ''' self.create_full_backup() - self.add_bitmap('bitmap0', 'drive0') + 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('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) self.create_incremental() return True
@@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): bitmap AFTER writes have already occurred. Use transactions to create a full backup and synchronize both bitmaps to this backup. Create an incremental backup through both bitmaps and verify that - both backups match the full backup. + both backups match the current drive0 image. ''' - bitmap0 = self.add_bitmap('bitmap0', 'drive0') - self.hmp_io_writes('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) - bitmap1 = self.add_bitmap('bitmap1', 'drive0') + + drive0 = self.drives[0] + bitmap0 = self.add_bitmap('bitmap0', self.drives[0]) + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + bitmap1 = self.add_bitmap('bitmap1', drive0) result = self.vm.qmp('transaction', actions=[ { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap0' }, + 'data': { 'node': bitmap0.drive['id'], + 'name': bitmap0.name }, }, { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap1' }, + 'data': { 'node': bitmap1.drive['id'], + 'name': bitmap1.name }, }, { 'type': 'drive-backup', - 'data': { 'device': 'drive0', + 'data': { 'device': drive0['id'], 'sync': 'full', - 'format': iotests.imgfmt, - 'target': self.full_bak }, + 'format': drive0['fmt'], + 'target': drive0['backup'] }, } ]) self.assert_qmp(result, 'return', {}) self.wait_until_completed() - self.files.append(self.full_bak) + self.files.append(drive0['backup']) self.check_full_backup() - self.hmp_io_writes('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) # Both bitmaps should be in sync and create fully valid # incremental backups res1 = self.create_incremental(bitmap0)
@@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase): afterwards and verify that the backup created is correct. ''' - # Create a blkdebug interface to this img as 'drive1' + # 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', - 'driver': iotests.imgfmt, + 'driver': drive1['fmt'], 'file': { 'driver': 'blkdebug', 'image': { 'driver': 'file', - 'filename': self.test_img + 'filename': drive1['file'] }, 'set-state': [{ 'event': 'flush_to_disk',
@@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase): }) self.assert_qmp(result, 'return', {}) - self.create_full_backup() - self.add_bitmap('bitmap0', 'drive1') + self.create_full_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', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) self.create_incremental() def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() - self.files.append(self.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', - sync='dirty-bitmap', format=iotests.imgfmt, - target=self.foo_img) + 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.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', + self.files.append(self.err_img) + result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], sync='dirty-bitmap', bitmap='unknown', - format=iotests.imgfmt, target=self.foo_img) + format=self.drives[0]['fmt'], target=self.err_img) self.assert_qmp(result, 'error/class', 'GenericError')
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring
Quoted 19 lines · John Snow
The original test was not particularly good about keeping the relationships between bitmaps, drives, and images very explicit, so this patch adds an explicit 'drive' dict that is used to keep these relationships explicit.
This is necessary in order to test two full backup chains simultaneously for two drives, which will happen in a forthcoming test that examines failure scenarios for incremental backups created for multiple drives in a single transaction.
Highlights:
- Each test case carries around a list of drives
- Each bitmap now acknowledges the full backup belonging to the drive as its "last target" if it hasn't made an incremental backup yet.
- Most functions generally try to accept a drive argument instead of target or format arguments.
- Filenames are now based on their formatting and id name.
Quoted 4 lines
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 212 +++++++++++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 86 deletions(-)
How about putting the previous patch in your transactionless series and squashing this one into the respective patches there?
Some remarks below, whether you follow them or not is up to you.
Quoted 39 lines
tests/qemu-iotests/124 +10
-4@@ -29,14 +29,18 @@ def io_write_patterns(img, patterns): iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) class Bitmap: - def __init__(self, name, node): + def __init__(self, name, drive): self.name = name - self.node = node + self.drive = drive self.pattern = os.path.join(iotests.test_dir.replace('%', '%%'), - '%s.backup.%%i.img' % name) + '%s.%s.backup.%%i.img' % (drive['id'], + name)) self.num = 0 self.backups = list() + def base_target(self): + return self.drive['backup'] + def new_target(self, num=None): if num is None: num = self.num@@ -46,7 +50,9 @@ class Bitmap: return target def last_target(self): - return self.backups[-1] + if self.backups: + return self.backups[-1] + return self.base_target() def del_target(self): os.remove(self.backups.pop())@@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase): def setUp(self):
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 74
Quoted 10 lines
tests/qemu-iotests/124 +2
-5self.bitmaps = list() self.files = list() + self.drives = list() self.vm = iotests.VM() - self.test_img = os.path.join(iotests.test_dir, 'base.img') - self.full_bak = os.path.join(iotests.test_dir, 'backup.img') - self.foo_img = os.path.join(iotests.test_dir, 'foo.bar') - self.img_create(self.test_img, iotests.imgfmt) - self.vm.add_drive(self.test_img) + self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)
Nice. *g*
(using the format name as the file extension)
((even though you're not doing it in __init__))
Quoted 64 lines
tests/qemu-iotests/124 +37
-12+ # Create a base image with a distinctive patterning - io_write_patterns(self.test_img, (('0x41', 0, 512), - ('0xd5', '1M', '32k'), - ('0xdc', '32M', '124k'))) + 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): plist = list()@@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.files.append(img) - def create_full_backup(self, drive='drive0'): - res = self.vm.qmp('drive-backup', device=drive, - sync='full', format=iotests.imgfmt, - target=self.full_bak) + def create_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + + res = self.vm.qmp('drive-backup', device=drive['id'], + sync='full', format=drive['fmt'], + target=drive['backup']) self.assert_qmp(res, 'return', {}) - self.wait_until_completed(drive) - self.check_full_backup() - self.files.append(self.full_bak) + self.wait_until_completed(drive['id']) + self.check_full_backup(drive) + self.files.append(drive['backup']) + return drive['backup'] - def check_full_backup(self): - self.assertTrue(iotests.compare_images(self.test_img, self.full_bak)) + def check_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + self.assertTrue(iotests.compare_images(drive['file'], drive['backup']))
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 138
Quoted 10 lines
tests/qemu-iotests/124 +4
-4- def add_bitmap(self, name, node='drive0'): - bitmap = Bitmap(name, node) + def add_bitmap(self, name, drive): + bitmap = Bitmap(name, drive) self.bitmaps.append(bitmap) - result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node, - name=bitmap.name) + result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], + name=name)
No real need to replace bitmap.name by name here, but no harm in doing it.
Quoted 157 lines
tests/qemu-iotests/124 +51
-43self.assert_qmp(result, 'return', {}) return bitmap@@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): if bitmap is None: bitmap = self.bitmaps[-1] - # If this is the first incremental backup for a bitmap, - # use the full backup as a backing image. Otherwise, use - # the last incremental backup. if parent is None: - if bitmap.num == 0: - parent = self.full_bak - else: - parent = bitmap.last_target() + parent = bitmap.last_target() target = bitmap.new_target(num) - self.img_create(target, iotests.imgfmt, parent=parent) + self.img_create(target, bitmap.drive['fmt'], parent=parent) - result = self.vm.qmp('drive-backup', device=bitmap.node, + result = self.vm.qmp('drive-backup', device=bitmap.drive['id'], sync='dirty-bitmap', bitmap=bitmap.name, - format=iotests.imgfmt, target=target, + format=bitmap.drive['fmt'], target=target, mode='existing') self.assert_qmp(result, 'return', {}) - event = self.wait_until_completed(bitmap.node, check_offset=validate, - allow_failures=(not validate)) - if 'error' in event['data']: + return self.wait_incremental(bitmap, validate) + + + def wait_incremental(self, bitmap=None, + validate=True, error='Input/output error'): + if bitmap is None: + bitmap = self.bitmaps[-1] + + event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", + match={'data': {'device': + bitmap.drive['id']}}) + if validate: + self.assert_qmp_absent(event, 'data/error') + return self.check_incremental(bitmap) + else: + self.assert_qmp(event, 'data/error', error) bitmap.del_target() return False - if validate: - return self.check_incremental(target) - def check_incremental(self, target=None): - if target is None: - target = self.bitmaps[-1].last_target() - self.assertTrue(iotests.compare_images(self.test_img, target)) + def check_incremental(self, bitmap=None): + if bitmap is None: + bitmap = self.bitmaps[-1] + self.assertTrue(iotests.compare_images(bitmap.drive['file'], + bitmap.last_target())) return True@@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase): i.e.; after IO requests begin modifying the drive. ''' self.create_full_backup() - self.add_bitmap('bitmap0', 'drive0') + 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('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) self.create_incremental() return True@@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): bitmap AFTER writes have already occurred. Use transactions to create a full backup and synchronize both bitmaps to this backup. Create an incremental backup through both bitmaps and verify that - both backups match the full backup. + both backups match the current drive0 image. ''' - bitmap0 = self.add_bitmap('bitmap0', 'drive0') - self.hmp_io_writes('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) - bitmap1 = self.add_bitmap('bitmap1', 'drive0') + + drive0 = self.drives[0] + bitmap0 = self.add_bitmap('bitmap0', self.drives[0]) + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + bitmap1 = self.add_bitmap('bitmap1', drive0) result = self.vm.qmp('transaction', actions=[ { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap0' }, + 'data': { 'node': bitmap0.drive['id'], + 'name': bitmap0.name }, }, { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap1' }, + 'data': { 'node': bitmap1.drive['id'], + 'name': bitmap1.name }, }, { 'type': 'drive-backup', - 'data': { 'device': 'drive0', + 'data': { 'device': drive0['id'], 'sync': 'full', - 'format': iotests.imgfmt, - 'target': self.full_bak }, + 'format': drive0['fmt'], + 'target': drive0['backup'] }, } ]) self.assert_qmp(result, 'return', {}) self.wait_until_completed() - self.files.append(self.full_bak) + self.files.append(drive0['backup']) self.check_full_backup() - self.hmp_io_writes('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) # Both bitmaps should be in sync and create fully valid # incremental backups res1 = self.create_incremental(bitmap0)@@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase): afterwards and verify that the backup created is correct.
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 286
Quoted 10 lines
tests/qemu-iotests/124 +5
-1''' - # Create a blkdebug interface to this img as 'drive1' + # 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': drive1['id']" would work, too.
Max
Quoted 65 lines
tests/qemu-iotests/124 +17
-17- 'driver': iotests.imgfmt, + 'driver': drive1['fmt'], 'file': { 'driver': 'blkdebug', 'image': { 'driver': 'file', - 'filename': self.test_img + 'filename': drive1['file'] }, 'set-state': [{ 'event': 'flush_to_disk',@@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase): }) self.assert_qmp(result, 'return', {}) - self.create_full_backup() - self.add_bitmap('bitmap0', 'drive1') + self.create_full_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', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) self.create_incremental() def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() - self.files.append(self.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', - sync='dirty-bitmap', format=iotests.imgfmt, - target=self.foo_img) + 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.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', + self.files.append(self.err_img) + result = self.vm.qmp('drive-backup', device=self.drives[0]['id'], sync='dirty-bitmap', bitmap='unknown', - format=iotests.imgfmt, target=self.foo_img) + format=self.drives[0]['fmt'], target=self.err_img) self.assert_qmp(result, 'error/class', 'GenericError')
Re: [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring
Quoted 20 lines · Max Reitz
Quoted 19 lines · John Snow
The original test was not particularly good about keeping the relationships between bitmaps, drives, and images very explicit, so this patch adds an explicit 'drive' dict that is used to keep these relationships explicit.
This is necessary in order to test two full backup chains simultaneously for two drives, which will happen in a forthcoming test that examines failure scenarios for incremental backups created for multiple drives in a single transaction.
Highlights:
- Each test case carries around a list of drives
- Each bitmap now acknowledges the full backup belonging to the drive as its "last target" if it hasn't made an incremental backup yet.
- Most functions generally try to accept a drive argument instead of target or format arguments.
- Filenames are now based on their formatting and id name.
Quoted 9 lines
Quoted 5 lines
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 212 +++++++++++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 86 deletions(-)How about putting the previous patch in your transactionless series and squashing this one into the respective patches there?
I don't want to startle the cat.
I'd rather get the 'transactionless' checked in, then worry about features that are important for transactions in the series that introduces those features.
If we keep thinking "Oh, but this function hasn't gone upstream in the first place, so let's fix it there" in this series, then the first series getting committed is going to wind up dependent on this series getting reviewed as we keep changing things.
Since this group of patches is more experimental, I'd really prefer to keep it separate that way.
Even if this patch is ugly. Sorry about that.
Quoted 55 lines
Reviewed-by Max Reitz <[email protected]>Some remarks below, whether you follow them or not is up to you.
Quoted 38 lines
tests/qemu-iotests/124 +10
-4@@ -29,14 +29,18 @@ def io_write_patterns(img, patterns): iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) class Bitmap: - def __init__(self, name, node): + def __init__(self, name, drive): self.name = name - self.node = node + self.drive = drive self.pattern = os.path.join(iotests.test_dir.replace('%', '%%'), - '%s.backup.%%i.img' % name) + '%s.%s.backup.%%i.img' % (drive['id'], + name)) self.num = 0 self.backups = list() + def base_target(self): + return self.drive['backup'] + def new_target(self, num=None): if num is None: num = self.num@@ -46,7 +50,9 @@ class Bitmap: return target def last_target(self): - return self.backups[-1] + if self.backups: + return self.backups[-1] + return self.base_target() def del_target(self): os.remove(self.backups.pop())@@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase): def setUp(self): self.bitmaps = list()tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 75
Quoted 10 lines
tests/qemu-iotests/124 +2
-5self.files = list() + self.drives = list() self.vm = iotests.VM() - self.test_img = os.path.join(iotests.test_dir, 'base.img') - self.full_bak = os.path.join(iotests.test_dir, 'backup.img') - self.foo_img = os.path.join(iotests.test_dir, 'foo.bar') - self.img_create(self.test_img, iotests.imgfmt) - self.vm.add_drive(self.test_img) + self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)Nice. *g*
Quoted 4 lines
(using the format name as the file extension)
((even though you're not doing it in
__init__))
Can't please everyone. :)
Quoted 236 lines
Quoted 60 lines
tests/qemu-iotests/124 +8
-3+ # Create a base image with a distinctive patterning - io_write_patterns(self.test_img, (('0x41', 0, 512), - ('0xd5', '1M', '32k'), - ('0xdc', '32M', '124k'))) + 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):
tests/qemu-iotests/124 +2
-0+ if path is None: + path = os.path.join(iotests.test_dir, '%s.%s' % (node_id,fmt))
tests/qemu-iotests/124 +27
-9+ 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): plist = list()@@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.files.append(img) - def create_full_backup(self, drive='drive0'): - res = self.vm.qmp('drive-backup', device=drive, - sync='full', format=iotests.imgfmt, - target=self.full_bak) + def create_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + + res = self.vm.qmp('drive-backup', device=drive['id'], + sync='full', format=drive['fmt'], + target=drive['backup']) self.assert_qmp(res, 'return', {}) - self.wait_until_completed(drive) - self.check_full_backup() - self.files.append(self.full_bak) + self.wait_until_completed(drive['id']) + self.check_full_backup(drive) + self.files.append(drive['backup']) + return drive['backup'] - def check_full_backup(self): - self.assertTrue(iotests.compare_images(self.test_img, self.full_bak)) + def check_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + self.assertTrue(iotests.compare_images(drive['file'],tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 134
Quoted 10 lines
tests/qemu-iotests/124 +4
-4drive['backup'])) - def add_bitmap(self, name, node='drive0'): - bitmap = Bitmap(name, node) + def add_bitmap(self, name, drive): + bitmap = Bitmap(name, drive) self.bitmaps.append(bitmap) - result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node, - name=bitmap.name) + result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], + name=name)No real need to replace bitmap.name by name here, but no harm in doing it.
Quoted 150 lines
tests/qemu-iotests/124 +32
-26self.assert_qmp(result, 'return', {}) return bitmap@@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): if bitmap is None: bitmap = self.bitmaps[-1] - # If this is the first incremental backup for a bitmap, - # use the full backup as a backing image. Otherwise, use - # the last incremental backup. if parent is None: - if bitmap.num == 0: - parent = self.full_bak - else: - parent = bitmap.last_target() + parent = bitmap.last_target() target = bitmap.new_target(num) - self.img_create(target, iotests.imgfmt, parent=parent) + self.img_create(target, bitmap.drive['fmt'], parent=parent) - result = self.vm.qmp('drive-backup', device=bitmap.node, + result = self.vm.qmp('drive-backup', device=bitmap.drive['id'], sync='dirty-bitmap', bitmap=bitmap.name, - format=iotests.imgfmt, target=target, + format=bitmap.drive['fmt'], target=target, mode='existing') self.assert_qmp(result, 'return', {}) - event = self.wait_until_completed(bitmap.node, check_offset=validate, - allow_failures=(not validate)) - if 'error' in event['data']: + return self.wait_incremental(bitmap, validate) + + + def wait_incremental(self, bitmap=None, + validate=True, error='Input/output error'): + if bitmap is None: + bitmap = self.bitmaps[-1] + + event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", + match={'data': {'device': + bitmap.drive['id']}}) + if validate: + self.assert_qmp_absent(event, 'data/error') + return self.check_incremental(bitmap) + else: + self.assert_qmp(event, 'data/error', error) bitmap.del_target() return False - if validate: - return self.check_incremental(target) - def check_incremental(self, target=None): - if target is None: - target = self.bitmaps[-1].last_target() - self.assertTrue(iotests.compare_images(self.test_img, target)) + def check_incremental(self, bitmap=None): + if bitmap is None: + bitmap = self.bitmaps[-1] + self.assertTrue(iotests.compare_images(bitmap.drive['file'], + bitmap.last_target())) return True@@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase): i.e.; after IO requests begin modifying the drive. ''' self.create_full_backup() - self.add_bitmap('bitmap0', 'drive0') + 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('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M')))
self.create_incremental()
return Truetests/qemu-iotests/124 +0
-0@@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): bitmap AFTER writes have already occurred. Use transactionsto create
a full backup and synchronize both bitmaps to this backup. Create an incremental backup through both bitmaps and verify
thattests/qemu-iotests/124 +19
-17- both backups match the full backup. + both backups match the current drive0 image. ''' - bitmap0 = self.add_bitmap('bitmap0', 'drive0') - self.hmp_io_writes('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) - bitmap1 = self.add_bitmap('bitmap1', 'drive0') + + drive0 = self.drives[0] + bitmap0 = self.add_bitmap('bitmap0', self.drives[0]) + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + bitmap1 = self.add_bitmap('bitmap1', drive0) result = self.vm.qmp('transaction', actions=[ { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap0' }, + 'data': { 'node': bitmap0.drive['id'], + 'name': bitmap0.name }, }, { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap1' }, + 'data': { 'node': bitmap1.drive['id'], + 'name': bitmap1.name }, }, { 'type': 'drive-backup', - 'data': { 'device': 'drive0', + 'data': { 'device': drive0['id'], 'sync': 'full', - 'format': iotests.imgfmt, - 'target': self.full_bak }, + 'format': drive0['fmt'], + 'target': drive0['backup'] }, } ]) self.assert_qmp(result, 'return', {}) self.wait_until_completed() - self.files.append(self.full_bak) + self.files.append(drive0['backup']) self.check_full_backup() - self.hmp_io_writes('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) # Both bitmaps should be in sync and create fully valid # incremental backups res1 = self.create_incremental(bitmap0)tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 285
Quoted 10 lines
tests/qemu-iotests/124 +5
-1@@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase): afterwards and verify that the backup created is correct. ''' - # Create a blkdebug interface to this img as 'drive1' + # 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': drive1['id']" would work, too.
Quoted 2 lines
Max
You're right! I missed one.
Quoted 63 lines
Quoted 61 lines
tests/qemu-iotests/124 +4
-4- 'driver': iotests.imgfmt, + 'driver': drive1['fmt'], 'file': { 'driver': 'blkdebug', 'image': { 'driver': 'file', - 'filename': self.test_img + 'filename': drive1['file'] }, 'set-state': [{ 'event': 'flush_to_disk',@@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase): }) self.assert_qmp(result, 'return', {}) - self.create_full_backup() - self.add_bitmap('bitmap0', 'drive1') + self.create_full_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 requestshere.
tests/qemu-iotests/124 +8
-10- self.hmp_io_writes('drive1', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) self.create_incremental() def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() - self.files.append(self.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', - sync='dirty-bitmap', format=iotests.imgfmt, - target=self.foo_img) + 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'],tests/qemu-iotests/124 +3
-2+ 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.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', + self.files.append(self.err_img) + result = self.vm.qmp('drive-backup',device=self.drives[0]['id'],
sync='dirty-bitmap', bitmap='unknown',tests/qemu-iotests/124 +1
-1- format=iotests.imgfmt, target=self.foo_img) + format=self.drives[0]['fmt'],target=self.err_img)
self.assert_qmp(result, 'error/class', 'GenericError')
Re: [Qemu-devel] [PATCH 09/11] iotests: test 124 - drive object refactoring
Quoted 36 lines · John Snow
Quoted 20 lines · Max Reitz
Quoted 19 lines · John Snow
The original test was not particularly good about keeping the relationships between bitmaps, drives, and images very explicit, so this patch adds an explicit 'drive' dict that is used to keep these relationships explicit.
This is necessary in order to test two full backup chains simultaneously for two drives, which will happen in a forthcoming test that examines failure scenarios for incremental backups created for multiple drives in a single transaction.
Highlights:
- Each test case carries around a list of drives
- Each bitmap now acknowledges the full backup belonging to the drive as its "last target" if it hasn't made an incremental backup yet.
- Most functions generally try to accept a drive argument instead of target or format arguments.
- Filenames are now based on their formatting and id name.Quoted 9 lines
Quoted 5 lines
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 212 +++++++++++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 86 deletions(-)How about putting the previous patch in your transactionless series and squashing this one into the respective patches there?
I don't want to startle the cat.
Quoted 7 lines
I'd rather get the 'transactionless' checked in, then worry about features that are important for transactions in the series that introduces those features.
If we keep thinking "Oh, but this function hasn't gone upstream in the first place, so let's fix it there" in this series, then the first series getting committed is going to wind up dependent on this series getting reviewed as we keep changing things.
Since this group of patches is more experimental, I'd really prefer to keep it separate that way.
Even if this patch is ugly. Sorry about that.
Well, you're targetting 2.4 with your other series anyway. I understand that it's been a couple of revisions already and you really want to get it merged by now, but this is a test, so if you just squash it in there, I don't think it will hold up that series much.
(A good rule for tests is: As long as they're passing and catch all desirable cases, nobody will really complain)
Up to you, but I guess you can see how this patch just looks like "I should have done it differently in the first place", and because that "first place" is still in flight, I'm wondering why you're not just fixing it there.
Max
Quoted 375 lines
Quoted 56 lines
Reviewed-by Max Reitz <[email protected]>Some remarks below, whether you follow them or not is up to you.
Quoted 38 lines
tests/qemu-iotests/124 +10
-4@@ -29,14 +29,18 @@ def io_write_patterns(img, patterns): iotests.qemu_io('-c', 'write -P%s %s %s' % pattern, img) class Bitmap: - def __init__(self, name, node): + def __init__(self, name, drive): self.name = name - self.node = node + self.drive = drive self.pattern = os.path.join(iotests.test_dir.replace('%', '%%'), - '%s.backup.%%i.img' % name) + '%s.%s.backup.%%i.img' % (drive['id'], + name)) self.num = 0 self.backups = list() + def base_target(self): + return self.drive['backup'] + def new_target(self, num=None): if num is None: num = self.num@@ -46,7 +50,9 @@ class Bitmap: return target def last_target(self): - return self.backups[-1] + if self.backups: + return self.backups[-1] + return self.base_target() def del_target(self): os.remove(self.backups.pop())@@ -63,19 +69,35 @@ class TestIncrementalBackup(iotests.QMPTestCase): def setUp(self): self.bitmaps = list()tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 75
Quoted 10 lines
tests/qemu-iotests/124 +2
-5self.files = list() + self.drives = list() self.vm = iotests.VM() - self.test_img = os.path.join(iotests.test_dir, 'base.img') - self.full_bak = os.path.join(iotests.test_dir, 'backup.img') - self.foo_img = os.path.join(iotests.test_dir, 'foo.bar') - self.img_create(self.test_img, iotests.imgfmt) - self.vm.add_drive(self.test_img) + self.err_img = os.path.join(iotests.test_dir, 'err.%s' % iotests.imgfmt)Nice. *g*
Quoted 4 lines
(using the format name as the file extension)
((even though you're not doing it in
__init__))Can't please everyone. :)
Quoted 242 lines
Quoted 63 lines
tests/qemu-iotests/124 +8
-3+ # Create a base image with a distinctive patterning - io_write_patterns(self.test_img, (('0x41', 0, 512), - ('0xd5', '1M', '32k'), - ('0xdc', '32M', '124k'))) + 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):
tests/qemu-iotests/124 +2
-0+ if path is None: + path = os.path.join(iotests.test_dir, '%s.%s' % (node_id,fmt))
tests/qemu-iotests/124 +3
-0+ if backup is None: + backup = os.path.join(iotests.test_dir, + '%s.full.backup.%s' % (node_id,fmt))
tests/qemu-iotests/124 +24
-10+ + 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): plist = list()@@ -89,25 +111,31 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.files.append(img) - def create_full_backup(self, drive='drive0'): - res = self.vm.qmp('drive-backup', device=drive, - sync='full', format=iotests.imgfmt, - target=self.full_bak) + def create_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + + res = self.vm.qmp('drive-backup', device=drive['id'], + sync='full', format=drive['fmt'], + target=drive['backup']) self.assert_qmp(res, 'return', {}) - self.wait_until_completed(drive) - self.check_full_backup() - self.files.append(self.full_bak) + self.wait_until_completed(drive['id']) + self.check_full_backup(drive) + self.files.append(drive['backup']) + return drive['backup'] - def check_full_backup(self): - self.assertTrue(iotests.compare_images(self.test_img, self.full_bak)) + def check_full_backup(self, drive=None): + if drive is None: + drive = self.drives[-1] + self.assertTrue(iotests.compare_images(drive['file'], drive['backup'])) - def add_bitmap(self, name, node='drive0'):tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 136
Quoted 10 lines
tests/qemu-iotests/124 +4
-3- bitmap = Bitmap(name, node) + def add_bitmap(self, name, drive): + bitmap = Bitmap(name, drive) self.bitmaps.append(bitmap) - result = self.vm.qmp('block-dirty-bitmap-add', node=bitmap.node, - name=bitmap.name) + result = self.vm.qmp('block-dirty-bitmap-add', node=drive['id'], + name=name)No real need to replace bitmap.name by name here, but no harm in doing it.
Quoted 153 lines
tests/qemu-iotests/124 +32
-26self.assert_qmp(result, 'return', {}) return bitmap@@ -117,37 +145,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): if bitmap is None: bitmap = self.bitmaps[-1] - # If this is the first incremental backup for a bitmap, - # use the full backup as a backing image. Otherwise, use - # the last incremental backup. if parent is None: - if bitmap.num == 0: - parent = self.full_bak - else: - parent = bitmap.last_target() + parent = bitmap.last_target() target = bitmap.new_target(num) - self.img_create(target, iotests.imgfmt, parent=parent) + self.img_create(target, bitmap.drive['fmt'], parent=parent) - result = self.vm.qmp('drive-backup', device=bitmap.node, + result = self.vm.qmp('drive-backup', device=bitmap.drive['id'], sync='dirty-bitmap', bitmap=bitmap.name, - format=iotests.imgfmt, target=target, + format=bitmap.drive['fmt'], target=target, mode='existing') self.assert_qmp(result, 'return', {}) - event = self.wait_until_completed(bitmap.node, check_offset=validate, - allow_failures=(not validate)) - if 'error' in event['data']: + return self.wait_incremental(bitmap, validate) + + + def wait_incremental(self, bitmap=None, + validate=True, error='Input/output error'): + if bitmap is None: + bitmap = self.bitmaps[-1] + + event = self.vm.event_wait(name="BLOCK_JOB_COMPLETED", + match={'data': {'device': + bitmap.drive['id']}}) + if validate: + self.assert_qmp_absent(event, 'data/error') + return self.check_incremental(bitmap) + else: + self.assert_qmp(event, 'data/error', error) bitmap.del_target() return False - if validate: - return self.check_incremental(target) - def check_incremental(self, target=None): - if target is None: - target = self.bitmaps[-1].last_target() - self.assertTrue(iotests.compare_images(self.test_img, target)) + def check_incremental(self, bitmap=None): + if bitmap is None: + bitmap = self.bitmaps[-1] + self.assertTrue(iotests.compare_images(bitmap.drive['file'], + bitmap.last_target())) return True@@ -166,20 +200,20 @@ class TestIncrementalBackup(iotests.QMPTestCase): i.e.; after IO requests begin modifying the drive. ''' self.create_full_backup() - self.add_bitmap('bitmap0', 'drive0') + 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('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(self.drives[0]['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M')))
self.create_incremental()
return Truetests/qemu-iotests/124 +0
-0@@ -191,41 +225,43 @@ class TestIncrementalBackup(iotests.QMPTestCase): bitmap AFTER writes have already occurred. Use transactionsto create
a full backup and synchronize both bitmaps to this backup. Create an incremental backup through both bitmaps and verify
thattests/qemu-iotests/124 +19
-17- both backups match the full backup. + both backups match the current drive0 image. ''' - bitmap0 = self.add_bitmap('bitmap0', 'drive0') - self.hmp_io_writes('drive0', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) - bitmap1 = self.add_bitmap('bitmap1', 'drive0') + + drive0 = self.drives[0] + bitmap0 = self.add_bitmap('bitmap0', self.drives[0]) + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + bitmap1 = self.add_bitmap('bitmap1', drive0) result = self.vm.qmp('transaction', actions=[ { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap0' }, + 'data': { 'node': bitmap0.drive['id'], + 'name': bitmap0.name }, }, { 'type': 'block-dirty-bitmap-clear', - 'data': { 'node': 'drive0', - 'name': 'bitmap1' }, + 'data': { 'node': bitmap1.drive['id'], + 'name': bitmap1.name }, }, { 'type': 'drive-backup', - 'data': { 'device': 'drive0', + 'data': { 'device': drive0['id'], 'sync': 'full', - 'format': iotests.imgfmt, - 'target': self.full_bak }, + 'format': drive0['fmt'], + 'target': drive0['backup'] }, } ]) self.assert_qmp(result, 'return', {}) self.wait_until_completed() - self.files.append(self.full_bak) + self.files.append(drive0['backup']) self.check_full_backup() - self.hmp_io_writes('drive0', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive0['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) # Both bitmaps should be in sync and create fully valid # incremental backups res1 = self.create_incremental(bitmap0)tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 285
Quoted 10 lines
tests/qemu-iotests/124 +5
-1@@ -241,15 +277,19 @@ class TestIncrementalBackup(iotests.QMPTestCase): afterwards and verify that the backup created is correct. ''' - # Create a blkdebug interface to this img as 'drive1' + # 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': drive1['id']" would work, too.
Quoted 2 lines
Max
You're right! I missed one.
Quoted 65 lines
Quoted 63 lines
tests/qemu-iotests/124 +4
-4- 'driver': iotests.imgfmt, + 'driver': drive1['fmt'], 'file': { 'driver': 'blkdebug', 'image': { 'driver': 'file', - 'filename': self.test_img + 'filename': drive1['file'] }, 'set-state': [{ 'event': 'flush_to_disk',@@ -267,38 +307,38 @@ class TestIncrementalBackup(iotests.QMPTestCase): }) self.assert_qmp(result, 'return', {}) - self.create_full_backup() - self.add_bitmap('bitmap0', 'drive1') + self.create_full_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 requestshere.
tests/qemu-iotests/124 +6
-9- self.hmp_io_writes('drive1', (('0xab', 0, 512), - ('0xfe', '16M', '256k'), - ('0x64', '32736k', '64k'))) + 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', (('0x9a', 0, 512), - ('0x55', '8M', '352k'), - ('0x78', '15872k', '1M'))) + self.hmp_io_writes(drive1['id'], (('0x9a', 0, 512), + ('0x55', '8M', '352k'), + ('0x78', '15872k', '1M'))) self.create_incremental() def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() - self.files.append(self.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', - sync='dirty-bitmap',format=iotests.imgfmt,
tests/qemu-iotests/124 +2
-1- target=self.foo_img) + 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'],tests/qemu-iotests/124 +3
-2+ 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.foo_img) - result = self.vm.qmp('drive-backup', device='drive0', + self.files.append(self.err_img) + result = self.vm.qmp('drive-backup',device=self.drives[0]['id'],
sync='dirty-bitmap', bitmap='unknown',
- format=iotests.imgfmt,
target=self.foo_img)
+ format=self.drives[0]['fmt'],
target=self.err_img)
self.assert_qmp(result, 'error/class', 'GenericError')
PATCH 10/11 iotests: 124 - backup_prepare refactoring
In reply to John SnowAllow tests to call just the backup preparation routine without invoking a backup. Useful for transactions where we want to prepare, but don't wish to issue the QMP command.
--- tests/qemu-iotests/124 | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
tests/qemu-iotests/124 +12 -5
@@ -100,7 +100,6 @@ class TestIncrementalBackup(iotests.QMPTestCase): def img_create(self, img, fmt=iotests.imgfmt, size='64M', parent=None, parentFormat=None): - plist = list() if parent: if parentFormat is None: parentFormat = fmt
@@ -140,17 +139,25 @@ class TestIncrementalBackup(iotests.QMPTestCase): return bitmap - def create_incremental(self, bitmap=None, num=None, - parent=None, parentFormat=None, validate=True): + 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(num) + 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) result = self.vm.qmp('drive-backup', device=bitmap.drive['id'], sync='dirty-bitmap', bitmap=bitmap.name, format=bitmap.drive['fmt'], target=target,
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring
Quoted 14 lines · John Snow
Allow tests to call just the backup preparation routine without invoking a backup. Useful for transactions where we want to prepare, but don't wish to issue the QMP command.
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)tests/qemu-iotests/124 +0
-0
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 103
Quoted 4 lines
tests/qemu-iotests/124 +0
-1@@ -100,7 +100,6 @@ class TestIncrementalBackup(iotests.QMPTestCase): def img_create(self, img, fmt=iotests.imgfmt, size='64M', parent=None, parentFormat=None): - plist = list()
Hm. This appears to never have been used. Would it make sense to drop it from "iotests: add invalid input incremental backup tests" in your transactionless series?
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 147
Quoted 13 lines
tests/qemu-iotests/124 +1
-3if parent: if parentFormat is None: parentFormat = fmt@@ -140,17 +139,25 @@ class TestIncrementalBackup(iotests.QMPTestCase): return bitmap - def create_incremental(self, bitmap=None, num=None, - parent=None, parentFormat=None, validate=True): + def prepare_backup(self, bitmap=None, parent=None): if bitmap is None: bitmap = self.bitmaps[-1] -
Removing this empty line looks like it should never have been added, too.
Quoted 10 lines
tests/qemu-iotests/124 +4
-1if parent is None: parent = bitmap.last_target() - target = bitmap.new_target(num) + target = bitmap.new_target() self.img_create(target, bitmap.drive['fmt'], parent=parent) + return target + + def create_incremental(self, bitmap=None, parent=None,
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 163
Quoted 10 lines
tests/qemu-iotests/124 +7
-0+ 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) result = self.vm.qmp('drive-backup', device=bitmap.drive['id'], sync='dirty-bitmap', bitmap=bitmap.name, format=bitmap.drive['fmt'], target=target,
Both "issues" are not caused by this patch, however, so whether you squash the hunks somewhere else or not:
Re: [Qemu-devel] [PATCH 10/11] iotests: 124 - backup_prepare refactoring
Quoted 10 lines · Max Reitz
Quoted 9 lines · John Snow
Allow tests to call just the backup preparation routine without invoking a backup. Useful for transactions where we want to prepare, but don't wish to issue the QMP command.
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
Quoted 13 lines
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 102
Quoted 8 lines
tests/qemu-iotests/124 +0
-1@@ -100,7 +100,6 @@ class TestIncrementalBackup(iotests.QMPTestCase): def img_create(self, img, fmt=iotests.imgfmt, size='64M', parent=None, parentFormat=None): - plist = list()Hm. This appears to never have been used. Would it make sense to drop it from "iotests: add invalid input incremental backup tests" in your transactionless series?
Yes. (Poor kitty.)
Quoted 6 lines
Quoted 6 lines
tests/qemu-iotests/124 +0
-1if parent: if parentFormat is None: parentFormat = fmt@@ -140,17 +139,25 @@ class TestIncrementalBackup(iotests.QMPTestCase): return bitmap - def create_incremental(self, bitmap=None, num=None,
Quoted 9 lines
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 147
Quoted 6 lines
tests/qemu-iotests/124 +0
-1- parent=None, parentFormat=None,validate=True):
tests/qemu-iotests/124 +1
-1+ def prepare_backup(self, bitmap=None, parent=None): if bitmap is None: bitmap = self.bitmaps[-1] -Removing this empty line looks like it should never have been added, too.
Also yes.
Quoted 22 lines
Quoted 8 lines
if parent is None:
parent =bitmap.last_target()tests/qemu-iotests/124 +4
-1- target = bitmap.new_target(num) + target = bitmap.new_target() self.img_create(target, bitmap.drive['fmt'], parent=parent) + return target + + def create_incremental(self, bitmap=None, parent=None,tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 163
Quoted 10 lines
tests/qemu-iotests/124 +7
-0+ 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) result = self.vm.qmp('drive-backup', device=bitmap.drive['id'], sync='dirty-bitmap', bitmap=bitmap.name, format=bitmap.drive['fmt'], target=target,Both "issues" are not caused by this patch, however, so whether you squash the hunks somewhere else or not:
Quoted 2 lines
Reviewed-by Max Reitz <[email protected]>
Thanks!
PATCH 11/11 iotests: 124 - transactional failure test
In reply to John SnowUse a transaction to request an incremental backup across two drives. Coerce one of the jobs to fail, and then re-run the transaction.
Verify that no bitmap data was lost due to the partial transaction failure.
--- tests/qemu-iotests/124 | 119 +++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/124.out | 4 +- 2 files changed, 121 insertions(+), 2 deletions(-)
tests/qemu-iotests/124 +119 -0
@@ -331,6 +331,125 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.create_incremental() + def test_transaction_failure(self): + '''Test: Verify backups made from a transaction that partially fails. + + Add a second drive with its own unique pattern, and add a bitmap to each + drive. Use blkdebug to interfere with the backup on just one drive and + attempt to create a coherent incremental backup across both drives. + + verify a failure in one but not both, then delete the failed stubs and + re-run the same transaction. + + verify that both incrementals are created successfully. + ''' + + # Create a second drive, with pattern: + drive1 = self.add_node('drive1') + self.img_create(drive1['file'], drive1['fmt']) + io_write_patterns(drive1['file'], (('0x14', 0, 512), + ('0x5d', '1M', '32k'), + ('0xcd', '32M', '124k'))) + + # Create a blkdebug interface to this img as 'drive1' + 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', {}) + + # Create bitmaps and full backups for both drives + drive0 = self.drives[0] + dr0bm0 = self.add_bitmap('bitmap0', drive0) + dr1bm0 = self.add_bitmap('bitmap0', drive1) + self.create_full_backup(drive0) + self.create_full_backup(drive1) + self.assert_no_active_block_jobs() + self.assertFalse(self.vm.get_qmp_events(wait=False)) + + # Emulate some writes + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), + ('0xef', '16M', '256k'), + ('0x46', '32736k', '64k'))) + + # Create incremental backup targets + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Ask for a new incremental backup per-each drive, + # expecting drive1's backup to fail: + transaction = [ + { + 'type': 'drive-backup', + 'data': { 'device': drive0['id'], + 'sync': 'dirty-bitmap', + 'format': drive0['fmt'], + 'target': target0, + 'mode': 'existing', + 'bitmap': dr0bm0.name }, + }, + { + 'type': 'drive-backup', + 'data': { 'device': drive1['id'], + 'sync': 'dirty-bitmap', + 'format': drive1['fmt'], + 'target': target1, + 'mode': 'existing', + 'bitmap': dr1bm0.name } + } + ] + result = self.vm.qmp('transaction', actions=transaction) + self.assert_qmp(result, 'return', {}) + + # Observe that drive0's backup completes, but drive1's does not. + # Consume drive1's error and ensure all pending actions are completed. + self.wait_incremental(dr0bm0, validate=True) + self.wait_incremental(dr1bm0, validate=False) + error = self.vm.event_wait('BLOCK_JOB_ERROR') + self.assert_qmp(error, 'data', {'device': drive1['id'], + 'action': 'report', + 'operation': 'read'}) + self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + # Delete drive0's (successful) backup and create two new empty + # targets to re-run the transaction. + dr0bm0.del_target() + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Re-run the exact same transaction. + result = self.vm.qmp('transaction', actions=transaction) + self.assert_qmp(result, 'return', {}) + # Both should complete successfully this time. + self.wait_incremental(dr0bm0, 'drive0') + self.wait_incremental(dr1bm0, 'drive1') + self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() self.files.append(self.err_img)
tests/qemu-iotests/124.out +2 -2
@@ -1,5 +1,5 @@ -..... +...... ---------------------------------------------------------------------- -Ran 5 tests +Ran 6 tests OK
Signature · 1 line
1.9.3
Re: [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test
Quoted 125 lines · John Snow
Use a transaction to request an incremental backup across two drives. Coerce one of the jobs to fail, and then re-run the transaction.
Verify that no bitmap data was lost due to the partial transaction failure.
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 119 +++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/124.out | 4 +- 2 files changed, 121 insertions(+), 2 deletions(-)tests/qemu-iotests/124 +105
-0@@ -331,6 +331,125 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.create_incremental() + def test_transaction_failure(self): + '''Test: Verify backups made from a transaction that partially fails. + + Add a second drive with its own unique pattern, and add a bitmap to each + drive. Use blkdebug to interfere with the backup on just one drive and + attempt to create a coherent incremental backup across both drives. + + verify a failure in one but not both, then delete the failed stubs and + re-run the same transaction. + + verify that both incrementals are created successfully. + ''' + + # Create a second drive, with pattern: + drive1 = self.add_node('drive1') + self.img_create(drive1['file'], drive1['fmt']) + io_write_patterns(drive1['file'], (('0x14', 0, 512), + ('0x5d', '1M', '32k'), + ('0xcd', '32M', '124k'))) + + # Create a blkdebug interface to this img as 'drive1' + 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', {}) + + # Create bitmaps and full backups for both drives + drive0 = self.drives[0] + dr0bm0 = self.add_bitmap('bitmap0', drive0) + dr1bm0 = self.add_bitmap('bitmap0', drive1) + self.create_full_backup(drive0) + self.create_full_backup(drive1) + self.assert_no_active_block_jobs() + self.assertFalse(self.vm.get_qmp_events(wait=False)) + + # Emulate some writes + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), + ('0xef', '16M', '256k'), + ('0x46', '32736k', '64k'))) + + # Create incremental backup targets + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Ask for a new incremental backup per-each drive, + # expecting drive1's backup to fail: + transaction = [ + { + 'type': 'drive-backup', + 'data': { 'device': drive0['id'], + 'sync': 'dirty-bitmap', + 'format': drive0['fmt'], + 'target': target0, + 'mode': 'existing', + 'bitmap': dr0bm0.name }, + }, + { + 'type': 'drive-backup', + 'data': { 'device': drive1['id'], + 'sync': 'dirty-bitmap', + 'format': drive1['fmt'], + 'target': target1, + 'mode': 'existing', + 'bitmap': dr1bm0.name } + } + ] + result = self.vm.qmp('transaction', actions=transaction) + self.assert_qmp(result, 'return', {}) + + # Observe that drive0's backup completes, but drive1's does not. + # Consume drive1's error and ensure all pending actions are completed. + self.wait_incremental(dr0bm0, validate=True) + self.wait_incremental(dr1bm0, validate=False) + error = self.vm.event_wait('BLOCK_JOB_ERROR') + self.assert_qmp(error, 'data', {'device': drive1['id'], + 'action': 'report', + 'operation': 'read'}) + self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + # Delete drive0's (successful) backup and create two new empty + # targets to re-run the transaction.
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 448
Quoted 10 lines
tests/qemu-iotests/124 +10
-0+ dr0bm0.del_target() + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Re-run the exact same transaction. + result = self.vm.qmp('transaction', actions=transaction) + self.assert_qmp(result, 'return', {}) + # Both should complete successfully this time. + self.wait_incremental(dr0bm0, 'drive0') + self.wait_incremental(dr1bm0, 'drive1')
s/'drive.'/validate=True/, I think (will lead to the same result, though, yay for dynamically typed languages).
With that fixed:
Nice!
Quoted 19 lines
tests/qemu-iotests/124 +4
-0+ self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() self.files.append(self.err_img)tests/qemu-iotests/124.out +2
-2@@ -1,5 +1,5 @@ -..... +...... ---------------------------------------------------------------------- -Ran 5 tests +Ran 6 tests OK
Re: [Qemu-devel] [PATCH 11/11] iotests: 124 - transactional failure test
Quoted 136 lines · Max Reitz
Quoted 135 lines · John Snow
Use a transaction to request an incremental backup across two drives. Coerce one of the jobs to fail, and then re-run the transaction.
Verify that no bitmap data was lost due to the partial transaction failure.
Signed-off-by John Snow <[email protected]>--- tests/qemu-iotests/124 | 119 +++++++++++++++++++++++++++++++++++++++++++++ tests/qemu-iotests/124.out | 4 +- 2 files changed, 121 insertions(+), 2 deletions(-)tests/qemu-iotests/124 +2
-0@@ -331,6 +331,125 @@ class TestIncrementalBackup(iotests.QMPTestCase): self.create_incremental() + def test_transaction_failure(self): + '''Test: Verify backups made from a transaction thatpartially fails.
tests/qemu-iotests/124 +2
-0+ + Add a second drive with its own unique pattern, and add abitmap to each
+ drive. Use blkdebug to interfere with the backup on just one
drive and
+ attempt to create a coherent incremental backup across both
drives.tests/qemu-iotests/124 +2
-0+ + verify a failure in one but not both, then delete the failedstubs and
tests/qemu-iotests/124 +86
-0+ re-run the same transaction. + + verify that both incrementals are created successfully. + ''' + + # Create a second drive, with pattern: + drive1 = self.add_node('drive1') + self.img_create(drive1['file'], drive1['fmt']) + io_write_patterns(drive1['file'], (('0x14', 0, 512), + ('0x5d', '1M', '32k'), + ('0xcd', '32M', '124k'))) + + # Create a blkdebug interface to this img as 'drive1' + 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', {}) + + # Create bitmaps and full backups for both drives + drive0 = self.drives[0] + dr0bm0 = self.add_bitmap('bitmap0', drive0) + dr1bm0 = self.add_bitmap('bitmap0', drive1) + self.create_full_backup(drive0) + self.create_full_backup(drive1) + self.assert_no_active_block_jobs() + self.assertFalse(self.vm.get_qmp_events(wait=False)) + + # Emulate some writes + self.hmp_io_writes(drive0['id'], (('0xab', 0, 512), + ('0xfe', '16M', '256k'), + ('0x64', '32736k', '64k'))) + self.hmp_io_writes(drive1['id'], (('0xba', 0, 512), + ('0xef', '16M', '256k'), + ('0x46', '32736k', '64k'))) + + # Create incremental backup targets + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Ask for a new incremental backup per-each drive, + # expecting drive1's backup to fail: + transaction = [ + { + 'type': 'drive-backup', + 'data': { 'device': drive0['id'], + 'sync': 'dirty-bitmap', + 'format': drive0['fmt'], + 'target': target0, + 'mode': 'existing', + 'bitmap': dr0bm0.name }, + }, + { + 'type': 'drive-backup', + 'data': { 'device': drive1['id'], + 'sync': 'dirty-bitmap', + 'format': drive1['fmt'], + 'target': target1, + 'mode': 'existing', + 'bitmap': dr1bm0.name } + } + ] + result = self.vm.qmp('transaction', actions=transaction) + self.assert_qmp(result, 'return', {}) + + # Observe that drive0's backup completes, but drive1's does not. + # Consume drive1's error and ensure all pending actions arecompleted.
tests/qemu-iotests/124 +16
-0+ self.wait_incremental(dr0bm0, validate=True) + self.wait_incremental(dr1bm0, validate=False) + error = self.vm.event_wait('BLOCK_JOB_ERROR') + self.assert_qmp(error, 'data', {'device': drive1['id'], + 'action': 'report', + 'operation': 'read'}) + self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + # Delete drive0's (successful) backup and create two new empty + # targets to re-run the transaction. + dr0bm0.del_target() + target0 = self.prepare_backup(dr0bm0) + target1 = self.prepare_backup(dr1bm0) + + # Re-run the exact same transaction.
Quoted 9 lines
tests/qemu-iotests/124 class TestIncrementalBackup(iotests.QMPTestCase): · line 448
Quoted 5 lines
tests/qemu-iotests/124 +5
-0+ result = self.vm.qmp('transaction', actions=transaction) + self.assert_qmp(result, 'return', {}) + # Both should complete successfully this time. + self.wait_incremental(dr0bm0, 'drive0') + self.wait_incremental(dr1bm0, 'drive1')s/'drive.'/validate=True/, I think (will lead to the same result, though, yay for dynamically typed languages).
They really let you get away with murder sometimes. Thanks for the catch.
Quoted 26 lines
With that fixed:
Reviewed-by Max Reitz <[email protected]>Nice!
Quoted 18 lines
tests/qemu-iotests/124 +4
-0+ self.assertFalse(self.vm.get_qmp_events(wait=False)) + self.assert_no_active_block_jobs() + + def test_sync_dirty_bitmap_missing(self): self.assert_no_active_block_jobs() self.files.append(self.err_img)tests/qemu-iotests/124.out +2
-2@@ -1,5 +1,5 @@ -..... +...... ---------------------------------------------------------------------- -Ran 5 tests +Ran 6 tests OK
Signature · 1 line
—js