[PATCH v3 00/11] blockdev-replace

Hi all! Finally, that's a proposal for new interface for filter insertion, which provides generic way for inserting between different block graph nodes, like BDS nodes, block exports and block devices. v3: - add transaction support - add test, that shows transactional filter insertion in different cases - drop RFC mark. I think it's now close to be a good solution. And anyway, no comments on "RFC v2" version :) Still, I want to keep x- prefix for now, just because there were too many different ideas on this topic. Vladimir Sementsov-Ogievskiy (11): block-backend: blk_root(): drop const specifier on return type block/export: add blk_by_export_id() block: make bdrv_find_child() function public block: bdrv_replace_child_bs(): move to external transaction qapi: add x-blockdev-replace command qapi: add x-blockdev-replace transaction action block: bdrv_get_xdbg_block_graph(): report export ids iotests.py: qemu_img_create: use imgfmt by default iotests.py: introduce VM.assert_edges_list() method iotests.py: add VM.qmp_check() helper iotests: add filter-insertion qapi/block-core.json | 62 +++++ qapi/transaction.json | 14 +- include/block/block.h | 2 +- include/block/block_int.h | 1 + include/block/export.h | 1 + include/sysemu/block-backend.h | 3 +- block.c | 59 ++-- block/block-backend.c | 10 +- block/export/export.c | 31 +++ blockdev.c | 113 +++++++- stubs/blk-by-qdev-id.c | 9 + stubs/blk-exp-find-by-blk.c | 9 + stubs/meson.build | 2 + tests/qemu-iotests/iotests.py | 23 ++ tests/qemu-iotests/tests/filter-insertion | 253 ++++++++++++++++++ tests/qemu-iotests/tests/filter-insertion.out | 5 + 16 files changed, 563 insertions(+), 34 deletions(-) create mode 100644 stubs/blk-by-qdev-id.c create mode 100644 stubs/blk-exp-find-by-blk.c create mode 100755 tests/qemu-iotests/tests/filter-insertion create mode 100644 tests/qemu-iotests/tests/filter-insertion.out -- 2.31.1

We'll need get non-const child pointer for graph modifications in further commits. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- include/sysemu/block-backend.h | 2 +- block/block-backend.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h index e5e1524f06..904d70f49c 100644 --- a/include/sysemu/block-backend.h +++ b/include/sysemu/block-backend.h @@ -277,7 +277,7 @@ int coroutine_fn blk_co_copy_range(BlockBackend *blk_in, int64_t off_in, int64_t bytes, BdrvRequestFlags read_flags, BdrvRequestFlags write_flags); -const BdrvChild *blk_root(BlockBackend *blk); +BdrvChild *blk_root(BlockBackend *blk); int blk_make_empty(BlockBackend *blk, Error **errp); diff --git a/block/block-backend.c b/block/block-backend.c index 4ff6b4d785..97913acfcd 100644 --- a/block/block-backend.c +++ b/block/block-backend.c @@ -2464,7 +2464,7 @@ int coroutine_fn blk_co_copy_range(BlockBackend *blk_in, int64_t off_in, bytes, read_flags, write_flags); } -const BdrvChild *blk_root(BlockBackend *blk) +BdrvChild *blk_root(BlockBackend *blk) { return blk->root; } -- 2.31.1

Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- include/sysemu/block-backend.h | 1 + block/export/export.c | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/include/sysemu/block-backend.h b/include/sysemu/block-backend.h index 904d70f49c..250c7465a5 100644 --- a/include/sysemu/block-backend.h +++ b/include/sysemu/block-backend.h @@ -124,6 +124,7 @@ DeviceState *blk_get_attached_dev(BlockBackend *blk); char *blk_get_attached_dev_id(BlockBackend *blk); BlockBackend *blk_by_dev(void *dev); BlockBackend *blk_by_qdev_id(const char *id, Error **errp); +BlockBackend *blk_by_export_id(const char *id, Error **errp); void blk_set_dev_ops(BlockBackend *blk, const BlockDevOps *ops, void *opaque); int coroutine_fn blk_co_preadv(BlockBackend *blk, int64_t offset, int64_t bytes, QEMUIOVector *qiov, diff --git a/block/export/export.c b/block/export/export.c index 6d3b9964c8..613b5bc1d5 100644 --- a/block/export/export.c +++ b/block/export/export.c @@ -362,3 +362,21 @@ BlockExportInfoList *qmp_query_block_exports(Error **errp) return head; } + +BlockBackend *blk_by_export_id(const char *id, Error **errp) +{ + BlockExport *exp; + + exp = blk_exp_find(id); + if (exp == NULL) { + error_setg(errp, "Export '%s' not found", id); + return NULL; + } + + if (!exp->blk) { + error_setg(errp, "Export '%s' is empty", id); + return NULL; + } + + return exp->blk; +} -- 2.31.1

To be reused soon. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- include/block/block_int.h | 1 + block.c | 13 +++++++++++++ blockdev.c | 14 -------------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/block/block_int.h b/include/block/block_int.h index 27008cfb22..e44348e851 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -1430,6 +1430,7 @@ BdrvDirtyBitmap *block_dirty_bitmap_remove(const char *node, const char *name, BlockDriverState **bitmap_bs, Error **errp); +BdrvChild *bdrv_find_child(BlockDriverState *parent_bs, const char *child_name); BdrvChild *bdrv_cow_child(BlockDriverState *bs); BdrvChild *bdrv_filter_child(BlockDriverState *bs); BdrvChild *bdrv_filter_or_cow_child(BlockDriverState *bs); diff --git a/block.c b/block.c index b54d59d1fa..601fee163b 100644 --- a/block.c +++ b/block.c @@ -7728,6 +7728,19 @@ int bdrv_make_empty(BdrvChild *c, Error **errp) return 0; } +BdrvChild *bdrv_find_child(BlockDriverState *parent_bs, const char *child_name) +{ + BdrvChild *child; + + QLIST_FOREACH(child, &parent_bs->children, next) { + if (strcmp(child->name, child_name) == 0) { + return child; + } + } + + return NULL; +} + /* * Return the child that @bs acts as an overlay for, and from which data may be * copied in COW or COR operations. Usually this is the backing file. diff --git a/blockdev.c b/blockdev.c index eb9ad9cb89..d20963be2a 100644 --- a/blockdev.c +++ b/blockdev.c @@ -3661,20 +3661,6 @@ out: aio_context_release(aio_context); } -static BdrvChild *bdrv_find_child(BlockDriverState *parent_bs, - const char *child_name) -{ - BdrvChild *child; - - QLIST_FOREACH(child, &parent_bs->children, next) { - if (strcmp(child->name, child_name) == 0) { - return child; - } - } - - return NULL; -} - void qmp_x_blockdev_change(const char *parent, bool has_child, const char *child, bool has_node, const char *node, Error **errp) -- 2.31.1

We'll need this functionality as part of external transaction, so make the whole function to be transaction action. For this we need to introduce a transaction action helper: bdrv_drained(), which calls bdrv_drained_begin() and postpone bdrv_drained_end() to .clean() phase. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- include/block/block.h | 2 +- block.c | 42 +++++++++++++++++++++++++++--------------- block/block-backend.c | 8 +++++++- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/include/block/block.h b/include/block/block.h index e1713ee306..1cc1f736c7 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -362,7 +362,7 @@ int bdrv_append(BlockDriverState *bs_new, BlockDriverState *bs_top, int bdrv_replace_node(BlockDriverState *from, BlockDriverState *to, Error **errp); int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs, - Error **errp); + Transaction *tran, Error **errp); BlockDriverState *bdrv_insert_node(BlockDriverState *bs, QDict *node_options, int flags, Error **errp); int bdrv_drop_filter(BlockDriverState *bs, Error **errp); diff --git a/block.c b/block.c index 601fee163b..b2f55ff872 100644 --- a/block.c +++ b/block.c @@ -5204,19 +5204,39 @@ out: return ret; } +static void bdrv_drained_clean(void *opaque) +{ + BlockDriverState *bs = opaque; + + bdrv_drained_end(bs); + bdrv_unref(bs); +} + +TransactionActionDrv bdrv_drained_drv = { + .clean = bdrv_drained_clean, +}; + +/* + * Start drained section on @bs, and finish it in .clean action. + * Reference to @bs is kept, so @bs can't be removed during transaction. + */ +static void bdrv_drained(BlockDriverState *bs, Transaction *tran) +{ + bdrv_ref(bs); + bdrv_drained_begin(bs); + tran_add(tran, &bdrv_drained_drv, bs); +} + /* Not for empty child */ int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs, - Error **errp) + Transaction *tran, Error **errp) { - int ret; - Transaction *tran = tran_new(); g_autoptr(GHashTable) found = NULL; g_autoptr(GSList) refresh_list = NULL; BlockDriverState *old_bs = child->bs; - bdrv_ref(old_bs); - bdrv_drained_begin(old_bs); - bdrv_drained_begin(new_bs); + bdrv_drained(old_bs, tran); + bdrv_drained(new_bs, tran); bdrv_replace_child_tran(&child, new_bs, tran, true); /* @new_bs must have been non-NULL, so @child must not have been freed */ @@ -5226,15 +5246,7 @@ int bdrv_replace_child_bs(BdrvChild *child, BlockDriverState *new_bs, refresh_list = bdrv_topological_dfs(refresh_list, found, old_bs); refresh_list = bdrv_topological_dfs(refresh_list, found, new_bs); - ret = bdrv_list_refresh_perms(refresh_list, NULL, tran, errp); - - tran_finalize(tran, ret); - - bdrv_drained_end(old_bs); - bdrv_drained_end(new_bs); - bdrv_unref(old_bs); - - return ret; + return bdrv_list_refresh_perms(refresh_list, NULL, tran, errp); } static void bdrv_delete(BlockDriverState *bs) diff --git a/block/block-backend.c b/block/block-backend.c index 97913acfcd..dbbbc56b2c 100644 --- a/block/block-backend.c +++ b/block/block-backend.c @@ -892,7 +892,13 @@ int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp) */ int blk_replace_bs(BlockBackend *blk, BlockDriverState *new_bs, Error **errp) { - return bdrv_replace_child_bs(blk->root, new_bs, errp); + int ret; + Transaction *tran = tran_new(); + + ret = bdrv_replace_child_bs(blk->root, new_bs, tran, errp); + tran_finalize(tran, ret); + + return ret; } /* -- 2.31.1

Add a command that can replace bs in following BdrvChild structures: - qdev blk root child - block-export blk root child - any child BlockDriverState selected by child-name Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- qapi/block-core.json | 62 ++++++++++++++++++++++++++++++++++++++++ blockdev.c | 65 ++++++++++++++++++++++++++++++++++++++++++ stubs/blk-by-qdev-id.c | 9 ++++++ stubs/meson.build | 1 + 4 files changed, 137 insertions(+) create mode 100644 stubs/blk-by-qdev-id.c diff --git a/qapi/block-core.json b/qapi/block-core.json index 9a5a3641d0..f760dc21f5 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -5574,3 +5574,65 @@ { 'command': 'blockdev-snapshot-delete-internal-sync', 'data': { 'device': 'str', '*id': 'str', '*name': 'str'}, 'returns': 'SnapshotInfo' } + +## +# @BlockParentType: +# +# Since 7.0 +## +{ 'enum': 'BlockParentType', + 'data': ['qdev', 'driver', 'export'] } + +## +# @BdrvChildRefQdev: +# +# Since 7.0 +## +{ 'struct': 'BdrvChildRefQdev', + 'data': { 'qdev-id': 'str' } } + +## +# @BdrvChildRefExport: +# +# Since 7.0 +## +{ 'struct': 'BdrvChildRefExport', + 'data': { 'export-id': 'str' } } + +## +# @BdrvChildRefDriver: +# +# Since 7.0 +## +{ 'struct': 'BdrvChildRefDriver', + 'data': { 'node-name': 'str', 'child': 'str' } } + +## +# @BlockdevReplace: +# +# Since 7.0 +## +{ 'union': 'BlockdevReplace', + 'base': { + 'parent-type': 'BlockParentType', + 'new-child': 'str' + }, + 'discriminator': 'parent-type', + 'data': { + 'qdev': 'BdrvChildRefQdev', + 'export': 'BdrvChildRefExport', + 'driver': 'BdrvChildRefDriver' + } } + +## +# @x-blockdev-replace: +# +# Replace a block-node associated with device (selected by +# @qdev-id) or with block-export (selected by @export-id) or +# any child of block-node (selected by @node-name and @child) +# with @new-child block-node. +# +# Since 7.0 +## +{ 'command': 'x-blockdev-replace', 'boxed': true, + 'data': 'BlockdevReplace' } diff --git a/blockdev.c b/blockdev.c index d20963be2a..9fd1783be2 100644 --- a/blockdev.c +++ b/blockdev.c @@ -2229,6 +2229,71 @@ static void blockdev_add_abort(BlkActionState *common) bdrv_unref(s->bs); } +static int blockdev_replace(BlockdevReplace *repl, Transaction *tran, + Error **errp) +{ + BdrvChild *child = NULL; + BlockDriverState *new_child_bs; + + if (repl->parent_type == BLOCK_PARENT_TYPE_DRIVER) { + BlockDriverState *parent_bs; + + parent_bs = bdrv_find_node(repl->u.driver.node_name); + if (!parent_bs) { + error_setg(errp, "Block driver node with node-name '%s' not " + "found", repl->u.driver.node_name); + return -EINVAL; + } + + child = bdrv_find_child(parent_bs, repl->u.driver.child); + if (!child) { + error_setg(errp, "Block driver node '%s' doesn't have child " + "named '%s'", repl->u.driver.node_name, + repl->u.driver.child); + return -EINVAL; + } + } else { + /* Other types are similar, they work through blk */ + BlockBackend *blk; + bool is_qdev = repl->parent_type == BLOCK_PARENT_TYPE_QDEV; + const char *id = + is_qdev ? repl->u.qdev.qdev_id : repl->u.export.export_id; + + assert(is_qdev || repl->parent_type == BLOCK_PARENT_TYPE_EXPORT); + + blk = is_qdev ? blk_by_qdev_id(id, errp) : blk_by_export_id(id, errp); + if (!blk) { + return -EINVAL; + } + + child = blk_root(blk); + if (!child) { + error_setg(errp, "%s '%s' is empty, nothing to replace", + is_qdev ? "Device" : "Export", id); + return -EINVAL; + } + } + + assert(child); + assert(child->bs); + + new_child_bs = bdrv_find_node(repl->new_child); + if (!new_child_bs) { + error_setg(errp, "Node '%s' not found", repl->new_child); + return -EINVAL; + } + + return bdrv_replace_child_bs(child, new_child_bs, tran, errp); +} + +void qmp_x_blockdev_replace(BlockdevReplace *repl, Error **errp) +{ + Transaction *tran = tran_new(); + int ret = blockdev_replace(repl, tran, errp); + + tran_finalize(tran, ret); +} + static const BlkActionOps actions[] = { [TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT] = { .instance_size = sizeof(ExternalSnapshotState), diff --git a/stubs/blk-by-qdev-id.c b/stubs/blk-by-qdev-id.c new file mode 100644 index 0000000000..0e751ce4f7 --- /dev/null +++ b/stubs/blk-by-qdev-id.c @@ -0,0 +1,9 @@ +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "sysemu/block-backend.h" + +BlockBackend *blk_by_qdev_id(const char *id, Error **errp) +{ + error_setg(errp, "blk '%s' not found", id); + return NULL; +} diff --git a/stubs/meson.build b/stubs/meson.build index d359cbe1ad..90358823fc 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -1,6 +1,7 @@ stub_ss.add(files('bdrv-next-monitor-owned.c')) stub_ss.add(files('blk-commit-all.c')) stub_ss.add(files('blk-exp-close-all.c')) +stub_ss.add(files('blk-by-qdev-id.c')) stub_ss.add(files('blockdev-close-all-bdrv-states.c')) stub_ss.add(files('change-state-handler.c')) stub_ss.add(files('cmos.c')) -- 2.31.1

Support blockdev-replace in a transaction. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- qapi/transaction.json | 14 +++++++++++++- blockdev.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/qapi/transaction.json b/qapi/transaction.json index a938dc7d10..48dd2db1ed 100644 --- a/qapi/transaction.json +++ b/qapi/transaction.json @@ -54,10 +54,12 @@ # @blockdev-snapshot-sync: since 1.1 # @drive-backup: Since 1.6 # @blockdev-add: since 7.0 +# @x-blockdev-replace: since 7.0 # # Features: # @deprecated: Member @drive-backup is deprecated. Use member # @blockdev-backup instead. +# @unstable: Member @x-blockdev-replace is experimental # # Since: 1.1 ## @@ -68,6 +70,7 @@ 'blockdev-backup', 'blockdev-snapshot', 'blockdev-snapshot-internal-sync', 'blockdev-snapshot-sync', 'blockdev-add', + { 'name': 'x-blockdev-replace', 'features': [ 'unstable' ] }, { 'name': 'drive-backup', 'features': [ 'deprecated' ] } ] } ## @@ -150,6 +153,14 @@ { 'struct': 'BlockdevAddWrapper', 'data': { 'data': 'BlockdevOptions' } } +## +# @BlockdevReplaceWrapper: +# +# Since: 7.0 +## +{ 'struct': 'BlockdevReplaceWrapper', + 'data': { 'data': 'BlockdevReplace' } } + ## # @TransactionAction: # @@ -174,7 +185,8 @@ 'blockdev-snapshot-internal-sync': 'BlockdevSnapshotInternalWrapper', 'blockdev-snapshot-sync': 'BlockdevSnapshotSyncWrapper', 'blockdev-add': 'BlockdevAddWrapper', - 'drive-backup': 'DriveBackupWrapper' + 'drive-backup': 'DriveBackupWrapper', + 'x-blockdev-replace': 'BlockdevReplaceWrapper' } } ## diff --git a/blockdev.c b/blockdev.c index 9fd1783be2..8ff0e2afe8 100644 --- a/blockdev.c +++ b/blockdev.c @@ -2294,6 +2294,34 @@ void qmp_x_blockdev_replace(BlockdevReplace *repl, Error **errp) tran_finalize(tran, ret); } +typedef struct TranObjState { + BlkActionState common; + Transaction *tran; +} TranObjState; + +static void tran_obj_commit(BlkActionState *common) +{ + TranObjState *s = DO_UPCAST(TranObjState, common, common); + + tran_commit(s->tran); +} + +static void tran_obj_abort(BlkActionState *common) +{ + TranObjState *s = DO_UPCAST(TranObjState, common, common); + + tran_abort(s->tran); +} + +static void blockdev_replace_prepare(BlkActionState *common, Error **errp) +{ + TranObjState *s = DO_UPCAST(TranObjState, common, common); + + s->tran = tran_new(); + + blockdev_replace(common->action->u.x_blockdev_replace.data, s->tran, errp); +} + static const BlkActionOps actions[] = { [TRANSACTION_ACTION_KIND_BLOCKDEV_SNAPSHOT] = { .instance_size = sizeof(ExternalSnapshotState), @@ -2372,6 +2400,12 @@ static const BlkActionOps actions[] = { .prepare = blockdev_add_prepare, .abort = blockdev_add_abort, }, + [TRANSACTION_ACTION_KIND_X_BLOCKDEV_REPLACE] = { + .instance_size = sizeof(TranObjState), + .prepare = blockdev_replace_prepare, + .commit = tran_obj_commit, + .abort = tran_obj_abort, + }, /* Where are transactions for MIRROR, COMMIT and STREAM? * Although these blockjobs use transaction callbacks like the backup job, * these jobs do not necessarily adhere to transaction semantics. -- 2.31.1

Currently for block exports we report empty blk names. That's not good. Let's try to find corresponding block export and report its id. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- include/block/export.h | 1 + block.c | 4 ++++ block/export/export.c | 13 +++++++++++++ stubs/blk-exp-find-by-blk.c | 9 +++++++++ stubs/meson.build | 1 + 5 files changed, 28 insertions(+) create mode 100644 stubs/blk-exp-find-by-blk.c diff --git a/include/block/export.h b/include/block/export.h index 7feb02e10d..172c180819 100644 --- a/include/block/export.h +++ b/include/block/export.h @@ -80,6 +80,7 @@ struct BlockExport { BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp); BlockExport *blk_exp_find(const char *id); +BlockExport *blk_exp_find_by_blk(BlockBackend *blk); void blk_exp_ref(BlockExport *exp); void blk_exp_unref(BlockExport *exp); void blk_exp_request_shutdown(BlockExport *exp); diff --git a/block.c b/block.c index b2f55ff872..24baf58e80 100644 --- a/block.c +++ b/block.c @@ -5979,7 +5979,11 @@ XDbgBlockGraph *bdrv_get_xdbg_block_graph(Error **errp) for (blk = blk_all_next(NULL); blk; blk = blk_all_next(blk)) { char *allocated_name = NULL; const char *name = blk_name(blk); + BlockExport *exp = blk_exp_find_by_blk(blk); + if (!*name && exp) { + name = exp->id; + } if (!*name) { name = allocated_name = blk_get_attached_dev_id(blk); } diff --git a/block/export/export.c b/block/export/export.c index 613b5bc1d5..ca6c8969ca 100644 --- a/block/export/export.c +++ b/block/export/export.c @@ -54,6 +54,19 @@ BlockExport *blk_exp_find(const char *id) return NULL; } +BlockExport *blk_exp_find_by_blk(BlockBackend *blk) +{ + BlockExport *exp; + + QLIST_FOREACH(exp, &block_exports, next) { + if (exp->blk == blk) { + return exp; + } + } + + return NULL; +} + static const BlockExportDriver *blk_exp_find_driver(BlockExportType type) { int i; diff --git a/stubs/blk-exp-find-by-blk.c b/stubs/blk-exp-find-by-blk.c new file mode 100644 index 0000000000..2fc1da953b --- /dev/null +++ b/stubs/blk-exp-find-by-blk.c @@ -0,0 +1,9 @@ +#include "qemu/osdep.h" +#include "sysemu/block-backend.h" +#include "block/export.h" + +BlockExport *blk_exp_find_by_blk(BlockBackend *blk) +{ + return NULL; +} + diff --git a/stubs/meson.build b/stubs/meson.build index 90358823fc..92e362a45e 100644 --- a/stubs/meson.build +++ b/stubs/meson.build @@ -2,6 +2,7 @@ stub_ss.add(files('bdrv-next-monitor-owned.c')) stub_ss.add(files('blk-commit-all.c')) stub_ss.add(files('blk-exp-close-all.c')) stub_ss.add(files('blk-by-qdev-id.c')) +stub_ss.add(files('blk-exp-find-by-blk.c')) stub_ss.add(files('blockdev-close-all-bdrv-states.c')) stub_ss.add(files('change-state-handler.c')) stub_ss.add(files('cmos.c')) -- 2.31.1

Less typing: let's use imgfmt by default if user doesn't specify neither -f nor --image-opts. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- tests/qemu-iotests/iotests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 6ba65eb1ff..ca17a5c64c 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -233,6 +233,8 @@ def ordered_qmp(qmsg, conv_keys=True): return qmsg def qemu_img_create(*args): + if '-f' not in args and '--image-opts' not in args: + args = ['-f', imgfmt] + list(args) return qemu_img('create', *args) def qemu_img_measure(*args): -- 2.31.1

Add an alternative method to check block graph, to be used in further commit. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- tests/qemu-iotests/iotests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index ca17a5c64c..1b48c5b9c9 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -960,6 +960,23 @@ def check_bitmap_status(self, node_name, bitmap_name, fields): return fields.items() <= ret.items() + def get_block_graph(self): + """ + Returns block graph in form of edges list, where each edge is a tuple: + (parent_node_name, child_name, child_node_name) + """ + graph = self.qmp('x-debug-query-block-graph')['return'] + + nodes = {n['id']: n['name'] for n in graph['nodes']} + # Check that all names are unique: + assert len(set(nodes.values())) == len(nodes) + + return [(nodes[e['parent']], e['name'], nodes[e['child']]) + for e in graph['edges']] + + def assert_edges_list(self, edges): + assert sorted(edges) == sorted(self.get_block_graph()) + def assert_block_path(self, root, path, expected_node, graph=None): """ Check whether the node under the given path in the block graph -- 2.31.1

I'm tired of this pattern being everywhere. Let's add a helper. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- tests/qemu-iotests/iotests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py index 1b48c5b9c9..dd33970454 100644 --- a/tests/qemu-iotests/iotests.py +++ b/tests/qemu-iotests/iotests.py @@ -977,6 +977,10 @@ def get_block_graph(self): def assert_edges_list(self, edges): assert sorted(edges) == sorted(self.get_block_graph()) + def qmp_check(self, *args, **kwargs): + result = self.qmp(*args, **kwargs) + assert result == {'return': {}} + def assert_block_path(self, root, path, expected_node, graph=None): """ Check whether the node under the given path in the block graph -- 2.31.1

Demonstrate new API for filter insertion and removal. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> --- tests/qemu-iotests/tests/filter-insertion | 253 ++++++++++++++++++ tests/qemu-iotests/tests/filter-insertion.out | 5 + 2 files changed, 258 insertions(+) create mode 100755 tests/qemu-iotests/tests/filter-insertion create mode 100644 tests/qemu-iotests/tests/filter-insertion.out diff --git a/tests/qemu-iotests/tests/filter-insertion b/tests/qemu-iotests/tests/filter-insertion new file mode 100755 index 0000000000..4898d6e043 --- /dev/null +++ b/tests/qemu-iotests/tests/filter-insertion @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +# +# Tests for inserting and removing filters in a block graph. +# +# Copyright (c) 2022 Virtuozzo International GmbH. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import os + +import iotests +from iotests import qemu_img_create, try_remove + + +disk = os.path.join(iotests.test_dir, 'disk') +sock = os.path.join(iotests.sock_dir, 'sock') +size = 1024 * 1024 + + +class TestFilterInsertion(iotests.QMPTestCase): + def setUp(self): + qemu_img_create(disk, str(size)) + + self.vm = iotests.VM() + self.vm.launch() + + self.vm.qmp_check('blockdev-add', { + 'node-name': 'disk0', + 'driver': 'qcow2', + 'file': { + 'node-name': 'file0', + 'driver': 'file', + 'filename': disk + } + }) + + def tearDown(self): + self.vm.shutdown() + os.remove(disk) + try_remove(sock) + + def test_simple_insertion(self): + vm = self.vm + + vm.qmp_check('blockdev-add', { + 'node-name': 'filter', + 'driver': 'preallocate', + 'file': 'file0' + }) + + vm.qmp_check('x-blockdev-replace', { + 'parent-type': 'driver', + 'node-name': 'disk0', + 'child': 'file', + 'new-child': 'filter' + }) + + # Filter inserted: + # disk0 -file-> filter -file-> file0 + vm.assert_edges_list([ + ('disk0', 'file', 'filter'), + ('filter', 'file', 'file0') + ]) + + vm.qmp_check('x-blockdev-replace', { + 'parent-type': 'driver', + 'node-name': 'disk0', + 'child': 'file', + 'new-child': 'file0' + }) + + # Filter replaced, but still exists: + # dik0 -file-> file0 <-file- filter + vm.assert_edges_list([ + ('disk0', 'file', 'file0'), + ('filter', 'file', 'file0') + ]) + + vm.qmp_check('blockdev-del', node_name='filter') + + # Filter removed + # dik0 -file-> file0 + vm.assert_edges_list([ + ('disk0', 'file', 'file0') + ]) + + def test_tran_insert_under_qdev(self): + vm = self.vm + + vm.qmp_check('device_add', driver='virtio-scsi') + vm.qmp_check('device_add', id='sda', driver='scsi-hd', drive='disk0') + + vm.qmp_check('transaction', actions=[ + { + 'type': 'blockdev-add', + 'data': { + 'node-name': 'filter', + 'driver': 'compress', + 'file': 'disk0' + } + }, { + 'type': 'x-blockdev-replace', + 'data': { + 'parent-type': 'qdev', + 'qdev-id': 'sda', + 'new-child': 'filter' + } + } + ]) + + # Filter inserted: + # sda -root-> filter -file-> disk0 -file-> file0 + vm.assert_edges_list([ + # parent_node_name, child_name, child_node_name + ('sda', 'root', 'filter'), + ('filter', 'file', 'disk0'), + ('disk0', 'file', 'file0'), + ]) + + vm.qmp_check('x-blockdev-replace', { + 'parent-type': 'qdev', + 'qdev-id': 'sda', + 'new-child': 'disk0' + }) + vm.qmp_check('blockdev-del', node_name='filter') + + # Filter removed: + # sda -root-> disk0 -file-> file0 + vm.assert_edges_list([ + # parent_node_name, child_name, child_node_name + ('sda', 'root', 'disk0'), + ('disk0', 'file', 'file0'), + ]) + + def test_tran_insert_under_nbd_export(self): + vm = self.vm + + vm.qmp_check('nbd-server-start', + addr={'type': 'unix', 'data': {'path': sock}}) + vm.qmp_check('block-export-add', id='exp1', type='nbd', + node_name='disk0', name='exp1') + vm.qmp_check('block-export-add', id='exp2', type='nbd', + node_name='disk0', name='exp2') + vm.qmp_check('object-add', qom_type='throttle-group', + id='tg', limits={'iops-read': 1}) + + vm.qmp_check('transaction', actions=[ + { + 'type': 'blockdev-add', + 'data': { + 'node-name': 'filter', + 'driver': 'throttle', + 'throttle-group': 'tg', + 'file': 'disk0' + } + }, { + 'type': 'x-blockdev-replace', + 'data': { + 'parent-type': 'export', + 'export-id': 'exp1', + 'new-child': 'filter' + } + } + ]) + + # Only exp1 is throttled, exp2 is not: + # exp1 -root-> filter + # | + # |file + # v + # exp2 -file-> disk0 -file> file0 + vm.assert_edges_list([ + # parent_node_name, child_name, child_node_name + ('exp1', 'root', 'filter'), + ('filter', 'file', 'disk0'), + ('disk0', 'file', 'file0'), + ('exp2', 'root', 'disk0') + ]) + + vm.qmp_check('x-blockdev-replace', { + 'parent-type': 'export', + 'export-id': 'exp2', + 'new-child': 'filter' + }) + + # Both throttled: + # exp1 -root-> filter <-file- exp2 + # | + # |file + # v + # disk0 -file> file0 + vm.assert_edges_list([ + # parent_node_name, child_name, child_node_name + ('exp1', 'root', 'filter'), + ('filter', 'file', 'disk0'), + ('disk0', 'file', 'file0'), + ('exp2', 'root', 'filter') + ]) + + # Check, that filter is in use and can't be removed + result = vm.qmp('blockdev-del', node_name='filter') + self.assert_qmp(result, 'error/desc', 'Node filter is in use') + + vm.qmp_check('transaction', actions=[ + { + 'type': 'x-blockdev-replace', + 'data': { + 'parent-type': 'export', + 'export-id': 'exp1', + 'new-child': 'disk0' + } + }, { + 'type': 'x-blockdev-replace', + 'data': { + 'parent-type': 'export', + 'export-id': 'exp2', + 'new-child': 'disk0' + } + } + ]) + vm.qmp_check('blockdev-del', node_name='filter') + + # Filter removed: + # exp1 -root-> disk0 <-file- exp2 + # | + # |file + # v + # file0 + vm.assert_edges_list([ + # parent_node_name, child_name, child_node_name + ('exp1', 'root', 'disk0'), + ('disk0', 'file', 'file0'), + ('exp2', 'root', 'disk0') + ]) + + +if __name__ == '__main__': + iotests.main( + supported_fmts=['qcow2'], + supported_protocols=['file'] + ) diff --git a/tests/qemu-iotests/tests/filter-insertion.out b/tests/qemu-iotests/tests/filter-insertion.out new file mode 100644 index 0000000000..8d7e996700 --- /dev/null +++ b/tests/qemu-iotests/tests/filter-insertion.out @@ -0,0 +1,5 @@ +... +---------------------------------------------------------------------- +Ran 3 tests + +OK -- 2.31.1

26.02.2022 02:42, Vladimir Sementsov-Ogievskiy wrote:
Hi all!
Finally, that's a proposal for new interface for filter insertion, which provides generic way for inserting between different block graph nodes, like BDS nodes, block exports and block devices.
v3: - add transaction support - add test, that shows transactional filter insertion in different cases - drop RFC mark. I think it's now close to be a good solution. And anyway, no comments on "RFC v2" version:) Still, I want to keep x- prefix for now, just because there were too many different ideas on this topic.
Oh, forget to mention, that it's based on recent "[PATCH 0/2] blockdev-add transaction": Based-on: <20220224171328.1628047-1-vsementsov@virtuozzo.com> -- Best regards, Vladimir
participants (1)
-
Vladimir Sementsov-Ogievskiy