1 | The following changes since commit 7b1db0908d88f0c9cfac24e214ff72a860692e23: | 1 | The following changes since commit d922088eb4ba6bc31a99f17b32cf75e59dd306cd: |
---|---|---|---|
2 | 2 | ||
3 | Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20180323' into staging (2018-03-25 13:51:33 +0100) | 3 | Merge tag 'ui-pull-request' of https://gitlab.com/marcandre.lureau/qemu into staging (2025-02-03 13:42:02 -0500) |
4 | 4 | ||
5 | are available in the git repository at: | 5 | are available in the Git repository at: |
6 | 6 | ||
7 | git://repo.or.cz/qemu/kevin.git tags/for-upstream | 7 | https://repo.or.cz/qemu/kevin.git tags/for-upstream |
8 | 8 | ||
9 | for you to fetch changes up to 0b7e7f66813a7e346e12d47be977a32a530a6316: | 9 | for you to fetch changes up to fc4e394b2887e15d5f83994e4fc7b26c895c627a: |
10 | 10 | ||
11 | qemu-iotests: Test vhdx image creation with QMP (2018-03-26 12:17:43 +0200) | 11 | block: remove unused BLOCK_OP_TYPE_DATAPLANE (2025-02-06 14:51:10 +0100) |
12 | 12 | ||
13 | ---------------------------------------------------------------- | 13 | ---------------------------------------------------------------- |
14 | Block layer patches | 14 | Block layer patches |
15 | 15 | ||
16 | - Managing inactive nodes (enables QSD migration with shared storage) | ||
17 | - Fix swapped values for BLOCK_IO_ERROR 'device' and 'qom-path' | ||
18 | - vpc: Read images exported from Azure correctly | ||
19 | - scripts/qemu-gdb: Support coroutine dumps in coredumps | ||
20 | - Minor cleanups | ||
21 | |||
16 | ---------------------------------------------------------------- | 22 | ---------------------------------------------------------------- |
17 | Alberto Garcia (1): | 23 | Fabiano Rosas (1): |
18 | qcow2: Reset free_cluster_index when allocating a new refcount block | 24 | block: Fix leak in send_qmp_error_event |
19 | 25 | ||
20 | Eric Blake (1): | 26 | Kevin Wolf (16): |
21 | iotests: 163 is not quick | 27 | block: Add 'active' field to BlockDeviceInfo |
28 | block: Allow inactivating already inactive nodes | ||
29 | block: Inactivate external snapshot overlays when necessary | ||
30 | migration/block-active: Remove global active flag | ||
31 | block: Don't attach inactive child to active node | ||
32 | block: Fix crash on block_resize on inactive node | ||
33 | block: Add option to create inactive nodes | ||
34 | block: Add blockdev-set-active QMP command | ||
35 | block: Support inactive nodes in blk_insert_bs() | ||
36 | block/export: Don't ignore image activation error in blk_exp_add() | ||
37 | block: Drain nodes before inactivating them | ||
38 | block/export: Add option to allow export of inactive nodes | ||
39 | nbd/server: Support inactive nodes | ||
40 | iotests: Add filter_qtest() | ||
41 | iotests: Add qsd-migrate case | ||
42 | iotests: Add (NBD-based) tests for inactive nodes | ||
22 | 43 | ||
23 | Fabiano Rosas (5): | 44 | Peter Krempa (1): |
24 | block/replication: Remove protocol_name field | 45 | block-backend: Fix argument order when calling 'qapi_event_send_block_io_error()' |
25 | block/quorum: Remove protocol-related fields | ||
26 | block/throttle: Remove protocol-related fields | ||
27 | block/blkreplay: Remove protocol-related fields | ||
28 | include/block/block_int: Document protocol related functions | ||
29 | 46 | ||
30 | Kevin Wolf (12): | 47 | Peter Xu (3): |
31 | vdi: Change 'static' create option to 'preallocation' in QMP | 48 | scripts/qemu-gdb: Always do full stack dump for python errors |
32 | vdi: Fix build with CONFIG_VDI_DEBUG | 49 | scripts/qemu-gdb: Simplify fs_base fetching for coroutines |
33 | qemu-iotests: Test vdi image creation with QMP | 50 | scripts/qemu-gdb: Support coroutine dumps in coredumps |
34 | qemu-iotests: Enable 025 for luks | ||
35 | luks: Turn another invalid assertion into check | ||
36 | qemu-iotests: Test invalid resize on luks | ||
37 | parallels: Check maximum cluster size on create | ||
38 | qemu-iotests: Test parallels image creation with QMP | ||
39 | vhdx: Require power-of-two block size on create | ||
40 | vhdx: Don't use error_setg_errno() with constant errno | ||
41 | vhdx: Check for 4 GB maximum log size on creation | ||
42 | qemu-iotests: Test vhdx image creation with QMP | ||
43 | 51 | ||
44 | qapi/block-core.json | 7 +- | 52 | Philippe Mathieu-Daudé (1): |
45 | include/block/block_int.h | 8 ++ | 53 | block: Improve blk_get_attached_dev_id() docstring |
46 | replication.h | 1 - | ||
47 | block/blkreplay.c | 3 +- | ||
48 | block/crypto.c | 6 +- | ||
49 | block/parallels.c | 5 + | ||
50 | block/qcow2-refcount.c | 7 + | ||
51 | block/quorum.c | 3 +- | ||
52 | block/replication.c | 1 - | ||
53 | block/throttle.c | 3 +- | ||
54 | block/vdi.c | 46 ++++-- | ||
55 | block/vhdx.c | 17 ++- | ||
56 | tests/qemu-iotests/025 | 9 +- | ||
57 | tests/qemu-iotests/026.out | 6 +- | ||
58 | tests/qemu-iotests/121 | 20 +++ | ||
59 | tests/qemu-iotests/121.out | 10 ++ | ||
60 | tests/qemu-iotests/210 | 37 +++++ | ||
61 | tests/qemu-iotests/210.out | 16 +++ | ||
62 | tests/qemu-iotests/211 | 246 ++++++++++++++++++++++++++++++++ | ||
63 | tests/qemu-iotests/211.out | 97 +++++++++++++ | ||
64 | tests/qemu-iotests/212 | 326 ++++++++++++++++++++++++++++++++++++++++++ | ||
65 | tests/qemu-iotests/212.out | 111 ++++++++++++++ | ||
66 | tests/qemu-iotests/213 | 349 +++++++++++++++++++++++++++++++++++++++++++++ | ||
67 | tests/qemu-iotests/213.out | 121 ++++++++++++++++ | ||
68 | tests/qemu-iotests/group | 5 +- | ||
69 | 25 files changed, 1423 insertions(+), 37 deletions(-) | ||
70 | create mode 100755 tests/qemu-iotests/211 | ||
71 | create mode 100644 tests/qemu-iotests/211.out | ||
72 | create mode 100755 tests/qemu-iotests/212 | ||
73 | create mode 100644 tests/qemu-iotests/212.out | ||
74 | create mode 100755 tests/qemu-iotests/213 | ||
75 | create mode 100644 tests/qemu-iotests/213.out | ||
76 | 54 | ||
55 | Stefan Hajnoczi (1): | ||
56 | block: remove unused BLOCK_OP_TYPE_DATAPLANE | ||
57 | |||
58 | Vitaly Kuznetsov (2): | ||
59 | vpc: Split off vpc_ignore_current_size() helper | ||
60 | vpc: Read images exported from Azure correctly | ||
61 | |||
62 | qapi/block-core.json | 44 +++- | ||
63 | qapi/block-export.json | 10 +- | ||
64 | include/block/block-common.h | 2 +- | ||
65 | include/block/block-global-state.h | 6 + | ||
66 | include/block/export.h | 3 + | ||
67 | include/system/block-backend-io.h | 7 + | ||
68 | migration/migration.h | 3 - | ||
69 | block.c | 64 +++++- | ||
70 | block/block-backend.c | 32 ++- | ||
71 | block/export/export.c | 29 ++- | ||
72 | block/monitor/block-hmp-cmds.c | 5 +- | ||
73 | block/qapi.c | 1 + | ||
74 | block/replication.c | 1 - | ||
75 | block/vpc.c | 65 +++--- | ||
76 | blockdev.c | 48 ++++ | ||
77 | blockjob.c | 2 - | ||
78 | hw/block/virtio-blk.c | 9 - | ||
79 | hw/scsi/virtio-scsi.c | 3 - | ||
80 | migration/block-active.c | 46 ---- | ||
81 | migration/migration.c | 8 - | ||
82 | nbd/server.c | 17 ++ | ||
83 | scripts/qemu-gdb.py | 2 + | ||
84 | scripts/qemugdb/coroutine.py | 102 ++++++--- | ||
85 | tests/qemu-iotests/iotests.py | 8 + | ||
86 | tests/qemu-iotests/041 | 4 +- | ||
87 | tests/qemu-iotests/165 | 4 +- | ||
88 | tests/qemu-iotests/184.out | 2 + | ||
89 | tests/qemu-iotests/191.out | 16 ++ | ||
90 | tests/qemu-iotests/273.out | 5 + | ||
91 | tests/qemu-iotests/tests/copy-before-write | 3 +- | ||
92 | tests/qemu-iotests/tests/inactive-node-nbd | 303 +++++++++++++++++++++++++ | ||
93 | tests/qemu-iotests/tests/inactive-node-nbd.out | 239 +++++++++++++++++++ | ||
94 | tests/qemu-iotests/tests/migrate-bitmaps-test | 7 +- | ||
95 | tests/qemu-iotests/tests/qsd-migrate | 140 ++++++++++++ | ||
96 | tests/qemu-iotests/tests/qsd-migrate.out | 59 +++++ | ||
97 | 35 files changed, 1133 insertions(+), 166 deletions(-) | ||
98 | create mode 100755 tests/qemu-iotests/tests/inactive-node-nbd | ||
99 | create mode 100644 tests/qemu-iotests/tests/inactive-node-nbd.out | ||
100 | create mode 100755 tests/qemu-iotests/tests/qsd-migrate | ||
101 | create mode 100644 tests/qemu-iotests/tests/qsd-migrate.out | ||
102 | |||
103 | diff view generated by jsdifflib |
1 | From: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 1 | From: Vitaly Kuznetsov <vkuznets@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | The throttle driver is not a protocol so it should implement bdrv_open | 3 | In preparation to making changes to the logic deciding whether CHS or |
4 | instead of bdrv_file_open and not provide a protocol_name. | 4 | 'current_size' need to be used in determining the image size, split off |
5 | vpc_ignore_current_size() helper. | ||
5 | 6 | ||
6 | Attempts to invoke this driver using protocol syntax | 7 | No functional change intended. |
7 | (i.e. throttle:<filename:options:...>) will now fail gracefully: | ||
8 | 8 | ||
9 | $ qemu-img info throttle:foo | 9 | Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com> |
10 | qemu-img: Could not open 'throttle:foo': Unknown protocol 'throttle' | 10 | Message-ID: <20241212134504.1983757-2-vkuznets@redhat.com> |
11 | 11 | Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> | |
12 | Signed-off-by: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 12 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> |
13 | Reviewed-by: Max Reitz <mreitz@redhat.com> | ||
14 | Reviewed-by: Alberto Garcia <berto@igalia.com> | ||
15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 13 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
16 | --- | 14 | --- |
17 | block/throttle.c | 3 +-- | 15 | block/vpc.c | 67 +++++++++++++++++++++++++++++------------------------ |
18 | 1 file changed, 1 insertion(+), 2 deletions(-) | 16 | 1 file changed, 37 insertions(+), 30 deletions(-) |
19 | 17 | ||
20 | diff --git a/block/throttle.c b/block/throttle.c | 18 | diff --git a/block/vpc.c b/block/vpc.c |
21 | index XXXXXXX..XXXXXXX 100644 | 19 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/block/throttle.c | 20 | --- a/block/vpc.c |
23 | +++ b/block/throttle.c | 21 | +++ b/block/vpc.c |
24 | @@ -XXX,XX +XXX,XX @@ static void coroutine_fn throttle_co_drain_end(BlockDriverState *bs) | 22 | @@ -XXX,XX +XXX,XX @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts, |
25 | 23 | } | |
26 | static BlockDriver bdrv_throttle = { | 24 | } |
27 | .format_name = "throttle", | 25 | |
28 | - .protocol_name = "throttle", | 26 | +/* |
29 | .instance_size = sizeof(ThrottleGroupMember), | 27 | + * Microsoft Virtual PC and Microsoft Hyper-V produce and read |
30 | 28 | + * VHD image sizes differently. VPC will rely on CHS geometry, | |
31 | - .bdrv_file_open = throttle_open, | 29 | + * while Hyper-V and disk2vhd use the size specified in the footer. |
32 | + .bdrv_open = throttle_open, | 30 | + * |
33 | .bdrv_close = throttle_close, | 31 | + * We use a couple of approaches to try and determine the correct method: |
34 | .bdrv_co_flush = throttle_co_flush, | 32 | + * look at the Creator App field, and look for images that have CHS |
35 | 33 | + * geometry that is the maximum value. | |
34 | + * | ||
35 | + * If the CHS geometry is the maximum CHS geometry, then we assume that | ||
36 | + * the size is the footer->current_size to avoid truncation. Otherwise, | ||
37 | + * we follow the table based on footer->creator_app: | ||
38 | + * | ||
39 | + * Known creator apps: | ||
40 | + * 'vpc ' : CHS Virtual PC (uses disk geometry) | ||
41 | + * 'qemu' : CHS QEMU (uses disk geometry) | ||
42 | + * 'qem2' : current_size QEMU (uses current_size) | ||
43 | + * 'win ' : current_size Hyper-V | ||
44 | + * 'd2v ' : current_size Disk2vhd | ||
45 | + * 'tap\0' : current_size XenServer | ||
46 | + * 'CTXS' : current_size XenConverter | ||
47 | + * | ||
48 | + * The user can override the table values via drive options, however | ||
49 | + * even with an override we will still use current_size for images | ||
50 | + * that have CHS geometry of the maximum size. | ||
51 | + */ | ||
52 | +static bool vpc_ignore_current_size(VHDFooter *footer) | ||
53 | +{ | ||
54 | + return !!strncmp(footer->creator_app, "win ", 4) && | ||
55 | + !!strncmp(footer->creator_app, "qem2", 4) && | ||
56 | + !!strncmp(footer->creator_app, "d2v ", 4) && | ||
57 | + !!strncmp(footer->creator_app, "CTXS", 4) && | ||
58 | + !!memcmp(footer->creator_app, "tap", 4)); | ||
59 | +} | ||
60 | + | ||
61 | static int vpc_open(BlockDriverState *bs, QDict *options, int flags, | ||
62 | Error **errp) | ||
63 | { | ||
64 | @@ -XXX,XX +XXX,XX @@ static int vpc_open(BlockDriverState *bs, QDict *options, int flags, | ||
65 | bs->total_sectors = (int64_t) | ||
66 | be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl; | ||
67 | |||
68 | - /* Microsoft Virtual PC and Microsoft Hyper-V produce and read | ||
69 | - * VHD image sizes differently. VPC will rely on CHS geometry, | ||
70 | - * while Hyper-V and disk2vhd use the size specified in the footer. | ||
71 | - * | ||
72 | - * We use a couple of approaches to try and determine the correct method: | ||
73 | - * look at the Creator App field, and look for images that have CHS | ||
74 | - * geometry that is the maximum value. | ||
75 | - * | ||
76 | - * If the CHS geometry is the maximum CHS geometry, then we assume that | ||
77 | - * the size is the footer->current_size to avoid truncation. Otherwise, | ||
78 | - * we follow the table based on footer->creator_app: | ||
79 | - * | ||
80 | - * Known creator apps: | ||
81 | - * 'vpc ' : CHS Virtual PC (uses disk geometry) | ||
82 | - * 'qemu' : CHS QEMU (uses disk geometry) | ||
83 | - * 'qem2' : current_size QEMU (uses current_size) | ||
84 | - * 'win ' : current_size Hyper-V | ||
85 | - * 'd2v ' : current_size Disk2vhd | ||
86 | - * 'tap\0' : current_size XenServer | ||
87 | - * 'CTXS' : current_size XenConverter | ||
88 | - * | ||
89 | - * The user can override the table values via drive options, however | ||
90 | - * even with an override we will still use current_size for images | ||
91 | - * that have CHS geometry of the maximum size. | ||
92 | - */ | ||
93 | - use_chs = (!!strncmp(footer->creator_app, "win ", 4) && | ||
94 | - !!strncmp(footer->creator_app, "qem2", 4) && | ||
95 | - !!strncmp(footer->creator_app, "d2v ", 4) && | ||
96 | - !!strncmp(footer->creator_app, "CTXS", 4) && | ||
97 | - !!memcmp(footer->creator_app, "tap", 4)) || s->force_use_chs; | ||
98 | + /* Use CHS or current_size to determine the image size. */ | ||
99 | + use_chs = vpc_ignore_current_size(footer) || s->force_use_chs; | ||
100 | |||
101 | if (!use_chs || bs->total_sectors == VHD_MAX_GEOMETRY || s->force_use_sz) { | ||
102 | bs->total_sectors = be64_to_cpu(footer->current_size) / | ||
36 | -- | 103 | -- |
37 | 2.13.6 | 104 | 2.48.1 |
38 | 105 | ||
39 | 106 | diff view generated by jsdifflib |
New patch | |||
---|---|---|---|
1 | From: Vitaly Kuznetsov <vkuznets@redhat.com> | ||
1 | 2 | ||
3 | It was found that 'qemu-nbd' is not able to work with some disk images | ||
4 | exported from Azure. Looking at the 512b footer (which contains VPC | ||
5 | metadata): | ||
6 | |||
7 | 00000000 63 6f 6e 65 63 74 69 78 00 00 00 02 00 01 00 00 |conectix........| | ||
8 | 00000010 ff ff ff ff ff ff ff ff 2e c7 9b 96 77 61 00 00 |............wa..| | ||
9 | 00000020 00 07 00 00 57 69 32 6b 00 00 00 01 40 00 00 00 |....Wi2k....@...| | ||
10 | 00000030 00 00 00 01 40 00 00 00 28 a2 10 3f 00 00 00 02 |....@...(..?....| | ||
11 | 00000040 ff ff e7 47 8c 54 df 94 bd 35 71 4c 94 5f e5 44 |...G.T...5qL._.D| | ||
12 | 00000050 44 53 92 1a 00 00 00 00 00 00 00 00 00 00 00 00 |DS..............| | ||
13 | 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | ||
14 | |||
15 | we can see that Azure uses a different 'Creator application' -- | ||
16 | 'wa\0\0' (offset 0x1c, likely reads as 'Windows Azure') and QEMU uses this | ||
17 | field to determine how it can get image size. Apparently, Azure uses 'new' | ||
18 | method, just like Hyper-V. | ||
19 | |||
20 | Overall, it seems that only VPC and old QEMUs need to be ignored as all new | ||
21 | creator apps seem to have reliable current_size. Invert the logic and make | ||
22 | 'current_size' method the default to avoid adding every new creator app to | ||
23 | the list. | ||
24 | |||
25 | Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com> | ||
26 | Message-ID: <20241212134504.1983757-3-vkuznets@redhat.com> | ||
27 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
28 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
29 | --- | ||
30 | block/vpc.c | 8 +++----- | ||
31 | 1 file changed, 3 insertions(+), 5 deletions(-) | ||
32 | |||
33 | diff --git a/block/vpc.c b/block/vpc.c | ||
34 | index XXXXXXX..XXXXXXX 100644 | ||
35 | --- a/block/vpc.c | ||
36 | +++ b/block/vpc.c | ||
37 | @@ -XXX,XX +XXX,XX @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts, | ||
38 | * 'd2v ' : current_size Disk2vhd | ||
39 | * 'tap\0' : current_size XenServer | ||
40 | * 'CTXS' : current_size XenConverter | ||
41 | + * 'wa\0\0': current_size Azure | ||
42 | * | ||
43 | * The user can override the table values via drive options, however | ||
44 | * even with an override we will still use current_size for images | ||
45 | @@ -XXX,XX +XXX,XX @@ static void vpc_parse_options(BlockDriverState *bs, QemuOpts *opts, | ||
46 | */ | ||
47 | static bool vpc_ignore_current_size(VHDFooter *footer) | ||
48 | { | ||
49 | - return !!strncmp(footer->creator_app, "win ", 4) && | ||
50 | - !!strncmp(footer->creator_app, "qem2", 4) && | ||
51 | - !!strncmp(footer->creator_app, "d2v ", 4) && | ||
52 | - !!strncmp(footer->creator_app, "CTXS", 4) && | ||
53 | - !!memcmp(footer->creator_app, "tap", 4)); | ||
54 | + return !strncmp(footer->creator_app, "vpc ", 4) || | ||
55 | + !strncmp(footer->creator_app, "qemu", 4); | ||
56 | } | ||
57 | |||
58 | static int vpc_open(BlockDriverState *bs, QDict *options, int flags, | ||
59 | -- | ||
60 | 2.48.1 | diff view generated by jsdifflib |
1 | From: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 1 | From: Philippe Mathieu-Daudé <philmd@linaro.org> |
---|---|---|---|
2 | 2 | ||
3 | The blkreplay driver is not a protocol so it should implement bdrv_open | 3 | Expose the method docstring in the header, and mention |
4 | instead of bdrv_file_open and not provide a protocol_name. | 4 | returned value must be free'd by caller. |
5 | 5 | ||
6 | Attempts to invoke this driver using protocol syntax | 6 | Reported-by: Fabiano Rosas <farosas@suse.de> |
7 | (i.e. blkreplay:<filename:options:...>) will now fail gracefully: | 7 | Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> |
8 | 8 | Message-ID: <20241111170333.43833-2-philmd@linaro.org> | |
9 | $ qemu-img info blkreplay:foo | ||
10 | qemu-img: Could not open 'blkreplay:foo': Unknown protocol 'blkreplay' | ||
11 | |||
12 | Signed-off-by: Fabiano Rosas <farosas@linux.vnet.ibm.com> | ||
13 | Reviewed-by: Pavel Dovgalyuk <pavel.dovgaluk@ispras.ru> | ||
14 | Reviewed-by: Max Reitz <mreitz@redhat.com> | ||
15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
16 | --- | 10 | --- |
17 | block/blkreplay.c | 3 +-- | 11 | include/system/block-backend-io.h | 7 +++++++ |
18 | 1 file changed, 1 insertion(+), 2 deletions(-) | 12 | block/block-backend.c | 12 ++++++++---- |
13 | 2 files changed, 15 insertions(+), 4 deletions(-) | ||
19 | 14 | ||
20 | diff --git a/block/blkreplay.c b/block/blkreplay.c | 15 | diff --git a/include/system/block-backend-io.h b/include/system/block-backend-io.h |
21 | index XXXXXXX..XXXXXXX 100755 | 16 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/block/blkreplay.c | 17 | --- a/include/system/block-backend-io.h |
23 | +++ b/block/blkreplay.c | 18 | +++ b/include/system/block-backend-io.h |
24 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn blkreplay_co_flush(BlockDriverState *bs) | 19 | @@ -XXX,XX +XXX,XX @@ void blk_set_allow_aio_context_change(BlockBackend *blk, bool allow); |
25 | 20 | void blk_set_disable_request_queuing(BlockBackend *blk, bool disable); | |
26 | static BlockDriver bdrv_blkreplay = { | 21 | bool blk_iostatus_is_enabled(const BlockBackend *blk); |
27 | .format_name = "blkreplay", | 22 | |
28 | - .protocol_name = "blkreplay", | 23 | +/* |
29 | .instance_size = 0, | 24 | + * Return the qdev ID, or if no ID is assigned the QOM path, |
30 | 25 | + * of the block device attached to the BlockBackend. | |
31 | - .bdrv_file_open = blkreplay_open, | 26 | + * |
32 | + .bdrv_open = blkreplay_open, | 27 | + * The caller is responsible for releasing the value returned |
33 | .bdrv_close = blkreplay_close, | 28 | + * with g_free() after use. |
34 | .bdrv_child_perm = bdrv_filter_default_perms, | 29 | + */ |
35 | .bdrv_getlength = blkreplay_getlength, | 30 | char *blk_get_attached_dev_id(BlockBackend *blk); |
31 | |||
32 | BlockAIOCB *blk_aio_pwrite_zeroes(BlockBackend *blk, int64_t offset, | ||
33 | diff --git a/block/block-backend.c b/block/block-backend.c | ||
34 | index XXXXXXX..XXXXXXX 100644 | ||
35 | --- a/block/block-backend.c | ||
36 | +++ b/block/block-backend.c | ||
37 | @@ -XXX,XX +XXX,XX @@ DeviceState *blk_get_attached_dev(BlockBackend *blk) | ||
38 | return blk->dev; | ||
39 | } | ||
40 | |||
41 | +/* | ||
42 | + * The caller is responsible for releasing the value returned | ||
43 | + * with g_free() after use. | ||
44 | + */ | ||
45 | static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id) | ||
46 | { | ||
47 | DeviceState *dev = blk->dev; | ||
48 | @@ -XXX,XX +XXX,XX @@ static char *blk_get_attached_dev_id_or_path(BlockBackend *blk, bool want_id) | ||
49 | return object_get_canonical_path(OBJECT(dev)) ?: g_strdup(""); | ||
50 | } | ||
51 | |||
52 | -/* | ||
53 | - * Return the qdev ID, or if no ID is assigned the QOM path, of the block | ||
54 | - * device attached to the BlockBackend. | ||
55 | - */ | ||
56 | char *blk_get_attached_dev_id(BlockBackend *blk) | ||
57 | { | ||
58 | return blk_get_attached_dev_id_or_path(blk, true); | ||
59 | } | ||
60 | |||
61 | +/* | ||
62 | + * The caller is responsible for releasing the value returned | ||
63 | + * with g_free() after use. | ||
64 | + */ | ||
65 | static char *blk_get_attached_dev_path(BlockBackend *blk) | ||
66 | { | ||
67 | return blk_get_attached_dev_id_or_path(blk, false); | ||
36 | -- | 68 | -- |
37 | 2.13.6 | 69 | 2.48.1 |
38 | 70 | ||
39 | 71 | diff view generated by jsdifflib |
1 | From: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 1 | From: Fabiano Rosas <farosas@suse.de> |
---|---|---|---|
2 | 2 | ||
3 | Clarify that: | 3 | ASAN detected a leak when running the ahci-test |
4 | /ahci/io/dma/lba28/retry: | ||
4 | 5 | ||
5 | - for protocols the brdv_file_open function is used instead | 6 | Direct leak of 35 byte(s) in 1 object(s) allocated from: |
6 | of bdrv_open; | 7 | #0 in malloc |
8 | #1 in __vasprintf_internal | ||
9 | #2 in vasprintf | ||
10 | #3 in g_vasprintf | ||
11 | #4 in g_strdup_vprintf | ||
12 | #5 in g_strdup_printf | ||
13 | #6 in object_get_canonical_path ../qom/object.c:2096:19 | ||
14 | #7 in blk_get_attached_dev_id_or_path ../block/block-backend.c:1033:12 | ||
15 | #8 in blk_get_attached_dev_path ../block/block-backend.c:1047:12 | ||
16 | #9 in send_qmp_error_event ../block/block-backend.c:2140:36 | ||
17 | #10 in blk_error_action ../block/block-backend.c:2172:9 | ||
18 | #11 in ide_handle_rw_error ../hw/ide/core.c:875:5 | ||
19 | #12 in ide_dma_cb ../hw/ide/core.c:894:13 | ||
20 | #13 in dma_complete ../system/dma-helpers.c:107:9 | ||
21 | #14 in dma_blk_cb ../system/dma-helpers.c:129:9 | ||
22 | #15 in blk_aio_complete ../block/block-backend.c:1552:9 | ||
23 | #16 in blk_aio_write_entry ../block/block-backend.c:1619:5 | ||
24 | #17 in coroutine_trampoline ../util/coroutine-ucontext.c:175:9 | ||
7 | 25 | ||
8 | - when protocol_name is set, a driver should expect | 26 | Plug the leak by freeing the device path string. |
9 | to be given only a filename and no other options. | ||
10 | 27 | ||
11 | Signed-off-by: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 28 | Signed-off-by: Fabiano Rosas <farosas@suse.de> |
29 | Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> | ||
30 | Message-ID: <20241111145214.8261-1-farosas@suse.de> | ||
31 | [PMD: Use g_autofree] | ||
32 | Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> | ||
33 | Message-ID: <20241111170333.43833-3-philmd@linaro.org> | ||
12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 34 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
13 | --- | 35 | --- |
14 | include/block/block_int.h | 8 ++++++++ | 36 | block/block-backend.c | 4 ++-- |
15 | 1 file changed, 8 insertions(+) | 37 | 1 file changed, 2 insertions(+), 2 deletions(-) |
16 | 38 | ||
17 | diff --git a/include/block/block_int.h b/include/block/block_int.h | 39 | diff --git a/block/block-backend.c b/block/block-backend.c |
18 | index XXXXXXX..XXXXXXX 100644 | 40 | index XXXXXXX..XXXXXXX 100644 |
19 | --- a/include/block/block_int.h | 41 | --- a/block/block-backend.c |
20 | +++ b/include/block/block_int.h | 42 | +++ b/block/block-backend.c |
21 | @@ -XXX,XX +XXX,XX @@ struct BlockDriver { | 43 | @@ -XXX,XX +XXX,XX @@ static void send_qmp_error_event(BlockBackend *blk, |
22 | 44 | { | |
23 | int (*bdrv_open)(BlockDriverState *bs, QDict *options, int flags, | 45 | IoOperationType optype; |
24 | Error **errp); | 46 | BlockDriverState *bs = blk_bs(blk); |
25 | + | 47 | + g_autofree char *path = blk_get_attached_dev_path(blk); |
26 | + /* Protocol drivers should implement this instead of bdrv_open */ | 48 | |
27 | int (*bdrv_file_open)(BlockDriverState *bs, QDict *options, int flags, | 49 | optype = is_read ? IO_OPERATION_TYPE_READ : IO_OPERATION_TYPE_WRITE; |
28 | Error **errp); | 50 | - qapi_event_send_block_io_error(blk_name(blk), |
29 | void (*bdrv_close)(BlockDriverState *bs); | 51 | - blk_get_attached_dev_path(blk), |
30 | @@ -XXX,XX +XXX,XX @@ struct BlockDriver { | 52 | + qapi_event_send_block_io_error(blk_name(blk), path, |
31 | */ | 53 | bs ? bdrv_get_node_name(bs) : NULL, optype, |
32 | int coroutine_fn (*bdrv_co_flush_to_os)(BlockDriverState *bs); | 54 | action, blk_iostatus_is_enabled(blk), |
33 | 55 | error == ENOSPC, strerror(error)); | |
34 | + /* | ||
35 | + * Drivers setting this field must be able to work with just a plain | ||
36 | + * filename with '<protocol_name>:' as a prefix, and no other options. | ||
37 | + * Options may be extracted from the filename by implementing | ||
38 | + * bdrv_parse_filename. | ||
39 | + */ | ||
40 | const char *protocol_name; | ||
41 | int (*bdrv_truncate)(BlockDriverState *bs, int64_t offset, | ||
42 | PreallocMode prealloc, Error **errp); | ||
43 | -- | 56 | -- |
44 | 2.13.6 | 57 | 2.48.1 |
45 | 58 | ||
46 | 59 | diff view generated by jsdifflib |
New patch | |||
---|---|---|---|
1 | From: Peter Xu <peterx@redhat.com> | ||
1 | 2 | ||
3 | It's easier for either debugging plugin errors, or issue reports. | ||
4 | |||
5 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
6 | Message-ID: <20241212204801.1420528-2-peterx@redhat.com> | ||
7 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
8 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
9 | --- | ||
10 | scripts/qemu-gdb.py | 2 ++ | ||
11 | 1 file changed, 2 insertions(+) | ||
12 | |||
13 | diff --git a/scripts/qemu-gdb.py b/scripts/qemu-gdb.py | ||
14 | index XXXXXXX..XXXXXXX 100644 | ||
15 | --- a/scripts/qemu-gdb.py | ||
16 | +++ b/scripts/qemu-gdb.py | ||
17 | @@ -XXX,XX +XXX,XX @@ def __init__(self): | ||
18 | # Default to silently passing through SIGUSR1, because QEMU sends it | ||
19 | # to itself a lot. | ||
20 | gdb.execute('handle SIGUSR1 pass noprint nostop') | ||
21 | +# Always print full stack for python errors, easier to debug and report issues | ||
22 | +gdb.execute('set python print-stack full') | ||
23 | -- | ||
24 | 2.48.1 | diff view generated by jsdifflib |
New patch | |||
---|---|---|---|
1 | From: Peter Xu <peterx@redhat.com> | ||
1 | 2 | ||
3 | There're a bunch of code trying to fetch fs_base in different ways. IIUC | ||
4 | the simplest way instead is "$fs_base". It also has the benefit that it'll | ||
5 | work for both live gdb session or coredumps. | ||
6 | |||
7 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
8 | Message-ID: <20241212204801.1420528-3-peterx@redhat.com> | ||
9 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
11 | --- | ||
12 | scripts/qemugdb/coroutine.py | 23 ++--------------------- | ||
13 | 1 file changed, 2 insertions(+), 21 deletions(-) | ||
14 | |||
15 | diff --git a/scripts/qemugdb/coroutine.py b/scripts/qemugdb/coroutine.py | ||
16 | index XXXXXXX..XXXXXXX 100644 | ||
17 | --- a/scripts/qemugdb/coroutine.py | ||
18 | +++ b/scripts/qemugdb/coroutine.py | ||
19 | @@ -XXX,XX +XXX,XX @@ | ||
20 | |||
21 | VOID_PTR = gdb.lookup_type('void').pointer() | ||
22 | |||
23 | -def get_fs_base(): | ||
24 | - '''Fetch %fs base value using arch_prctl(ARCH_GET_FS). This is | ||
25 | - pthread_self().''' | ||
26 | - # %rsp - 120 is scratch space according to the SystemV ABI | ||
27 | - old = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)') | ||
28 | - gdb.execute('call (int)arch_prctl(0x1003, $rsp - 120)', False, True) | ||
29 | - fs_base = gdb.parse_and_eval('*(uint64_t*)($rsp - 120)') | ||
30 | - gdb.execute('set *(uint64_t*)($rsp - 120) = %s' % old, False, True) | ||
31 | - return fs_base | ||
32 | - | ||
33 | def pthread_self(): | ||
34 | - '''Fetch pthread_self() from the glibc start_thread function.''' | ||
35 | - f = gdb.newest_frame() | ||
36 | - while f.name() != 'start_thread': | ||
37 | - f = f.older() | ||
38 | - if f is None: | ||
39 | - return get_fs_base() | ||
40 | - | ||
41 | - try: | ||
42 | - return f.read_var("arg") | ||
43 | - except ValueError: | ||
44 | - return get_fs_base() | ||
45 | + '''Fetch the base address of TLS.''' | ||
46 | + return gdb.parse_and_eval("$fs_base") | ||
47 | |||
48 | def get_glibc_pointer_guard(): | ||
49 | '''Fetch glibc pointer guard value''' | ||
50 | -- | ||
51 | 2.48.1 | diff view generated by jsdifflib |
New patch | |||
---|---|---|---|
1 | From: Peter Xu <peterx@redhat.com> | ||
1 | 2 | ||
3 | Dumping coroutines don't yet work with coredumps. Let's make it work. | ||
4 | |||
5 | We still kept most of the old code because they can be either more | ||
6 | flexible, or prettier. Only add the fallbacks when they stop working. | ||
7 | |||
8 | Currently the raw unwind is pretty ugly, but it works, like this: | ||
9 | |||
10 | (gdb) qemu bt | ||
11 | #0 process_incoming_migration_co (opaque=0x0) at ../migration/migration.c:788 | ||
12 | #1 0x000055ae6c0dc4d9 in coroutine_trampoline (i0=-1711718576, i1=21934) at ../util/coroutine-ucontext.c:175 | ||
13 | #2 0x00007f9f59d72f40 in ??? () at /lib64/libc.so.6 | ||
14 | #3 0x00007ffd549214a0 in ??? () | ||
15 | #4 0x0000000000000000 in ??? () | ||
16 | Coroutine at 0x7f9f4c57c748: | ||
17 | #0 0x55ae6c0dc9a8 in qemu_coroutine_switch<+120> () at ../util/coroutine-ucontext.c:321 | ||
18 | #1 0x55ae6c0da2f8 in qemu_aio_coroutine_enter<+356> () at ../util/qemu-coroutine.c:293 | ||
19 | #2 0x55ae6c0da3f1 in qemu_coroutine_enter<+34> () at ../util/qemu-coroutine.c:316 | ||
20 | #3 0x55ae6baf775e in migration_incoming_process<+43> () at ../migration/migration.c:876 | ||
21 | #4 0x55ae6baf7ab4 in migration_ioc_process_incoming<+490> () at ../migration/migration.c:1008 | ||
22 | #5 0x55ae6bae9ae7 in migration_channel_process_incoming<+145> () at ../migration/channel.c:45 | ||
23 | #6 0x55ae6bb18e35 in socket_accept_incoming_migration<+118> () at ../migration/socket.c:132 | ||
24 | #7 0x55ae6be939ef in qio_net_listener_channel_func<+131> () at ../io/net-listener.c:54 | ||
25 | #8 0x55ae6be8ce1a in qio_channel_fd_source_dispatch<+78> () at ../io/channel-watch.c:84 | ||
26 | #9 0x7f9f5b26728c in g_main_context_dispatch_unlocked.lto_priv<+315> () | ||
27 | #10 0x7f9f5b267555 in g_main_context_dispatch<+36> () | ||
28 | #11 0x55ae6c0d91a7 in glib_pollfds_poll<+90> () at ../util/main-loop.c:287 | ||
29 | #12 0x55ae6c0d9235 in os_host_main_loop_wait<+128> () at ../util/main-loop.c:310 | ||
30 | #13 0x55ae6c0d9364 in main_loop_wait<+203> () at ../util/main-loop.c:589 | ||
31 | #14 0x55ae6bac212a in qemu_main_loop<+41> () at ../system/runstate.c:835 | ||
32 | #15 0x55ae6bfdf522 in qemu_default_main<+19> () at ../system/main.c:37 | ||
33 | #16 0x55ae6bfdf55f in main<+40> () at ../system/main.c:48 | ||
34 | #17 0x7f9f59d42248 in __libc_start_call_main<+119> () | ||
35 | #18 0x7f9f59d4230b in __libc_start_main_impl<+138> () | ||
36 | |||
37 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
38 | Message-ID: <20241212204801.1420528-4-peterx@redhat.com> | ||
39 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
40 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
41 | --- | ||
42 | scripts/qemugdb/coroutine.py | 79 +++++++++++++++++++++++++++++++++--- | ||
43 | 1 file changed, 73 insertions(+), 6 deletions(-) | ||
44 | |||
45 | diff --git a/scripts/qemugdb/coroutine.py b/scripts/qemugdb/coroutine.py | ||
46 | index XXXXXXX..XXXXXXX 100644 | ||
47 | --- a/scripts/qemugdb/coroutine.py | ||
48 | +++ b/scripts/qemugdb/coroutine.py | ||
49 | @@ -XXX,XX +XXX,XX @@ def get_jmpbuf_regs(jmpbuf): | ||
50 | 'r15': jmpbuf[JB_R15], | ||
51 | 'rip': glibc_ptr_demangle(jmpbuf[JB_PC], pointer_guard) } | ||
52 | |||
53 | -def bt_jmpbuf(jmpbuf): | ||
54 | - '''Backtrace a jmpbuf''' | ||
55 | - regs = get_jmpbuf_regs(jmpbuf) | ||
56 | +def symbol_lookup(addr): | ||
57 | + # Example: "__clone3 + 44 in section .text of /lib64/libc.so.6" | ||
58 | + result = gdb.execute(f"info symbol {hex(addr)}", to_string=True).strip() | ||
59 | + try: | ||
60 | + if "+" in result: | ||
61 | + (func, result) = result.split(" + ") | ||
62 | + (offset, result) = result.split(" in ") | ||
63 | + else: | ||
64 | + offset = "0" | ||
65 | + (func, result) = result.split(" in ") | ||
66 | + func_str = f"{func}<+{offset}> ()" | ||
67 | + except: | ||
68 | + return f"??? ({result})" | ||
69 | + | ||
70 | + # Example: Line 321 of "../util/coroutine-ucontext.c" starts at address | ||
71 | + # 0x55cf3894d993 <qemu_coroutine_switch+99> and ends at 0x55cf3894d9ab | ||
72 | + # <qemu_coroutine_switch+123>. | ||
73 | + result = gdb.execute(f"info line *{hex(addr)}", to_string=True).strip() | ||
74 | + if not result.startswith("Line "): | ||
75 | + return func_str | ||
76 | + result = result[5:] | ||
77 | + | ||
78 | + try: | ||
79 | + result = result.split(" starts ")[0] | ||
80 | + (line, path) = result.split(" of ") | ||
81 | + path = path.replace("\"", "") | ||
82 | + except: | ||
83 | + return func_str | ||
84 | + | ||
85 | + return f"{func_str} at {path}:{line}" | ||
86 | + | ||
87 | +def dump_backtrace(regs): | ||
88 | + ''' | ||
89 | + Backtrace dump with raw registers, mimic GDB command 'bt'. | ||
90 | + ''' | ||
91 | + # Here only rbp and rip that matter.. | ||
92 | + rbp = regs['rbp'] | ||
93 | + rip = regs['rip'] | ||
94 | + i = 0 | ||
95 | + | ||
96 | + while rbp: | ||
97 | + # For all return addresses on stack, we want to look up symbol/line | ||
98 | + # on the CALL command, because the return address is the next | ||
99 | + # instruction instead of the CALL. Here -1 would work for any | ||
100 | + # sized CALL instruction. | ||
101 | + print(f"#{i} {hex(rip)} in {symbol_lookup(rip if i == 0 else rip-1)}") | ||
102 | + rip = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)} + 8)") | ||
103 | + rbp = gdb.parse_and_eval(f"*(uint64_t *)(uint64_t)({hex(rbp)})") | ||
104 | + i += 1 | ||
105 | + | ||
106 | +def dump_backtrace_live(regs): | ||
107 | + ''' | ||
108 | + Backtrace dump with gdb's 'bt' command, only usable in a live session. | ||
109 | + ''' | ||
110 | old = dict() | ||
111 | |||
112 | # remember current stack frame and select the topmost | ||
113 | @@ -XXX,XX +XXX,XX @@ def bt_jmpbuf(jmpbuf): | ||
114 | |||
115 | selected_frame.select() | ||
116 | |||
117 | +def bt_jmpbuf(jmpbuf): | ||
118 | + '''Backtrace a jmpbuf''' | ||
119 | + regs = get_jmpbuf_regs(jmpbuf) | ||
120 | + try: | ||
121 | + # This reuses gdb's "bt" command, which can be slightly prettier | ||
122 | + # but only works with live sessions. | ||
123 | + dump_backtrace_live(regs) | ||
124 | + except: | ||
125 | + # If above doesn't work, fallback to poor man's unwind | ||
126 | + dump_backtrace(regs) | ||
127 | + | ||
128 | def co_cast(co): | ||
129 | return co.cast(gdb.lookup_type('CoroutineUContext').pointer()) | ||
130 | |||
131 | @@ -XXX,XX +XXX,XX @@ def invoke(self, arg, from_tty): | ||
132 | |||
133 | gdb.execute("bt") | ||
134 | |||
135 | - if gdb.parse_and_eval("qemu_in_coroutine()") == False: | ||
136 | - return | ||
137 | + try: | ||
138 | + # This only works with a live session | ||
139 | + co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") | ||
140 | + except: | ||
141 | + # Fallback to use hard-coded ucontext vars if it's coredump | ||
142 | + co_ptr = gdb.parse_and_eval("co_tls_current") | ||
143 | |||
144 | - co_ptr = gdb.parse_and_eval("qemu_coroutine_self()") | ||
145 | + if co_ptr == False: | ||
146 | + return | ||
147 | |||
148 | while True: | ||
149 | co = co_cast(co_ptr) | ||
150 | -- | ||
151 | 2.48.1 | diff view generated by jsdifflib |
1 | From: Eric Blake <eblake@redhat.com> | 1 | From: Peter Krempa <pkrempa@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | Testing on ext4, most 'quick' qcow2 tests took less than 5 seconds, | 3 | Commit 7452162adec25c10 introduced 'qom-path' argument to BLOCK_IO_ERROR |
4 | but 163 took more than 20. Let's remove it from the quick set. | 4 | event but when the event is instantiated in 'send_qmp_error_event()' the |
5 | arguments for 'device' and 'qom_path' in | ||
6 | qapi_event_send_block_io_error() were reversed : | ||
5 | 7 | ||
6 | Signed-off-by: Eric Blake <eblake@redhat.com> | 8 | Generated code for sending event: |
9 | |||
10 | void qapi_event_send_block_io_error(const char *qom_path, | ||
11 | const char *device, | ||
12 | const char *node_name, | ||
13 | IoOperationType operation, | ||
14 | [...] | ||
15 | |||
16 | Call inside send_qmp_error_event(): | ||
17 | |||
18 | qapi_event_send_block_io_error(blk_name(blk), | ||
19 | blk_get_attached_dev_path(blk), | ||
20 | bs ? bdrv_get_node_name(bs) : NULL, optype, | ||
21 | [...] | ||
22 | |||
23 | This results into reporting the QOM path as the device alias and vice | ||
24 | versa which in turn breaks libvirt, which expects the device alias being | ||
25 | either a valid alias or empty (which would make libvirt do the lookup by | ||
26 | node-name instead). | ||
27 | |||
28 | Cc: qemu-stable@nongnu.org | ||
29 | Fixes: 7452162adec2 ("qapi: add qom-path to BLOCK_IO_ERROR event") | ||
30 | Signed-off-by: Peter Krempa <pkrempa@redhat.com> | ||
31 | Message-ID: <09728d784888b38d7a8f09ee5e9e9c542c875e1e.1737973614.git.pkrempa@redhat.com> | ||
32 | Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> | ||
33 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
7 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 34 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
8 | --- | 35 | --- |
9 | tests/qemu-iotests/group | 2 +- | 36 | block/block-backend.c | 2 +- |
10 | 1 file changed, 1 insertion(+), 1 deletion(-) | 37 | 1 file changed, 1 insertion(+), 1 deletion(-) |
11 | 38 | ||
12 | diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group | 39 | diff --git a/block/block-backend.c b/block/block-backend.c |
13 | index XXXXXXX..XXXXXXX 100644 | 40 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/tests/qemu-iotests/group | 41 | --- a/block/block-backend.c |
15 | +++ b/tests/qemu-iotests/group | 42 | +++ b/block/block-backend.c |
16 | @@ -XXX,XX +XXX,XX @@ | 43 | @@ -XXX,XX +XXX,XX @@ static void send_qmp_error_event(BlockBackend *blk, |
17 | 159 rw auto quick | 44 | g_autofree char *path = blk_get_attached_dev_path(blk); |
18 | 160 rw auto quick | 45 | |
19 | 162 auto quick | 46 | optype = is_read ? IO_OPERATION_TYPE_READ : IO_OPERATION_TYPE_WRITE; |
20 | -163 rw auto quick | 47 | - qapi_event_send_block_io_error(blk_name(blk), path, |
21 | +163 rw auto | 48 | + qapi_event_send_block_io_error(path, blk_name(blk), |
22 | 165 rw auto quick | 49 | bs ? bdrv_get_node_name(bs) : NULL, optype, |
23 | 169 rw auto quick | 50 | action, blk_iostatus_is_enabled(blk), |
24 | 170 rw auto quick | 51 | error == ENOSPC, strerror(error)); |
25 | -- | 52 | -- |
26 | 2.13.6 | 53 | 2.48.1 |
27 | 54 | ||
28 | 55 | diff view generated by jsdifflib |
New patch | |||
---|---|---|---|
1 | This allows querying from QMP (and also HMP) whether an image is | ||
2 | currently active or inactive (in the sense of BDRV_O_INACTIVE). | ||
1 | 3 | ||
4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
5 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
6 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
7 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
8 | Message-ID: <20250204211407.381505-2-kwolf@redhat.com> | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
10 | --- | ||
11 | qapi/block-core.json | 6 +++++- | ||
12 | include/block/block-global-state.h | 3 +++ | ||
13 | block.c | 4 ++++ | ||
14 | block/monitor/block-hmp-cmds.c | 5 +++-- | ||
15 | block/qapi.c | 1 + | ||
16 | tests/qemu-iotests/184.out | 2 ++ | ||
17 | tests/qemu-iotests/191.out | 16 ++++++++++++++++ | ||
18 | tests/qemu-iotests/273.out | 5 +++++ | ||
19 | 8 files changed, 39 insertions(+), 3 deletions(-) | ||
20 | |||
21 | diff --git a/qapi/block-core.json b/qapi/block-core.json | ||
22 | index XXXXXXX..XXXXXXX 100644 | ||
23 | --- a/qapi/block-core.json | ||
24 | +++ b/qapi/block-core.json | ||
25 | @@ -XXX,XX +XXX,XX @@ | ||
26 | # @backing_file_depth: number of files in the backing file chain | ||
27 | # (since: 1.2) | ||
28 | # | ||
29 | +# @active: true if the backend is active; typical cases for inactive backends | ||
30 | +# are on the migration source instance after migration completes and on the | ||
31 | +# destination before it completes. (since: 10.0) | ||
32 | +# | ||
33 | # @encrypted: true if the backing device is encrypted | ||
34 | # | ||
35 | # @detect_zeroes: detect and optimize zero writes (Since 2.1) | ||
36 | @@ -XXX,XX +XXX,XX @@ | ||
37 | { 'struct': 'BlockDeviceInfo', | ||
38 | 'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str', | ||
39 | '*backing_file': 'str', 'backing_file_depth': 'int', | ||
40 | - 'encrypted': 'bool', | ||
41 | + 'active': 'bool', 'encrypted': 'bool', | ||
42 | 'detect_zeroes': 'BlockdevDetectZeroesOptions', | ||
43 | 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int', | ||
44 | 'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int', | ||
45 | diff --git a/include/block/block-global-state.h b/include/block/block-global-state.h | ||
46 | index XXXXXXX..XXXXXXX 100644 | ||
47 | --- a/include/block/block-global-state.h | ||
48 | +++ b/include/block/block-global-state.h | ||
49 | @@ -XXX,XX +XXX,XX @@ BlockDriverState * GRAPH_RDLOCK | ||
50 | check_to_replace_node(BlockDriverState *parent_bs, const char *node_name, | ||
51 | Error **errp); | ||
52 | |||
53 | + | ||
54 | +bool GRAPH_RDLOCK bdrv_is_inactive(BlockDriverState *bs); | ||
55 | + | ||
56 | int no_coroutine_fn GRAPH_RDLOCK | ||
57 | bdrv_activate(BlockDriverState *bs, Error **errp); | ||
58 | |||
59 | diff --git a/block.c b/block.c | ||
60 | index XXXXXXX..XXXXXXX 100644 | ||
61 | --- a/block.c | ||
62 | +++ b/block.c | ||
63 | @@ -XXX,XX +XXX,XX @@ void bdrv_init_with_whitelist(void) | ||
64 | bdrv_init(); | ||
65 | } | ||
66 | |||
67 | +bool bdrv_is_inactive(BlockDriverState *bs) { | ||
68 | + return bs->open_flags & BDRV_O_INACTIVE; | ||
69 | +} | ||
70 | + | ||
71 | int bdrv_activate(BlockDriverState *bs, Error **errp) | ||
72 | { | ||
73 | BdrvChild *child, *parent; | ||
74 | diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c | ||
75 | index XXXXXXX..XXXXXXX 100644 | ||
76 | --- a/block/monitor/block-hmp-cmds.c | ||
77 | +++ b/block/monitor/block-hmp-cmds.c | ||
78 | @@ -XXX,XX +XXX,XX @@ static void print_block_info(Monitor *mon, BlockInfo *info, | ||
79 | } | ||
80 | |||
81 | if (inserted) { | ||
82 | - monitor_printf(mon, ": %s (%s%s%s)\n", | ||
83 | + monitor_printf(mon, ": %s (%s%s%s%s)\n", | ||
84 | inserted->file, | ||
85 | inserted->drv, | ||
86 | inserted->ro ? ", read-only" : "", | ||
87 | - inserted->encrypted ? ", encrypted" : ""); | ||
88 | + inserted->encrypted ? ", encrypted" : "", | ||
89 | + inserted->active ? "" : ", inactive"); | ||
90 | } else { | ||
91 | monitor_printf(mon, ": [not inserted]\n"); | ||
92 | } | ||
93 | diff --git a/block/qapi.c b/block/qapi.c | ||
94 | index XXXXXXX..XXXXXXX 100644 | ||
95 | --- a/block/qapi.c | ||
96 | +++ b/block/qapi.c | ||
97 | @@ -XXX,XX +XXX,XX @@ BlockDeviceInfo *bdrv_block_device_info(BlockBackend *blk, | ||
98 | info->file = g_strdup(bs->filename); | ||
99 | info->ro = bdrv_is_read_only(bs); | ||
100 | info->drv = g_strdup(bs->drv->format_name); | ||
101 | + info->active = !bdrv_is_inactive(bs); | ||
102 | info->encrypted = bs->encrypted; | ||
103 | |||
104 | info->cache = g_new(BlockdevCacheInfo, 1); | ||
105 | diff --git a/tests/qemu-iotests/184.out b/tests/qemu-iotests/184.out | ||
106 | index XXXXXXX..XXXXXXX 100644 | ||
107 | --- a/tests/qemu-iotests/184.out | ||
108 | +++ b/tests/qemu-iotests/184.out | ||
109 | @@ -XXX,XX +XXX,XX @@ Testing: | ||
110 | { | ||
111 | "iops_rd": 0, | ||
112 | "detect_zeroes": "off", | ||
113 | + "active": true, | ||
114 | "image": { | ||
115 | "backing-image": { | ||
116 | "virtual-size": 1073741824, | ||
117 | @@ -XXX,XX +XXX,XX @@ Testing: | ||
118 | { | ||
119 | "iops_rd": 0, | ||
120 | "detect_zeroes": "off", | ||
121 | + "active": true, | ||
122 | "image": { | ||
123 | "virtual-size": 1073741824, | ||
124 | "filename": "null-co://", | ||
125 | diff --git a/tests/qemu-iotests/191.out b/tests/qemu-iotests/191.out | ||
126 | index XXXXXXX..XXXXXXX 100644 | ||
127 | --- a/tests/qemu-iotests/191.out | ||
128 | +++ b/tests/qemu-iotests/191.out | ||
129 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
130 | { | ||
131 | "iops_rd": 0, | ||
132 | "detect_zeroes": "off", | ||
133 | + "active": true, | ||
134 | "image": { | ||
135 | "backing-image": { | ||
136 | "virtual-size": 67108864, | ||
137 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
138 | { | ||
139 | "iops_rd": 0, | ||
140 | "detect_zeroes": "off", | ||
141 | + "active": true, | ||
142 | "image": { | ||
143 | "virtual-size": 197120, | ||
144 | "filename": "TEST_DIR/t.IMGFMT.ovl2", | ||
145 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
146 | { | ||
147 | "iops_rd": 0, | ||
148 | "detect_zeroes": "off", | ||
149 | + "active": true, | ||
150 | "image": { | ||
151 | "backing-image": { | ||
152 | "virtual-size": 67108864, | ||
153 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
154 | { | ||
155 | "iops_rd": 0, | ||
156 | "detect_zeroes": "off", | ||
157 | + "active": true, | ||
158 | "image": { | ||
159 | "virtual-size": 197120, | ||
160 | "filename": "TEST_DIR/t.IMGFMT", | ||
161 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
162 | { | ||
163 | "iops_rd": 0, | ||
164 | "detect_zeroes": "off", | ||
165 | + "active": true, | ||
166 | "image": { | ||
167 | "backing-image": { | ||
168 | "virtual-size": 67108864, | ||
169 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
170 | { | ||
171 | "iops_rd": 0, | ||
172 | "detect_zeroes": "off", | ||
173 | + "active": true, | ||
174 | "image": { | ||
175 | "virtual-size": 393216, | ||
176 | "filename": "TEST_DIR/t.IMGFMT.mid", | ||
177 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
178 | { | ||
179 | "iops_rd": 0, | ||
180 | "detect_zeroes": "off", | ||
181 | + "active": true, | ||
182 | "image": { | ||
183 | "virtual-size": 67108864, | ||
184 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
185 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
186 | { | ||
187 | "iops_rd": 0, | ||
188 | "detect_zeroes": "off", | ||
189 | + "active": true, | ||
190 | "image": { | ||
191 | "virtual-size": 393216, | ||
192 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
193 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
194 | { | ||
195 | "iops_rd": 0, | ||
196 | "detect_zeroes": "off", | ||
197 | + "active": true, | ||
198 | "image": { | ||
199 | "backing-image": { | ||
200 | "virtual-size": 67108864, | ||
201 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
202 | { | ||
203 | "iops_rd": 0, | ||
204 | "detect_zeroes": "off", | ||
205 | + "active": true, | ||
206 | "image": { | ||
207 | "virtual-size": 197120, | ||
208 | "filename": "TEST_DIR/t.IMGFMT.ovl2", | ||
209 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
210 | { | ||
211 | "iops_rd": 0, | ||
212 | "detect_zeroes": "off", | ||
213 | + "active": true, | ||
214 | "image": { | ||
215 | "backing-image": { | ||
216 | "backing-image": { | ||
217 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
218 | { | ||
219 | "iops_rd": 0, | ||
220 | "detect_zeroes": "off", | ||
221 | + "active": true, | ||
222 | "image": { | ||
223 | "virtual-size": 197120, | ||
224 | "filename": "TEST_DIR/t.IMGFMT.ovl3", | ||
225 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
226 | { | ||
227 | "iops_rd": 0, | ||
228 | "detect_zeroes": "off", | ||
229 | + "active": true, | ||
230 | "image": { | ||
231 | "virtual-size": 67108864, | ||
232 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
233 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
234 | { | ||
235 | "iops_rd": 0, | ||
236 | "detect_zeroes": "off", | ||
237 | + "active": true, | ||
238 | "image": { | ||
239 | "virtual-size": 393216, | ||
240 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
241 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
242 | { | ||
243 | "iops_rd": 0, | ||
244 | "detect_zeroes": "off", | ||
245 | + "active": true, | ||
246 | "image": { | ||
247 | "backing-image": { | ||
248 | "virtual-size": 67108864, | ||
249 | @@ -XXX,XX +XXX,XX @@ wrote 65536/65536 bytes at offset 1048576 | ||
250 | { | ||
251 | "iops_rd": 0, | ||
252 | "detect_zeroes": "off", | ||
253 | + "active": true, | ||
254 | "image": { | ||
255 | "virtual-size": 197120, | ||
256 | "filename": "TEST_DIR/t.IMGFMT", | ||
257 | diff --git a/tests/qemu-iotests/273.out b/tests/qemu-iotests/273.out | ||
258 | index XXXXXXX..XXXXXXX 100644 | ||
259 | --- a/tests/qemu-iotests/273.out | ||
260 | +++ b/tests/qemu-iotests/273.out | ||
261 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
262 | { | ||
263 | "iops_rd": 0, | ||
264 | "detect_zeroes": "off", | ||
265 | + "active": true, | ||
266 | "image": { | ||
267 | "backing-image": { | ||
268 | "backing-image": { | ||
269 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
270 | { | ||
271 | "iops_rd": 0, | ||
272 | "detect_zeroes": "off", | ||
273 | + "active": true, | ||
274 | "image": { | ||
275 | "virtual-size": 197120, | ||
276 | "filename": "TEST_DIR/t.IMGFMT", | ||
277 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
278 | { | ||
279 | "iops_rd": 0, | ||
280 | "detect_zeroes": "off", | ||
281 | + "active": true, | ||
282 | "image": { | ||
283 | "backing-image": { | ||
284 | "virtual-size": 197120, | ||
285 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
286 | { | ||
287 | "iops_rd": 0, | ||
288 | "detect_zeroes": "off", | ||
289 | + "active": true, | ||
290 | "image": { | ||
291 | "virtual-size": 197120, | ||
292 | "filename": "TEST_DIR/t.IMGFMT.mid", | ||
293 | @@ -XXX,XX +XXX,XX @@ Testing: -blockdev file,node-name=base,filename=TEST_DIR/t.IMGFMT.base -blockdev | ||
294 | { | ||
295 | "iops_rd": 0, | ||
296 | "detect_zeroes": "off", | ||
297 | + "active": true, | ||
298 | "image": { | ||
299 | "virtual-size": 197120, | ||
300 | "filename": "TEST_DIR/t.IMGFMT.base", | ||
301 | -- | ||
302 | 2.48.1 | diff view generated by jsdifflib |
New patch | |||
---|---|---|---|
1 | What we wanted to catch with the assertion is cases where the recursion | ||
2 | finds that a child was inactive before its parent. This should never | ||
3 | happen. But if the user tries to inactivate an image that is already | ||
4 | inactive, that's harmless and we don't want to fail the assertion. | ||
1 | 5 | ||
6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
7 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
8 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
9 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
10 | Message-ID: <20250204211407.381505-3-kwolf@redhat.com> | ||
11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
12 | --- | ||
13 | block.c | 16 ++++++++++++---- | ||
14 | 1 file changed, 12 insertions(+), 4 deletions(-) | ||
15 | |||
16 | diff --git a/block.c b/block.c | ||
17 | index XXXXXXX..XXXXXXX 100644 | ||
18 | --- a/block.c | ||
19 | +++ b/block.c | ||
20 | @@ -XXX,XX +XXX,XX @@ bdrv_has_bds_parent(BlockDriverState *bs, bool only_active) | ||
21 | return false; | ||
22 | } | ||
23 | |||
24 | -static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) | ||
25 | +static int GRAPH_RDLOCK | ||
26 | +bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) | ||
27 | { | ||
28 | BdrvChild *child, *parent; | ||
29 | int ret; | ||
30 | @@ -XXX,XX +XXX,XX @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) | ||
31 | return 0; | ||
32 | } | ||
33 | |||
34 | - assert(!(bs->open_flags & BDRV_O_INACTIVE)); | ||
35 | + /* | ||
36 | + * Inactivating an already inactive node on user request is harmless, but if | ||
37 | + * a child is already inactive before its parent, that's bad. | ||
38 | + */ | ||
39 | + if (bs->open_flags & BDRV_O_INACTIVE) { | ||
40 | + assert(top_level); | ||
41 | + return 0; | ||
42 | + } | ||
43 | |||
44 | /* Inactivate this node */ | ||
45 | if (bs->drv->bdrv_inactivate) { | ||
46 | @@ -XXX,XX +XXX,XX @@ static int GRAPH_RDLOCK bdrv_inactivate_recurse(BlockDriverState *bs) | ||
47 | |||
48 | /* Recursively inactivate children */ | ||
49 | QLIST_FOREACH(child, &bs->children, next) { | ||
50 | - ret = bdrv_inactivate_recurse(child->bs); | ||
51 | + ret = bdrv_inactivate_recurse(child->bs, false); | ||
52 | if (ret < 0) { | ||
53 | return ret; | ||
54 | } | ||
55 | @@ -XXX,XX +XXX,XX @@ int bdrv_inactivate_all(void) | ||
56 | if (bdrv_has_bds_parent(bs, false)) { | ||
57 | continue; | ||
58 | } | ||
59 | - ret = bdrv_inactivate_recurse(bs); | ||
60 | + ret = bdrv_inactivate_recurse(bs, true); | ||
61 | if (ret < 0) { | ||
62 | bdrv_next_cleanup(&it); | ||
63 | break; | ||
64 | -- | ||
65 | 2.48.1 | diff view generated by jsdifflib |
1 | It's unclear what the real maximum is, but we use an uint32_t to store | 1 | Putting an active block node on top of an inactive one is strictly |
---|---|---|---|
2 | the log size in vhdx_co_create(), so we should check that the given | 2 | speaking an invalid configuration and the next patch will turn it into a |
3 | value fits in 32 bits. | 3 | hard error. |
4 | |||
5 | However, taking a snapshot while disk images are inactive after | ||
6 | completing migration has an important use case: After migrating to a | ||
7 | file, taking an external snapshot is what is needed to take a full VM | ||
8 | snapshot. | ||
9 | |||
10 | In order for this to keep working after the later patches, change | ||
11 | creating a snapshot such that it automatically inactivates an overlay | ||
12 | that is added on top of an already inactive node. | ||
4 | 13 | ||
5 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 14 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
15 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
6 | Reviewed-by: Eric Blake <eblake@redhat.com> | 16 | Reviewed-by: Eric Blake <eblake@redhat.com> |
7 | Reviewed-by: Jeff Cody <jcody@redhat.com> | 17 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
18 | Message-ID: <20250204211407.381505-4-kwolf@redhat.com> | ||
19 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
8 | --- | 20 | --- |
9 | block/vhdx.c | 4 ++++ | 21 | blockdev.c | 16 ++++++++++++++++ |
10 | 1 file changed, 4 insertions(+) | 22 | 1 file changed, 16 insertions(+) |
11 | 23 | ||
12 | diff --git a/block/vhdx.c b/block/vhdx.c | 24 | diff --git a/blockdev.c b/blockdev.c |
13 | index XXXXXXX..XXXXXXX 100644 | 25 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/block/vhdx.c | 26 | --- a/blockdev.c |
15 | +++ b/block/vhdx.c | 27 | +++ b/blockdev.c |
16 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts, | 28 | @@ -XXX,XX +XXX,XX @@ static void external_snapshot_action(TransactionAction *action, |
17 | if (!vhdx_opts->has_log_size) { | 29 | return; |
18 | log_size = DEFAULT_LOG_SIZE; | 30 | } |
19 | } else { | 31 | |
20 | + if (vhdx_opts->log_size > UINT32_MAX) { | 32 | + /* |
21 | + error_setg(errp, "Log size must be smaller than 4 GB"); | 33 | + * Older QEMU versions have allowed adding an active parent node to an |
22 | + return -EINVAL; | 34 | + * inactive child node. This is unsafe in the general case, but there is an |
35 | + * important use case, which is taking a VM snapshot with migration to file | ||
36 | + * and then adding an external snapshot while the VM is still stopped and | ||
37 | + * images are inactive. Requiring the user to explicitly create the overlay | ||
38 | + * as inactive would break compatibility, so just do it automatically here | ||
39 | + * to keep this working. | ||
40 | + */ | ||
41 | + if (bdrv_is_inactive(state->old_bs) && !bdrv_is_inactive(state->new_bs)) { | ||
42 | + ret = bdrv_inactivate(state->new_bs, errp); | ||
43 | + if (ret < 0) { | ||
44 | + return; | ||
23 | + } | 45 | + } |
24 | log_size = vhdx_opts->log_size; | 46 | + } |
25 | } | 47 | + |
26 | if (log_size < MiB || (log_size % MiB) != 0) { | 48 | ret = bdrv_append(state->new_bs, state->old_bs, errp); |
49 | if (ret < 0) { | ||
50 | return; | ||
27 | -- | 51 | -- |
28 | 2.13.6 | 52 | 2.48.1 |
29 | |||
30 | diff view generated by jsdifflib |
1 | error_setg_errno() is meant for cases where we got an errno from the OS | 1 | Block devices have an individual active state, a single global flag |
---|---|---|---|
2 | that can add useful extra information to an error message. It's | 2 | can't cover this correctly. This becomes more important as we allow |
3 | pointless if we pass a constant errno, these cases should use plain | 3 | users to manually manage which nodes are active or inactive. |
4 | error_setg(). | 4 | |
5 | Now that it's allowed to call bdrv_inactivate_all() even when some | ||
6 | nodes are already inactive, we can remove the flag and just | ||
7 | unconditionally call bdrv_inactivate_all() and, more importantly, | ||
8 | bdrv_activate_all() before we make use of the nodes. | ||
5 | 9 | ||
6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
11 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
7 | Reviewed-by: Eric Blake <eblake@redhat.com> | 12 | Reviewed-by: Eric Blake <eblake@redhat.com> |
8 | Reviewed-by: Jeff Cody <jcody@redhat.com> | 13 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
14 | Message-ID: <20250204211407.381505-5-kwolf@redhat.com> | ||
15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
9 | --- | 16 | --- |
10 | block/vhdx.c | 9 ++++----- | 17 | migration/migration.h | 3 --- |
11 | 1 file changed, 4 insertions(+), 5 deletions(-) | 18 | migration/block-active.c | 46 ---------------------------------------- |
19 | migration/migration.c | 8 ------- | ||
20 | 3 files changed, 57 deletions(-) | ||
12 | 21 | ||
13 | diff --git a/block/vhdx.c b/block/vhdx.c | 22 | diff --git a/migration/migration.h b/migration/migration.h |
14 | index XXXXXXX..XXXXXXX 100644 | 23 | index XXXXXXX..XXXXXXX 100644 |
15 | --- a/block/vhdx.c | 24 | --- a/migration/migration.h |
16 | +++ b/block/vhdx.c | 25 | +++ b/migration/migration.h |
17 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts, | 26 | @@ -XXX,XX +XXX,XX @@ void migration_bitmap_sync_precopy(bool last_stage); |
18 | /* Validate options and set default values */ | 27 | void dirty_bitmap_mig_init(void); |
19 | image_size = vhdx_opts->size; | 28 | bool should_send_vmdesc(void); |
20 | if (image_size > VHDX_MAX_IMAGE_SIZE) { | 29 | |
21 | - error_setg_errno(errp, EINVAL, "Image size too large; max of 64TB"); | 30 | -/* migration/block-active.c */ |
22 | + error_setg(errp, "Image size too large; max of 64TB"); | 31 | -void migration_block_active_setup(bool active); |
23 | return -EINVAL; | 32 | - |
33 | #endif | ||
34 | diff --git a/migration/block-active.c b/migration/block-active.c | ||
35 | index XXXXXXX..XXXXXXX 100644 | ||
36 | --- a/migration/block-active.c | ||
37 | +++ b/migration/block-active.c | ||
38 | @@ -XXX,XX +XXX,XX @@ | ||
39 | #include "qemu/error-report.h" | ||
40 | #include "trace.h" | ||
41 | |||
42 | -/* | ||
43 | - * Migration-only cache to remember the block layer activation status. | ||
44 | - * Protected by BQL. | ||
45 | - * | ||
46 | - * We need this because.. | ||
47 | - * | ||
48 | - * - Migration can fail after block devices are invalidated (during | ||
49 | - * switchover phase). When that happens, we need to be able to recover | ||
50 | - * the block drive status by re-activating them. | ||
51 | - * | ||
52 | - * - Currently bdrv_inactivate_all() is not safe to be invoked on top of | ||
53 | - * invalidated drives (even if bdrv_activate_all() is actually safe to be | ||
54 | - * called any time!). It means remembering this could help migration to | ||
55 | - * make sure it won't invalidate twice in a row, crashing QEMU. It can | ||
56 | - * happen when we migrate a PAUSED VM from host1 to host2, then migrate | ||
57 | - * again to host3 without starting it. TODO: a cleaner solution is to | ||
58 | - * allow safe invoke of bdrv_inactivate_all() at anytime, like | ||
59 | - * bdrv_activate_all(). | ||
60 | - * | ||
61 | - * For freshly started QEMU, the flag is initialized to TRUE reflecting the | ||
62 | - * scenario where QEMU owns block device ownerships. | ||
63 | - * | ||
64 | - * For incoming QEMU taking a migration stream, the flag is initialized to | ||
65 | - * FALSE reflecting that the incoming side doesn't own the block devices, | ||
66 | - * not until switchover happens. | ||
67 | - */ | ||
68 | -static bool migration_block_active; | ||
69 | - | ||
70 | -/* Setup the disk activation status */ | ||
71 | -void migration_block_active_setup(bool active) | ||
72 | -{ | ||
73 | - migration_block_active = active; | ||
74 | -} | ||
75 | - | ||
76 | bool migration_block_activate(Error **errp) | ||
77 | { | ||
78 | ERRP_GUARD(); | ||
79 | |||
80 | assert(bql_locked()); | ||
81 | |||
82 | - if (migration_block_active) { | ||
83 | - trace_migration_block_activation("active-skipped"); | ||
84 | - return true; | ||
85 | - } | ||
86 | - | ||
87 | trace_migration_block_activation("active"); | ||
88 | |||
89 | bdrv_activate_all(errp); | ||
90 | @@ -XXX,XX +XXX,XX @@ bool migration_block_activate(Error **errp) | ||
91 | return false; | ||
24 | } | 92 | } |
25 | 93 | ||
26 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts, | 94 | - migration_block_active = true; |
27 | log_size = vhdx_opts->log_size; | 95 | return true; |
96 | } | ||
97 | |||
98 | @@ -XXX,XX +XXX,XX @@ bool migration_block_inactivate(void) | ||
99 | |||
100 | assert(bql_locked()); | ||
101 | |||
102 | - if (!migration_block_active) { | ||
103 | - trace_migration_block_activation("inactive-skipped"); | ||
104 | - return true; | ||
105 | - } | ||
106 | - | ||
107 | trace_migration_block_activation("inactive"); | ||
108 | |||
109 | ret = bdrv_inactivate_all(); | ||
110 | @@ -XXX,XX +XXX,XX @@ bool migration_block_inactivate(void) | ||
111 | return false; | ||
28 | } | 112 | } |
29 | if (log_size < MiB || (log_size % MiB) != 0) { | 113 | |
30 | - error_setg_errno(errp, EINVAL, "Log size must be a multiple of 1 MB"); | 114 | - migration_block_active = false; |
31 | + error_setg(errp, "Log size must be a multiple of 1 MB"); | 115 | return true; |
32 | return -EINVAL; | 116 | } |
117 | diff --git a/migration/migration.c b/migration/migration.c | ||
118 | index XXXXXXX..XXXXXXX 100644 | ||
119 | --- a/migration/migration.c | ||
120 | +++ b/migration/migration.c | ||
121 | @@ -XXX,XX +XXX,XX @@ void qmp_migrate_incoming(const char *uri, bool has_channels, | ||
122 | return; | ||
33 | } | 123 | } |
34 | 124 | ||
35 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts, | 125 | - /* |
36 | } | 126 | - * Newly setup incoming QEMU. Mark the block active state to reflect |
37 | 127 | - * that the src currently owns the disks. | |
38 | if (block_size < MiB || (block_size % MiB) != 0) { | 128 | - */ |
39 | - error_setg_errno(errp, EINVAL, "Block size must be a multiple of 1 MB"); | 129 | - migration_block_active_setup(false); |
40 | + error_setg(errp, "Block size must be a multiple of 1 MB"); | 130 | - |
41 | return -EINVAL; | 131 | once = false; |
42 | } | 132 | } |
43 | if (!is_power_of_2(block_size)) { | 133 | |
44 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts, | 134 | @@ -XXX,XX +XXX,XX @@ static void migration_instance_init(Object *obj) |
45 | return -EINVAL; | 135 | ms->state = MIGRATION_STATUS_NONE; |
46 | } | 136 | ms->mbps = -1; |
47 | if (block_size > VHDX_BLOCK_SIZE_MAX) { | 137 | ms->pages_per_second = -1; |
48 | - error_setg_errno(errp, EINVAL, "Block size must not exceed %d", | 138 | - /* Freshly started QEMU owns all the block devices */ |
49 | - VHDX_BLOCK_SIZE_MAX); | 139 | - migration_block_active_setup(true); |
50 | + error_setg(errp, "Block size must not exceed %d", VHDX_BLOCK_SIZE_MAX); | 140 | qemu_sem_init(&ms->pause_sem, 0); |
51 | return -EINVAL; | 141 | qemu_mutex_init(&ms->error_mutex); |
52 | } | ||
53 | 142 | ||
54 | -- | 143 | -- |
55 | 2.13.6 | 144 | 2.48.1 |
56 | |||
57 | diff view generated by jsdifflib |
1 | Images with a non-power-of-two block size are invalid and cannot be | 1 | An active node makes unrestricted use of its children and would possibly |
---|---|---|---|
2 | opened. Reject such block sizes when creating an image. | 2 | run into assertion failures when it operates on an inactive child node. |
3 | 3 | ||
4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
5 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
5 | Reviewed-by: Eric Blake <eblake@redhat.com> | 6 | Reviewed-by: Eric Blake <eblake@redhat.com> |
6 | Reviewed-by: Jeff Cody <jcody@redhat.com> | 7 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
8 | Message-ID: <20250204211407.381505-6-kwolf@redhat.com> | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
7 | --- | 10 | --- |
8 | block/vhdx.c | 4 ++++ | 11 | block.c | 5 +++++ |
9 | 1 file changed, 4 insertions(+) | 12 | 1 file changed, 5 insertions(+) |
10 | 13 | ||
11 | diff --git a/block/vhdx.c b/block/vhdx.c | 14 | diff --git a/block.c b/block.c |
12 | index XXXXXXX..XXXXXXX 100644 | 15 | index XXXXXXX..XXXXXXX 100644 |
13 | --- a/block/vhdx.c | 16 | --- a/block.c |
14 | +++ b/block/vhdx.c | 17 | +++ b/block.c |
15 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vhdx_co_create(BlockdevCreateOptions *opts, | 18 | @@ -XXX,XX +XXX,XX @@ bdrv_attach_child_noperm(BlockDriverState *parent_bs, |
16 | error_setg_errno(errp, EINVAL, "Block size must be a multiple of 1 MB"); | 19 | child_bs->node_name, child_name, parent_bs->node_name); |
17 | return -EINVAL; | 20 | return NULL; |
18 | } | 21 | } |
19 | + if (!is_power_of_2(block_size)) { | 22 | + if (bdrv_is_inactive(child_bs) && !bdrv_is_inactive(parent_bs)) { |
20 | + error_setg(errp, "Block size must be a power of two"); | 23 | + error_setg(errp, "Inactive '%s' can't be a %s child of active '%s'", |
21 | + return -EINVAL; | 24 | + child_bs->node_name, child_name, parent_bs->node_name); |
25 | + return NULL; | ||
22 | + } | 26 | + } |
23 | if (block_size > VHDX_BLOCK_SIZE_MAX) { | 27 | |
24 | error_setg_errno(errp, EINVAL, "Block size must not exceed %d", | 28 | bdrv_get_cumulative_perm(parent_bs, &perm, &shared_perm); |
25 | VHDX_BLOCK_SIZE_MAX); | 29 | bdrv_child_perm(parent_bs, child_bs, NULL, child_role, NULL, |
26 | -- | 30 | -- |
27 | 2.13.6 | 31 | 2.48.1 |
28 | |||
29 | diff view generated by jsdifflib |
1 | From: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 1 | In order for block_resize to fail gracefully on an inactive node instead |
---|---|---|---|
2 | of crashing with an assertion failure in bdrv_co_write_req_prepare() | ||
3 | (called from bdrv_co_truncate()), we need to check for inactive nodes | ||
4 | also when they are attached as a root node and make sure that | ||
5 | BLK_PERM_RESIZE isn't among the permissions allowed for inactive nodes. | ||
6 | To this effect, don't enumerate the permissions that are incompatible | ||
7 | with inactive nodes any more, but allow only BLK_PERM_CONSISTENT_READ | ||
8 | for them. | ||
2 | 9 | ||
3 | The quorum driver is not a protocol so it should implement bdrv_open | 10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | instead of bdrv_file_open and not provide a protocol_name. | 11 | Acked-by: Fabiano Rosas <farosas@suse.de> |
5 | 12 | Reviewed-by: Eric Blake <eblake@redhat.com> | |
6 | Attempts to invoke this driver using protocol syntax | 13 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
7 | (i.e. quorum:<filename:options:...>) will now fail gracefully: | 14 | Message-ID: <20250204211407.381505-7-kwolf@redhat.com> |
8 | |||
9 | $ qemu-img info quorum:foo | ||
10 | qemu-img: Could not open 'quorum:foo': Unknown protocol 'quorum' | ||
11 | |||
12 | Signed-off-by: Fabiano Rosas <farosas@linux.vnet.ibm.com> | ||
13 | Reviewed-by: Max Reitz <mreitz@redhat.com> | ||
14 | Reviewed-by: Alberto Garcia <berto@igalia.com> | ||
15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
16 | --- | 16 | --- |
17 | block/quorum.c | 3 +-- | 17 | block.c | 7 +++++++ |
18 | 1 file changed, 1 insertion(+), 2 deletions(-) | 18 | block/block-backend.c | 2 +- |
19 | 2 files changed, 8 insertions(+), 1 deletion(-) | ||
19 | 20 | ||
20 | diff --git a/block/quorum.c b/block/quorum.c | 21 | diff --git a/block.c b/block.c |
21 | index XXXXXXX..XXXXXXX 100644 | 22 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/block/quorum.c | 23 | --- a/block.c |
23 | +++ b/block/quorum.c | 24 | +++ b/block.c |
24 | @@ -XXX,XX +XXX,XX @@ static void quorum_refresh_filename(BlockDriverState *bs, QDict *options) | 25 | @@ -XXX,XX +XXX,XX @@ bdrv_attach_child_common(BlockDriverState *child_bs, |
25 | 26 | assert(child_class->get_parent_desc); | |
26 | static BlockDriver bdrv_quorum = { | 27 | GLOBAL_STATE_CODE(); |
27 | .format_name = "quorum", | 28 | |
28 | - .protocol_name = "quorum", | 29 | + if (bdrv_is_inactive(child_bs) && (perm & ~BLK_PERM_CONSISTENT_READ)) { |
29 | 30 | + g_autofree char *perm_names = bdrv_perm_names(perm); | |
30 | .instance_size = sizeof(BDRVQuorumState), | 31 | + error_setg(errp, "Permission '%s' unavailable on inactive node", |
31 | 32 | + perm_names); | |
32 | - .bdrv_file_open = quorum_open, | 33 | + return NULL; |
33 | + .bdrv_open = quorum_open, | 34 | + } |
34 | .bdrv_close = quorum_close, | 35 | + |
35 | .bdrv_refresh_filename = quorum_refresh_filename, | 36 | new_child = g_new(BdrvChild, 1); |
37 | *new_child = (BdrvChild) { | ||
38 | .bs = NULL, | ||
39 | diff --git a/block/block-backend.c b/block/block-backend.c | ||
40 | index XXXXXXX..XXXXXXX 100644 | ||
41 | --- a/block/block-backend.c | ||
42 | +++ b/block/block-backend.c | ||
43 | @@ -XXX,XX +XXX,XX @@ static bool blk_can_inactivate(BlockBackend *blk) | ||
44 | * guest. For block job BBs that satisfy this, we can just allow | ||
45 | * it. This is the case for mirror job source, which is required | ||
46 | * by libvirt non-shared block migration. */ | ||
47 | - if (!(blk->perm & (BLK_PERM_WRITE | BLK_PERM_WRITE_UNCHANGED))) { | ||
48 | + if (!(blk->perm & ~BLK_PERM_CONSISTENT_READ)) { | ||
49 | return true; | ||
50 | } | ||
36 | 51 | ||
37 | -- | 52 | -- |
38 | 2.13.6 | 53 | 2.48.1 |
39 | |||
40 | diff view generated by jsdifflib |
1 | What static=on really does is what we call metadata preallocation for | 1 | In QEMU, nodes are automatically created inactive while expecting an |
---|---|---|---|
2 | other block drivers. While we can still change the QMP interface, make | 2 | incoming migration (i.e. RUN_STATE_INMIGRATE). In qemu-storage-daemon, |
3 | it more consistent by using 'preallocation' for VDI, too. | 3 | the notion of runstates doesn't exist. It also wouldn't necessarily make |
4 | sense to introduce it because a single daemon can serve multiple VMs | ||
5 | that can be in different states. | ||
4 | 6 | ||
5 | This doesn't implement any new functionality, so the only supported | 7 | Therefore, allow the user to explicitly open images as inactive with a |
6 | preallocation modes are 'off' and 'metadata' for now. | 8 | new option. The default is as before: Nodes are usually active, except |
9 | when created during RUN_STATE_INMIGRATE. | ||
7 | 10 | ||
8 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
12 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
9 | Reviewed-by: Eric Blake <eblake@redhat.com> | 13 | Reviewed-by: Eric Blake <eblake@redhat.com> |
14 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
15 | Message-ID: <20250204211407.381505-8-kwolf@redhat.com> | ||
16 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
10 | --- | 17 | --- |
11 | qapi/block-core.json | 7 +++---- | 18 | qapi/block-core.json | 6 ++++++ |
12 | block/vdi.c | 24 ++++++++++++++++++++++-- | 19 | include/block/block-common.h | 1 + |
13 | 2 files changed, 25 insertions(+), 6 deletions(-) | 20 | block.c | 9 +++++++++ |
21 | 3 files changed, 16 insertions(+) | ||
14 | 22 | ||
15 | diff --git a/qapi/block-core.json b/qapi/block-core.json | 23 | diff --git a/qapi/block-core.json b/qapi/block-core.json |
16 | index XXXXXXX..XXXXXXX 100644 | 24 | index XXXXXXX..XXXXXXX 100644 |
17 | --- a/qapi/block-core.json | 25 | --- a/qapi/block-core.json |
18 | +++ b/qapi/block-core.json | 26 | +++ b/qapi/block-core.json |
19 | @@ -XXX,XX +XXX,XX @@ | 27 | @@ -XXX,XX +XXX,XX @@ |
20 | # | 28 | # |
21 | # @file Node to create the image format on | 29 | # @cache: cache-related options |
22 | # @size Size of the virtual disk in bytes | ||
23 | -# @static Whether to create a statically (true) or | ||
24 | -# dynamically (false) allocated image | ||
25 | -# (default: false, i.e. dynamic) | ||
26 | +# @preallocation Preallocation mode for the new image (allowed values: off, | ||
27 | +# metadata; default: off) | ||
28 | # | 30 | # |
29 | # Since: 2.12 | 31 | +# @active: whether the block node should be activated (default: true). |
30 | ## | 32 | +# Having inactive block nodes is useful primarily for migration because it |
31 | { 'struct': 'BlockdevCreateOptionsVdi', | 33 | +# allows opening an image on the destination while the source is still |
32 | 'data': { 'file': 'BlockdevRef', | 34 | +# holding locks for it. (Since 10.0) |
33 | 'size': 'size', | 35 | +# |
34 | - '*static': 'bool' } } | 36 | # @read-only: whether the block device should be read-only (default: |
35 | + '*preallocation': 'PreallocMode' } } | 37 | # false). Note that some block drivers support only read-only |
36 | 38 | # access, either generally or in certain configurations. In this | |
37 | ## | 39 | @@ -XXX,XX +XXX,XX @@ |
38 | # @BlockdevVhdxSubformat: | 40 | '*node-name': 'str', |
39 | diff --git a/block/vdi.c b/block/vdi.c | 41 | '*discard': 'BlockdevDiscardOptions', |
42 | '*cache': 'BlockdevCacheOptions', | ||
43 | + '*active': 'bool', | ||
44 | '*read-only': 'bool', | ||
45 | '*auto-read-only': 'bool', | ||
46 | '*force-share': 'bool', | ||
47 | diff --git a/include/block/block-common.h b/include/block/block-common.h | ||
40 | index XXXXXXX..XXXXXXX 100644 | 48 | index XXXXXXX..XXXXXXX 100644 |
41 | --- a/block/vdi.c | 49 | --- a/include/block/block-common.h |
42 | +++ b/block/vdi.c | 50 | +++ b/include/block/block-common.h |
43 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options, | 51 | @@ -XXX,XX +XXX,XX @@ typedef enum { |
44 | int ret = 0; | 52 | #define BDRV_OPT_AUTO_READ_ONLY "auto-read-only" |
45 | uint64_t bytes = 0; | 53 | #define BDRV_OPT_DISCARD "discard" |
46 | uint32_t blocks; | 54 | #define BDRV_OPT_FORCE_SHARE "force-share" |
47 | - uint32_t image_type = VDI_TYPE_DYNAMIC; | 55 | +#define BDRV_OPT_ACTIVE "active" |
48 | + uint32_t image_type; | 56 | |
49 | VdiHeader header; | 57 | |
50 | size_t i; | 58 | #define BDRV_SECTOR_BITS 9 |
51 | size_t bmap_size; | 59 | diff --git a/block.c b/block.c |
52 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options, | 60 | index XXXXXXX..XXXXXXX 100644 |
53 | 61 | --- a/block.c | |
54 | /* Validate options and set default values */ | 62 | +++ b/block.c |
55 | bytes = vdi_opts->size; | 63 | @@ -XXX,XX +XXX,XX @@ static void update_flags_from_options(int *flags, QemuOpts *opts) |
56 | - if (vdi_opts->q_static) { | 64 | if (qemu_opt_get_bool_del(opts, BDRV_OPT_AUTO_READ_ONLY, false)) { |
57 | + | 65 | *flags |= BDRV_O_AUTO_RDONLY; |
58 | + if (!vdi_opts->has_preallocation) { | ||
59 | + vdi_opts->preallocation = PREALLOC_MODE_OFF; | ||
60 | + } | ||
61 | + switch (vdi_opts->preallocation) { | ||
62 | + case PREALLOC_MODE_OFF: | ||
63 | + image_type = VDI_TYPE_DYNAMIC; | ||
64 | + break; | ||
65 | + case PREALLOC_MODE_METADATA: | ||
66 | image_type = VDI_TYPE_STATIC; | ||
67 | + break; | ||
68 | + default: | ||
69 | + error_setg(errp, "Preallocation mode not supported for vdi"); | ||
70 | + return -EINVAL; | ||
71 | } | 66 | } |
72 | + | 67 | + |
73 | #ifndef CONFIG_VDI_STATIC_IMAGE | 68 | + if (!qemu_opt_get_bool_del(opts, BDRV_OPT_ACTIVE, true)) { |
74 | if (image_type == VDI_TYPE_STATIC) { | 69 | + *flags |= BDRV_O_INACTIVE; |
75 | ret = -ENOTSUP; | ||
76 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, | ||
77 | BlockdevCreateOptions *create_options = NULL; | ||
78 | BlockDriverState *bs_file = NULL; | ||
79 | uint64_t block_size = DEFAULT_CLUSTER_SIZE; | ||
80 | + bool is_static = false; | ||
81 | Visitor *v; | ||
82 | Error *local_err = NULL; | ||
83 | int ret; | ||
84 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, | ||
85 | goto done; | ||
86 | } | ||
87 | #endif | ||
88 | + if (qemu_opt_get_bool_del(opts, BLOCK_OPT_STATIC, false)) { | ||
89 | + is_static = true; | ||
90 | + } | 70 | + } |
91 | 71 | } | |
92 | qdict = qemu_opts_to_qdict_filtered(opts, NULL, &vdi_create_opts, true); | 72 | |
93 | 73 | static void update_options_from_flags(QDict *options, int flags) | |
94 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vdi_co_create_opts(const char *filename, QemuOpts *opts, | 74 | @@ -XXX,XX +XXX,XX @@ QemuOptsList bdrv_runtime_opts = { |
95 | 75 | .type = QEMU_OPT_BOOL, | |
96 | qdict_put_str(qdict, "driver", "vdi"); | 76 | .help = "Ignore flush requests", |
97 | qdict_put_str(qdict, "file", bs_file->node_name); | 77 | }, |
98 | + if (is_static) { | 78 | + { |
99 | + qdict_put_str(qdict, "preallocation", "metadata"); | 79 | + .name = BDRV_OPT_ACTIVE, |
100 | + } | 80 | + .type = QEMU_OPT_BOOL, |
101 | 81 | + .help = "Node is activated", | |
102 | /* Get the QAPI object */ | 82 | + }, |
103 | v = qobject_input_visitor_new_keyval(QOBJECT(qdict)); | 83 | { |
84 | .name = BDRV_OPT_READ_ONLY, | ||
85 | .type = QEMU_OPT_BOOL, | ||
104 | -- | 86 | -- |
105 | 2.13.6 | 87 | 2.48.1 |
106 | |||
107 | diff view generated by jsdifflib |
1 | The system emulator tries to automatically activate and inactivate block | ||
---|---|---|---|
2 | nodes at the right point during migration. However, there are still | ||
3 | cases where it's necessary that the user can do this manually. | ||
4 | |||
5 | Images are only activated on the destination VM of a migration when the | ||
6 | VM is actually resumed. If the VM was paused, this doesn't happen | ||
7 | automatically. The user may want to perform some operation on a block | ||
8 | device (e.g. taking a snapshot or starting a block job) without also | ||
9 | resuming the VM yet. This is an example where a manual command is | ||
10 | necessary. | ||
11 | |||
12 | Another example is VM migration when the image files are opened by an | ||
13 | external qemu-storage-daemon instance on each side. In this case, the | ||
14 | process that needs to hand over the images isn't even part of the | ||
15 | migration and can't know when the migration completes. Management tools | ||
16 | need a way to explicitly inactivate images on the source and activate | ||
17 | them on the destination. | ||
18 | |||
19 | This adds a new blockdev-set-active QMP command that lets the user | ||
20 | change the status of individual nodes (this is necessary in | ||
21 | qemu-storage-daemon because it could be serving multiple VMs and only | ||
22 | one of them migrates at a time). For convenience, operating on all | ||
23 | devices (like QEMU does automatically during migration) is offered as an | ||
24 | option, too, and can be used in the context of single VM. | ||
25 | |||
26 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
27 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
28 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
29 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
30 | Message-ID: <20250204211407.381505-9-kwolf@redhat.com> | ||
1 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 31 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
2 | --- | 32 | --- |
3 | tests/qemu-iotests/213 | 349 +++++++++++++++++++++++++++++++++++++++++++++ | 33 | qapi/block-core.json | 32 ++++++++++++++++++++++++++++++ |
4 | tests/qemu-iotests/213.out | 121 ++++++++++++++++ | 34 | include/block/block-global-state.h | 3 +++ |
5 | tests/qemu-iotests/group | 1 + | 35 | block.c | 21 ++++++++++++++++++++ |
6 | 3 files changed, 471 insertions(+) | 36 | blockdev.c | 32 ++++++++++++++++++++++++++++++ |
7 | create mode 100755 tests/qemu-iotests/213 | 37 | 4 files changed, 88 insertions(+) |
8 | create mode 100644 tests/qemu-iotests/213.out | ||
9 | 38 | ||
10 | diff --git a/tests/qemu-iotests/213 b/tests/qemu-iotests/213 | 39 | diff --git a/qapi/block-core.json b/qapi/block-core.json |
11 | new file mode 100755 | 40 | index XXXXXXX..XXXXXXX 100644 |
12 | index XXXXXXX..XXXXXXX | 41 | --- a/qapi/block-core.json |
13 | --- /dev/null | 42 | +++ b/qapi/block-core.json |
14 | +++ b/tests/qemu-iotests/213 | ||
15 | @@ -XXX,XX +XXX,XX @@ | 43 | @@ -XXX,XX +XXX,XX @@ |
16 | +#!/bin/bash | 44 | { 'command': 'blockdev-del', 'data': { 'node-name': 'str' }, |
45 | 'allow-preconfig': true } | ||
46 | |||
47 | +## | ||
48 | +# @blockdev-set-active: | ||
17 | +# | 49 | +# |
18 | +# Test vhdx and file image creation | 50 | +# Activate or inactivate a block device. Use this to manage the handover of |
51 | +# block devices on migration with qemu-storage-daemon. | ||
19 | +# | 52 | +# |
20 | +# Copyright (C) 2018 Red Hat, Inc. | 53 | +# Activating a node automatically activates all of its child nodes first. |
54 | +# Inactivating a node automatically inactivates any of its child nodes that are | ||
55 | +# not in use by a still active node. | ||
21 | +# | 56 | +# |
22 | +# This program is free software; you can redistribute it and/or modify | 57 | +# @node-name: Name of the graph node to activate or inactivate. By default, all |
23 | +# it under the terms of the GNU General Public License as published by | 58 | +# nodes are affected by the operation. |
24 | +# the Free Software Foundation; either version 2 of the License, or | ||
25 | +# (at your option) any later version. | ||
26 | +# | 59 | +# |
27 | +# This program is distributed in the hope that it will be useful, | 60 | +# @active: true if the nodes should be active when the command returns success, |
28 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of | 61 | +# false if they should be inactive. |
29 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
30 | +# GNU General Public License for more details. | ||
31 | +# | 62 | +# |
32 | +# You should have received a copy of the GNU General Public License | 63 | +# Since: 10.0 |
33 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
34 | +# | 64 | +# |
65 | +# .. qmp-example:: | ||
66 | +# | ||
67 | +# -> { "execute": "blockdev-set-active", | ||
68 | +# "arguments": { | ||
69 | +# "node-name": "node0", | ||
70 | +# "active": false | ||
71 | +# } | ||
72 | +# } | ||
73 | +# <- { "return": {} } | ||
74 | +## | ||
75 | +{ 'command': 'blockdev-set-active', | ||
76 | + 'data': { '*node-name': 'str', 'active': 'bool' }, | ||
77 | + 'allow-preconfig': true } | ||
35 | + | 78 | + |
36 | +# creator | 79 | ## |
37 | +owner=kwolf@redhat.com | 80 | # @BlockdevCreateOptionsFile: |
81 | # | ||
82 | diff --git a/include/block/block-global-state.h b/include/block/block-global-state.h | ||
83 | index XXXXXXX..XXXXXXX 100644 | ||
84 | --- a/include/block/block-global-state.h | ||
85 | +++ b/include/block/block-global-state.h | ||
86 | @@ -XXX,XX +XXX,XX @@ bdrv_activate(BlockDriverState *bs, Error **errp); | ||
87 | int coroutine_fn no_co_wrapper_bdrv_rdlock | ||
88 | bdrv_co_activate(BlockDriverState *bs, Error **errp); | ||
89 | |||
90 | +int no_coroutine_fn | ||
91 | +bdrv_inactivate(BlockDriverState *bs, Error **errp); | ||
38 | + | 92 | + |
39 | +seq=`basename $0` | 93 | void bdrv_activate_all(Error **errp); |
40 | +echo "QA output created by $seq" | 94 | int bdrv_inactivate_all(void); |
95 | |||
96 | diff --git a/block.c b/block.c | ||
97 | index XXXXXXX..XXXXXXX 100644 | ||
98 | --- a/block.c | ||
99 | +++ b/block.c | ||
100 | @@ -XXX,XX +XXX,XX @@ bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) | ||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | +int bdrv_inactivate(BlockDriverState *bs, Error **errp) | ||
105 | +{ | ||
106 | + int ret; | ||
41 | + | 107 | + |
42 | +here=`pwd` | 108 | + GLOBAL_STATE_CODE(); |
43 | +status=1 # failure is the default! | 109 | + GRAPH_RDLOCK_GUARD_MAINLOOP(); |
44 | + | 110 | + |
45 | +# get standard environment, filters and checks | 111 | + if (bdrv_has_bds_parent(bs, true)) { |
46 | +. ./common.rc | 112 | + error_setg(errp, "Node has active parent node"); |
47 | +. ./common.filter | 113 | + return -EPERM; |
114 | + } | ||
48 | + | 115 | + |
49 | +_supported_fmt vhdx | 116 | + ret = bdrv_inactivate_recurse(bs, true); |
50 | +_supported_proto file | 117 | + if (ret < 0) { |
51 | +_supported_os Linux | 118 | + error_setg_errno(errp, -ret, "Failed to inactivate node"); |
119 | + return ret; | ||
120 | + } | ||
52 | + | 121 | + |
53 | +function do_run_qemu() | 122 | + return 0; |
54 | +{ | ||
55 | + echo Testing: "$@" | ||
56 | + $QEMU -nographic -qmp stdio -serial none "$@" | ||
57 | + echo | ||
58 | +} | 123 | +} |
59 | + | 124 | + |
60 | +function run_qemu() | 125 | int bdrv_inactivate_all(void) |
126 | { | ||
127 | BlockDriverState *bs = NULL; | ||
128 | diff --git a/blockdev.c b/blockdev.c | ||
129 | index XXXXXXX..XXXXXXX 100644 | ||
130 | --- a/blockdev.c | ||
131 | +++ b/blockdev.c | ||
132 | @@ -XXX,XX +XXX,XX @@ void qmp_blockdev_del(const char *node_name, Error **errp) | ||
133 | bdrv_unref(bs); | ||
134 | } | ||
135 | |||
136 | +void qmp_blockdev_set_active(const char *node_name, bool active, Error **errp) | ||
61 | +{ | 137 | +{ |
62 | + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp \ | 138 | + int ret; |
63 | + | _filter_qemu | _filter_imgfmt \ | 139 | + |
64 | + | _filter_actual_image_size | 140 | + GLOBAL_STATE_CODE(); |
141 | + GRAPH_RDLOCK_GUARD_MAINLOOP(); | ||
142 | + | ||
143 | + if (!node_name) { | ||
144 | + if (active) { | ||
145 | + bdrv_activate_all(errp); | ||
146 | + } else { | ||
147 | + ret = bdrv_inactivate_all(); | ||
148 | + if (ret < 0) { | ||
149 | + error_setg_errno(errp, -ret, "Failed to inactivate all nodes"); | ||
150 | + } | ||
151 | + } | ||
152 | + } else { | ||
153 | + BlockDriverState *bs = bdrv_find_node(node_name); | ||
154 | + if (!bs) { | ||
155 | + error_setg(errp, "Failed to find node with node-name='%s'", | ||
156 | + node_name); | ||
157 | + return; | ||
158 | + } | ||
159 | + | ||
160 | + if (active) { | ||
161 | + bdrv_activate(bs, errp); | ||
162 | + } else { | ||
163 | + bdrv_inactivate(bs, errp); | ||
164 | + } | ||
165 | + } | ||
65 | +} | 166 | +} |
66 | + | 167 | + |
67 | +echo | 168 | static BdrvChild * GRAPH_RDLOCK |
68 | +echo "=== Successful image creation (defaults) ===" | 169 | bdrv_find_child(BlockDriverState *parent_bs, const char *child_name) |
69 | +echo | 170 | { |
70 | + | ||
71 | +size=$((128 * 1024 * 1024)) | ||
72 | + | ||
73 | +run_qemu <<EOF | ||
74 | +{ "execute": "qmp_capabilities" } | ||
75 | +{ "execute": "x-blockdev-create", | ||
76 | + "arguments": { | ||
77 | + "driver": "file", | ||
78 | + "filename": "$TEST_IMG", | ||
79 | + "size": 0 | ||
80 | + } | ||
81 | +} | ||
82 | +{ "execute": "blockdev-add", | ||
83 | + "arguments": { | ||
84 | + "driver": "file", | ||
85 | + "node-name": "imgfile", | ||
86 | + "filename": "$TEST_IMG" | ||
87 | + } | ||
88 | +} | ||
89 | +{ "execute": "x-blockdev-create", | ||
90 | + "arguments": { | ||
91 | + "driver": "$IMGFMT", | ||
92 | + "file": "imgfile", | ||
93 | + "size": $size | ||
94 | + } | ||
95 | +} | ||
96 | +{ "execute": "quit" } | ||
97 | +EOF | ||
98 | + | ||
99 | +_img_info --format-specific | _filter_img_info --format-specific | ||
100 | + | ||
101 | +echo | ||
102 | +echo "=== Successful image creation (explicit defaults) ===" | ||
103 | +echo | ||
104 | + | ||
105 | +# Choose a different size to show that we got a new image | ||
106 | +size=$((64 * 1024 * 1024)) | ||
107 | + | ||
108 | +run_qemu <<EOF | ||
109 | +{ "execute": "qmp_capabilities" } | ||
110 | +{ "execute": "x-blockdev-create", | ||
111 | + "arguments": { | ||
112 | + "driver": "file", | ||
113 | + "filename": "$TEST_IMG", | ||
114 | + "size": 0 | ||
115 | + } | ||
116 | +} | ||
117 | +{ "execute": "x-blockdev-create", | ||
118 | + "arguments": { | ||
119 | + "driver": "$IMGFMT", | ||
120 | + "file": { | ||
121 | + "driver": "file", | ||
122 | + "filename": "$TEST_IMG" | ||
123 | + }, | ||
124 | + "size": $size, | ||
125 | + "log-size": 1048576, | ||
126 | + "block-size": 8388608, | ||
127 | + "subformat": "dynamic", | ||
128 | + "block-state-zero": true | ||
129 | + } | ||
130 | +} | ||
131 | +{ "execute": "quit" } | ||
132 | +EOF | ||
133 | + | ||
134 | +_img_info --format-specific | _filter_img_info --format-specific | ||
135 | + | ||
136 | +echo | ||
137 | +echo "=== Successful image creation (with non-default options) ===" | ||
138 | +echo | ||
139 | + | ||
140 | +# Choose a different size to show that we got a new image | ||
141 | +size=$((32 * 1024 * 1024)) | ||
142 | + | ||
143 | +run_qemu <<EOF | ||
144 | +{ "execute": "qmp_capabilities" } | ||
145 | +{ "execute": "x-blockdev-create", | ||
146 | + "arguments": { | ||
147 | + "driver": "file", | ||
148 | + "filename": "$TEST_IMG", | ||
149 | + "size": 0 | ||
150 | + } | ||
151 | +} | ||
152 | +{ "execute": "x-blockdev-create", | ||
153 | + "arguments": { | ||
154 | + "driver": "$IMGFMT", | ||
155 | + "file": { | ||
156 | + "driver": "file", | ||
157 | + "filename": "$TEST_IMG" | ||
158 | + }, | ||
159 | + "size": $size, | ||
160 | + "log-size": 8388608, | ||
161 | + "block-size": 268435456, | ||
162 | + "subformat": "fixed", | ||
163 | + "block-state-zero": false | ||
164 | + } | ||
165 | +} | ||
166 | +{ "execute": "quit" } | ||
167 | +EOF | ||
168 | + | ||
169 | +_img_info --format-specific | _filter_img_info --format-specific | ||
170 | + | ||
171 | +echo | ||
172 | +echo "=== Invalid BlockdevRef ===" | ||
173 | +echo | ||
174 | + | ||
175 | +run_qemu <<EOF | ||
176 | +{ "execute": "qmp_capabilities" } | ||
177 | +{ "execute": "x-blockdev-create", | ||
178 | + "arguments": { | ||
179 | + "driver": "$IMGFMT", | ||
180 | + "file": "this doesn't exist", | ||
181 | + "size": $size | ||
182 | + } | ||
183 | +} | ||
184 | +{ "execute": "quit" } | ||
185 | +EOF | ||
186 | + | ||
187 | +echo | ||
188 | +echo "=== Zero size ===" | ||
189 | +echo | ||
190 | + | ||
191 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
192 | +{ "execute": "qmp_capabilities" } | ||
193 | +{ "execute": "x-blockdev-create", | ||
194 | + "arguments": { | ||
195 | + "driver": "$IMGFMT", | ||
196 | + "file": "node0", | ||
197 | + "size": 0 | ||
198 | + } | ||
199 | +} | ||
200 | +{ "execute": "quit" } | ||
201 | +EOF | ||
202 | + | ||
203 | +_img_info | _filter_img_info | ||
204 | + | ||
205 | +echo | ||
206 | +echo "=== Maximum size ===" | ||
207 | +echo | ||
208 | + | ||
209 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
210 | +{ "execute": "qmp_capabilities" } | ||
211 | +{ "execute": "x-blockdev-create", | ||
212 | + "arguments": { | ||
213 | + "driver": "$IMGFMT", | ||
214 | + "file": "node0", | ||
215 | + "size": 70368744177664 | ||
216 | + } | ||
217 | +} | ||
218 | +{ "execute": "quit" } | ||
219 | +EOF | ||
220 | + | ||
221 | +_img_info | _filter_img_info | ||
222 | + | ||
223 | +echo | ||
224 | +echo "=== Invalid sizes ===" | ||
225 | +echo | ||
226 | + | ||
227 | +# TODO Negative image sizes aren't handled correctly, but this is a problem | ||
228 | +# with QAPI's implementation of the 'size' type and affects other commands as | ||
229 | +# well. Once this is fixed, we may want to add a test case here. | ||
230 | + | ||
231 | +# 1. 2^64 - 512 | ||
232 | +# 2. 2^63 = 8 EB (qemu-img enforces image sizes less than this) | ||
233 | +# 3. 2^63 - 512 (generally valid, but with the image header the file will | ||
234 | +# exceed 63 bits) | ||
235 | +# 4. 2^46 + 1 (one byte more than maximum image size) | ||
236 | + | ||
237 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
238 | +{ "execute": "qmp_capabilities" } | ||
239 | +{ "execute": "x-blockdev-create", | ||
240 | + "arguments": { | ||
241 | + "driver": "$IMGFMT", | ||
242 | + "file": "node0", | ||
243 | + "size": 18446744073709551104 | ||
244 | + } | ||
245 | +} | ||
246 | +{ "execute": "x-blockdev-create", | ||
247 | + "arguments": { | ||
248 | + "driver": "$IMGFMT", | ||
249 | + "file": "node0", | ||
250 | + "size": 9223372036854775808 | ||
251 | + } | ||
252 | +} | ||
253 | +{ "execute": "x-blockdev-create", | ||
254 | + "arguments": { | ||
255 | + "driver": "$IMGFMT", | ||
256 | + "file": "node0", | ||
257 | + "size": 9223372036854775296 | ||
258 | + } | ||
259 | +} | ||
260 | +{ "execute": "x-blockdev-create", | ||
261 | + "arguments": { | ||
262 | + "driver": "$IMGFMT", | ||
263 | + "file": "node0", | ||
264 | + "size": 70368744177665 | ||
265 | + } | ||
266 | +} | ||
267 | +{ "execute": "quit" } | ||
268 | +EOF | ||
269 | + | ||
270 | +echo | ||
271 | +echo "=== Invalid block size ===" | ||
272 | +echo | ||
273 | + | ||
274 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
275 | +{ "execute": "qmp_capabilities" } | ||
276 | +{ "execute": "x-blockdev-create", | ||
277 | + "arguments": { | ||
278 | + "driver": "$IMGFMT", | ||
279 | + "file": "node0", | ||
280 | + "size": 67108864, | ||
281 | + "block-size": 1234567 | ||
282 | + } | ||
283 | +} | ||
284 | +{ "execute": "x-blockdev-create", | ||
285 | + "arguments": { | ||
286 | + "driver": "$IMGFMT", | ||
287 | + "file": "node0", | ||
288 | + "size": 67108864, | ||
289 | + "block-size": 128 | ||
290 | + } | ||
291 | +} | ||
292 | +{ "execute": "x-blockdev-create", | ||
293 | + "arguments": { | ||
294 | + "driver": "$IMGFMT", | ||
295 | + "file": "node0", | ||
296 | + "size": 67108864, | ||
297 | + "block-size": 3145728 | ||
298 | + } | ||
299 | +} | ||
300 | +{ "execute": "x-blockdev-create", | ||
301 | + "arguments": { | ||
302 | + "driver": "$IMGFMT", | ||
303 | + "file": "node0", | ||
304 | + "size": 67108864, | ||
305 | + "block-size": 536870912 | ||
306 | + } | ||
307 | +} | ||
308 | +{ "execute": "x-blockdev-create", | ||
309 | + "arguments": { | ||
310 | + "driver": "$IMGFMT", | ||
311 | + "file": "node0", | ||
312 | + "size": 67108864, | ||
313 | + "block-size": 0 | ||
314 | + } | ||
315 | +} | ||
316 | +{ "execute": "quit" } | ||
317 | +EOF | ||
318 | + | ||
319 | +echo | ||
320 | +echo "=== Invalid log size ===" | ||
321 | +echo | ||
322 | + | ||
323 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
324 | +{ "execute": "qmp_capabilities" } | ||
325 | +{ "execute": "x-blockdev-create", | ||
326 | + "arguments": { | ||
327 | + "driver": "$IMGFMT", | ||
328 | + "file": "node0", | ||
329 | + "size": 67108864, | ||
330 | + "log-size": 1234567 | ||
331 | + } | ||
332 | +} | ||
333 | +{ "execute": "x-blockdev-create", | ||
334 | + "arguments": { | ||
335 | + "driver": "$IMGFMT", | ||
336 | + "file": "node0", | ||
337 | + "size": 67108864, | ||
338 | + "log-size": 128 | ||
339 | + } | ||
340 | +} | ||
341 | +{ "execute": "x-blockdev-create", | ||
342 | + "arguments": { | ||
343 | + "driver": "$IMGFMT", | ||
344 | + "file": "node0", | ||
345 | + "size": 67108864, | ||
346 | + "log-size": 4294967296 | ||
347 | + } | ||
348 | +} | ||
349 | +{ "execute": "x-blockdev-create", | ||
350 | + "arguments": { | ||
351 | + "driver": "$IMGFMT", | ||
352 | + "file": "node0", | ||
353 | + "size": 67108864, | ||
354 | + "log-size": 0 | ||
355 | + } | ||
356 | +} | ||
357 | +{ "execute": "quit" } | ||
358 | +EOF | ||
359 | + | ||
360 | + | ||
361 | +# success, all done | ||
362 | +echo "*** done" | ||
363 | +rm -f $seq.full | ||
364 | +status=0 | ||
365 | diff --git a/tests/qemu-iotests/213.out b/tests/qemu-iotests/213.out | ||
366 | new file mode 100644 | ||
367 | index XXXXXXX..XXXXXXX | ||
368 | --- /dev/null | ||
369 | +++ b/tests/qemu-iotests/213.out | ||
370 | @@ -XXX,XX +XXX,XX @@ | ||
371 | +QA output created by 213 | ||
372 | + | ||
373 | +=== Successful image creation (defaults) === | ||
374 | + | ||
375 | +Testing: | ||
376 | +QMP_VERSION | ||
377 | +{"return": {}} | ||
378 | +{"return": {}} | ||
379 | +{"return": {}} | ||
380 | +{"return": {}} | ||
381 | +{"return": {}} | ||
382 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
383 | + | ||
384 | +image: TEST_DIR/t.IMGFMT | ||
385 | +file format: IMGFMT | ||
386 | +virtual size: 128M (134217728 bytes) | ||
387 | + | ||
388 | +=== Successful image creation (explicit defaults) === | ||
389 | + | ||
390 | +Testing: | ||
391 | +QMP_VERSION | ||
392 | +{"return": {}} | ||
393 | +{"return": {}} | ||
394 | +{"return": {}} | ||
395 | +{"return": {}} | ||
396 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
397 | + | ||
398 | +image: TEST_DIR/t.IMGFMT | ||
399 | +file format: IMGFMT | ||
400 | +virtual size: 64M (67108864 bytes) | ||
401 | + | ||
402 | +=== Successful image creation (with non-default options) === | ||
403 | + | ||
404 | +Testing: | ||
405 | +QMP_VERSION | ||
406 | +{"return": {}} | ||
407 | +{"return": {}} | ||
408 | +{"return": {}} | ||
409 | +{"return": {}} | ||
410 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
411 | + | ||
412 | +image: TEST_DIR/t.IMGFMT | ||
413 | +file format: IMGFMT | ||
414 | +virtual size: 32M (33554432 bytes) | ||
415 | + | ||
416 | +=== Invalid BlockdevRef === | ||
417 | + | ||
418 | +Testing: | ||
419 | +QMP_VERSION | ||
420 | +{"return": {}} | ||
421 | +{"error": {"class": "GenericError", "desc": "Cannot find device=this doesn't exist nor node_name=this doesn't exist"}} | ||
422 | +{"return": {}} | ||
423 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
424 | + | ||
425 | + | ||
426 | +=== Zero size === | ||
427 | + | ||
428 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
429 | +QMP_VERSION | ||
430 | +{"return": {}} | ||
431 | +{"return": {}} | ||
432 | +{"return": {}} | ||
433 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
434 | + | ||
435 | +image: TEST_DIR/t.IMGFMT | ||
436 | +file format: IMGFMT | ||
437 | +virtual size: 0 (0 bytes) | ||
438 | + | ||
439 | +=== Maximum size === | ||
440 | + | ||
441 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
442 | +QMP_VERSION | ||
443 | +{"return": {}} | ||
444 | +{"return": {}} | ||
445 | +{"return": {}} | ||
446 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
447 | + | ||
448 | +image: TEST_DIR/t.IMGFMT | ||
449 | +file format: IMGFMT | ||
450 | +virtual size: 64T (70368744177664 bytes) | ||
451 | + | ||
452 | +=== Invalid sizes === | ||
453 | + | ||
454 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
455 | +QMP_VERSION | ||
456 | +{"return": {}} | ||
457 | +{"error": {"class": "GenericError", "desc": "Image size too large; max of 64TB"}} | ||
458 | +{"error": {"class": "GenericError", "desc": "Image size too large; max of 64TB"}} | ||
459 | +{"error": {"class": "GenericError", "desc": "Image size too large; max of 64TB"}} | ||
460 | +{"error": {"class": "GenericError", "desc": "Image size too large; max of 64TB"}} | ||
461 | +{"return": {}} | ||
462 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
463 | + | ||
464 | + | ||
465 | +=== Invalid block size === | ||
466 | + | ||
467 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
468 | +QMP_VERSION | ||
469 | +{"return": {}} | ||
470 | +{"error": {"class": "GenericError", "desc": "Block size must be a multiple of 1 MB"}} | ||
471 | +{"error": {"class": "GenericError", "desc": "Block size must be a multiple of 1 MB"}} | ||
472 | +{"error": {"class": "GenericError", "desc": "Block size must be a power of two"}} | ||
473 | +{"error": {"class": "GenericError", "desc": "Block size must not exceed 268435456"}} | ||
474 | +{"error": {"class": "GenericError", "desc": "Block size must be a multiple of 1 MB"}} | ||
475 | +{"return": {}} | ||
476 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
477 | + | ||
478 | + | ||
479 | +=== Invalid log size === | ||
480 | + | ||
481 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
482 | +QMP_VERSION | ||
483 | +{"return": {}} | ||
484 | +{"error": {"class": "GenericError", "desc": "Log size must be a multiple of 1 MB"}} | ||
485 | +{"error": {"class": "GenericError", "desc": "Log size must be a multiple of 1 MB"}} | ||
486 | +{"error": {"class": "GenericError", "desc": "Log size must be smaller than 4 GB"}} | ||
487 | +{"error": {"class": "GenericError", "desc": "Log size must be a multiple of 1 MB"}} | ||
488 | +{"return": {}} | ||
489 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
490 | + | ||
491 | +*** done | ||
492 | diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group | ||
493 | index XXXXXXX..XXXXXXX 100644 | ||
494 | --- a/tests/qemu-iotests/group | ||
495 | +++ b/tests/qemu-iotests/group | ||
496 | @@ -XXX,XX +XXX,XX @@ | ||
497 | 210 rw auto | ||
498 | 211 rw auto quick | ||
499 | 212 rw auto quick | ||
500 | +213 rw auto quick | ||
501 | -- | 171 | -- |
502 | 2.13.6 | 172 | 2.48.1 |
503 | |||
504 | diff view generated by jsdifflib |
1 | This tests that the .bdrv_truncate implementation for luks doesn't crash | 1 | Device models have a relatively complex way to set up their block |
---|---|---|---|
2 | for invalid image sizes. | 2 | backends, in which blk_attach_dev() sets blk->disable_perm = true. |
3 | We want to support inactive images in exports, too, so that | ||
4 | qemu-storage-daemon can be used with migration. Because they don't use | ||
5 | blk_attach_dev(), they need another way to set this flag. The most | ||
6 | convenient is to do this automatically when an inactive node is attached | ||
7 | to a BlockBackend that can be inactivated. | ||
3 | 8 | ||
4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
10 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
5 | Reviewed-by: Eric Blake <eblake@redhat.com> | 11 | Reviewed-by: Eric Blake <eblake@redhat.com> |
12 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
13 | Message-ID: <20250204211407.381505-10-kwolf@redhat.com> | ||
14 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
6 | --- | 15 | --- |
7 | tests/qemu-iotests/210 | 37 +++++++++++++++++++++++++++++++++++++ | 16 | block/block-backend.c | 14 ++++++++++++-- |
8 | tests/qemu-iotests/210.out | 16 ++++++++++++++++ | 17 | 1 file changed, 12 insertions(+), 2 deletions(-) |
9 | 2 files changed, 53 insertions(+) | ||
10 | 18 | ||
11 | diff --git a/tests/qemu-iotests/210 b/tests/qemu-iotests/210 | 19 | diff --git a/block/block-backend.c b/block/block-backend.c |
12 | index XXXXXXX..XXXXXXX 100755 | 20 | index XXXXXXX..XXXXXXX 100644 |
13 | --- a/tests/qemu-iotests/210 | 21 | --- a/block/block-backend.c |
14 | +++ b/tests/qemu-iotests/210 | 22 | +++ b/block/block-backend.c |
15 | @@ -XXX,XX +XXX,XX @@ run_qemu -blockdev driver=file,filename="$TEST_IMG_FILE",node-name=node0 \ | 23 | @@ -XXX,XX +XXX,XX @@ void blk_remove_bs(BlockBackend *blk) |
16 | { "execute": "quit" } | 24 | int blk_insert_bs(BlockBackend *blk, BlockDriverState *bs, Error **errp) |
17 | EOF | 25 | { |
18 | 26 | ThrottleGroupMember *tgm = &blk->public.throttle_group_member; | |
19 | +echo | 27 | + uint64_t perm, shared_perm; |
20 | +echo "=== Resize image with invalid sizes ===" | 28 | |
21 | +echo | 29 | GLOBAL_STATE_CODE(); |
30 | bdrv_ref(bs); | ||
31 | bdrv_graph_wrlock(); | ||
22 | + | 32 | + |
23 | +run_qemu -blockdev driver=file,filename="$TEST_IMG_FILE",node-name=node0 \ | 33 | + if ((bs->open_flags & BDRV_O_INACTIVE) && blk_can_inactivate(blk)) { |
24 | + -blockdev driver=luks,file=node0,key-secret=keysec0,node-name=node1 \ | 34 | + blk->disable_perm = true; |
25 | + -object secret,id=keysec0,data="foo" <<EOF | 35 | + perm = 0; |
26 | +{ "execute": "qmp_capabilities" } | 36 | + shared_perm = BLK_PERM_ALL; |
27 | +{ "execute": "block_resize", | 37 | + } else { |
28 | + "arguments": { | 38 | + perm = blk->perm; |
29 | + "node-name": "node1", | 39 | + shared_perm = blk->shared_perm; |
30 | + "size": 9223372036854775296 | 40 | + } |
31 | + } | ||
32 | +} | ||
33 | +{ "execute": "block_resize", | ||
34 | + "arguments": { | ||
35 | + "node-name": "node1", | ||
36 | + "size": 9223372036854775808 | ||
37 | + } | ||
38 | +} | ||
39 | +{ "execute": "block_resize", | ||
40 | + "arguments": { | ||
41 | + "node-name": "node1", | ||
42 | + "size": 18446744073709551104 | ||
43 | + } | ||
44 | +} | ||
45 | +{ "execute": "block_resize", | ||
46 | + "arguments": { | ||
47 | + "node-name": "node1", | ||
48 | + "size": -9223372036854775808 | ||
49 | + } | ||
50 | +} | ||
51 | +{ "execute": "quit" } | ||
52 | +EOF | ||
53 | + | 41 | + |
54 | +_img_info | _filter_img_info | 42 | blk->root = bdrv_root_attach_child(bs, "root", &child_root, |
55 | + | 43 | BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY, |
56 | # success, all done | 44 | - blk->perm, blk->shared_perm, |
57 | echo "*** done" | 45 | - blk, errp); |
58 | rm -f $seq.full | 46 | + perm, shared_perm, blk, errp); |
59 | diff --git a/tests/qemu-iotests/210.out b/tests/qemu-iotests/210.out | 47 | bdrv_graph_wrunlock(); |
60 | index XXXXXXX..XXXXXXX 100644 | 48 | if (blk->root == NULL) { |
61 | --- a/tests/qemu-iotests/210.out | 49 | return -EPERM; |
62 | +++ b/tests/qemu-iotests/210.out | ||
63 | @@ -XXX,XX +XXX,XX @@ QMP_VERSION | ||
64 | {"return": {}} | ||
65 | {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
66 | |||
67 | + | ||
68 | +=== Resize image with invalid sizes === | ||
69 | + | ||
70 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 -blockdev driver=IMGFMT,file=node0,key-secret=keysec0,node-name=node1 -object secret,id=keysec0,data=foo | ||
71 | +QMP_VERSION | ||
72 | +{"return": {}} | ||
73 | +{"error": {"class": "GenericError", "desc": "The requested file size is too large"}} | ||
74 | +{"error": {"class": "GenericError", "desc": "Invalid parameter type for 'size', expected: integer"}} | ||
75 | +{"error": {"class": "GenericError", "desc": "Invalid parameter type for 'size', expected: integer"}} | ||
76 | +{"error": {"class": "GenericError", "desc": "Parameter 'size' expects a >0 size"}} | ||
77 | +{"return": {}} | ||
78 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
79 | + | ||
80 | +image: json:{"driver": "IMGFMT", "file": {"driver": "file", "filename": "TEST_DIR/t.IMGFMT"}, "key-secret": "keysec0"} | ||
81 | +file format: IMGFMT | ||
82 | +virtual size: 0 (0 bytes) | ||
83 | *** done | ||
84 | -- | 50 | -- |
85 | 2.13.6 | 51 | 2.48.1 |
86 | |||
87 | diff view generated by jsdifflib |
1 | Commit e39e959e fixed an invalid assertion in the .bdrv_length | 1 | Currently, block exports can't handle inactive images correctly. |
---|---|---|---|
2 | implementation, but left a similar assertion in place for | 2 | Incoming write requests would run into assertion failures. Make sure |
3 | .bdrv_truncate. Instead of crashing when the user requests a too large | 3 | that we return an error when creating an export can't activate the |
4 | image size, fail gracefully. | 4 | image. |
5 | |||
6 | A file size of exactly INT64_MAX caused failure before, but is actually | ||
7 | legal. | ||
8 | 5 | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
7 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
10 | Reviewed-by: Eric Blake <eblake@redhat.com> | 8 | Reviewed-by: Eric Blake <eblake@redhat.com> |
11 | Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> | 9 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
10 | Message-ID: <20250204211407.381505-11-kwolf@redhat.com> | ||
11 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
12 | --- | 12 | --- |
13 | block/crypto.c | 6 +++++- | 13 | block/export/export.c | 6 +++++- |
14 | 1 file changed, 5 insertions(+), 1 deletion(-) | 14 | 1 file changed, 5 insertions(+), 1 deletion(-) |
15 | 15 | ||
16 | diff --git a/block/crypto.c b/block/crypto.c | 16 | diff --git a/block/export/export.c b/block/export/export.c |
17 | index XXXXXXX..XXXXXXX 100644 | 17 | index XXXXXXX..XXXXXXX 100644 |
18 | --- a/block/crypto.c | 18 | --- a/block/export/export.c |
19 | +++ b/block/crypto.c | 19 | +++ b/block/export/export.c |
20 | @@ -XXX,XX +XXX,XX @@ static int block_crypto_truncate(BlockDriverState *bs, int64_t offset, | 20 | @@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) |
21 | BlockCrypto *crypto = bs->opaque; | 21 | * ctx was acquired in the caller. |
22 | uint64_t payload_offset = | 22 | */ |
23 | qcrypto_block_get_payload_offset(crypto->block); | 23 | bdrv_graph_rdlock_main_loop(); |
24 | - assert(payload_offset < (INT64_MAX - offset)); | 24 | - bdrv_activate(bs, NULL); |
25 | + | 25 | + ret = bdrv_activate(bs, errp); |
26 | + if (payload_offset > INT64_MAX - offset) { | 26 | + if (ret < 0) { |
27 | + error_setg(errp, "The requested file size is too large"); | 27 | + bdrv_graph_rdunlock_main_loop(); |
28 | + return -EFBIG; | 28 | + goto fail; |
29 | + } | 29 | + } |
30 | 30 | bdrv_graph_rdunlock_main_loop(); | |
31 | offset += payload_offset; | 31 | |
32 | 32 | perm = BLK_PERM_CONSISTENT_READ; | |
33 | -- | 33 | -- |
34 | 2.13.6 | 34 | 2.48.1 |
35 | |||
36 | diff view generated by jsdifflib |
1 | It's unclear what the real maximum cluster size is for the Parallels | 1 | So far the assumption has always been that if we try to inactivate a |
---|---|---|---|
2 | format, but let's at least make sure that we don't get integer | 2 | node, it is already idle. This doesn't hold true any more if we allow |
3 | overflows in our .bdrv_co_create implementation. | 3 | inactivating exported nodes because we can't know when new external |
4 | requests come in. | ||
5 | |||
6 | Drain the node around setting BDRV_O_INACTIVE so that requests can't | ||
7 | start operating on an active node and then in the middle it suddenly | ||
8 | becomes inactive. With this change, it's enough for exports to check | ||
9 | for new requests that they operate on an active node (or, like reads, | ||
10 | are allowed even on an inactive node). | ||
4 | 11 | ||
5 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
13 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
14 | Message-ID: <20250204211407.381505-12-kwolf@redhat.com> | ||
15 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
6 | Reviewed-by: Eric Blake <eblake@redhat.com> | 16 | Reviewed-by: Eric Blake <eblake@redhat.com> |
17 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
7 | --- | 18 | --- |
8 | block/parallels.c | 5 +++++ | 19 | block.c | 2 ++ |
9 | 1 file changed, 5 insertions(+) | 20 | 1 file changed, 2 insertions(+) |
10 | 21 | ||
11 | diff --git a/block/parallels.c b/block/parallels.c | 22 | diff --git a/block.c b/block.c |
12 | index XXXXXXX..XXXXXXX 100644 | 23 | index XXXXXXX..XXXXXXX 100644 |
13 | --- a/block/parallels.c | 24 | --- a/block.c |
14 | +++ b/block/parallels.c | 25 | +++ b/block.c |
15 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn parallels_co_create(BlockdevCreateOptions* opts, | 26 | @@ -XXX,XX +XXX,XX @@ bdrv_inactivate_recurse(BlockDriverState *bs, bool top_level) |
16 | cl_size = DEFAULT_CLUSTER_SIZE; | 27 | return -EPERM; |
17 | } | 28 | } |
18 | 29 | ||
19 | + /* XXX What is the real limit here? This is an insanely large maximum. */ | 30 | + bdrv_drained_begin(bs); |
20 | + if (cl_size >= INT64_MAX / MAX_PARALLELS_IMAGE_FACTOR) { | 31 | bs->open_flags |= BDRV_O_INACTIVE; |
21 | + error_setg(errp, "Cluster size is too large"); | 32 | + bdrv_drained_end(bs); |
22 | + return -EINVAL; | 33 | |
23 | + } | 34 | /* |
24 | if (total_size >= MAX_PARALLELS_IMAGE_FACTOR * cl_size) { | 35 | * Update permissions, they may differ for inactive nodes. |
25 | error_setg(errp, "Image size is too large for this cluster size"); | ||
26 | return -E2BIG; | ||
27 | -- | 36 | -- |
28 | 2.13.6 | 37 | 2.48.1 |
29 | |||
30 | diff view generated by jsdifflib |
1 | Use qemu_uuid_unparse() instead of uuid_unparse() to make vdi.c compile | 1 | Add an option in BlockExportOptions to allow creating an export on an |
---|---|---|---|
2 | again when CONFIG_VDI_DEBUG is set. In order to prevent future bitrot, | 2 | inactive node without activating the node. This mode needs to be |
3 | replace '#ifdef CONFIG_VDI_DEBUG' by 'if (VDI_DEBUG)' so that the | 3 | explicitly supported by the export type (so that it doesn't perform any |
4 | compiler always sees the code. | 4 | operations that are forbidden for inactive nodes), so this patch alone |
5 | doesn't allow this option to be successfully used yet. | ||
5 | 6 | ||
6 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 7 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
8 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
7 | Reviewed-by: Eric Blake <eblake@redhat.com> | 9 | Reviewed-by: Eric Blake <eblake@redhat.com> |
10 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
11 | Message-ID: <20250204211407.381505-13-kwolf@redhat.com> | ||
12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
8 | --- | 13 | --- |
9 | block/vdi.c | 22 ++++++++++------------ | 14 | qapi/block-export.json | 10 +++++++++- |
10 | 1 file changed, 10 insertions(+), 12 deletions(-) | 15 | include/block/export.h | 3 +++ |
16 | block/export/export.c | 31 +++++++++++++++++++++---------- | ||
17 | 3 files changed, 33 insertions(+), 11 deletions(-) | ||
11 | 18 | ||
12 | diff --git a/block/vdi.c b/block/vdi.c | 19 | diff --git a/qapi/block-export.json b/qapi/block-export.json |
13 | index XXXXXXX..XXXXXXX 100644 | 20 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/block/vdi.c | 21 | --- a/qapi/block-export.json |
15 | +++ b/block/vdi.c | 22 | +++ b/qapi/block-export.json |
16 | @@ -XXX,XX +XXX,XX @@ static void vdi_header_to_le(VdiHeader *header) | 23 | @@ -XXX,XX +XXX,XX @@ |
17 | qemu_uuid_bswap(&header->uuid_parent); | 24 | # cannot be moved to the iothread. The default is false. |
18 | } | 25 | # (since: 5.2) |
19 | 26 | # | |
20 | -#if defined(CONFIG_VDI_DEBUG) | 27 | +# @allow-inactive: If true, the export allows the exported node to be inactive. |
21 | static void vdi_header_print(VdiHeader *header) | 28 | +# If it is created for an inactive block node, the node remains inactive. If |
29 | +# the export type doesn't support running on an inactive node, an error is | ||
30 | +# returned. If false, inactive block nodes are automatically activated before | ||
31 | +# creating the export and trying to inactivate them later fails. | ||
32 | +# (since: 10.0; default: false) | ||
33 | +# | ||
34 | # Since: 4.2 | ||
35 | ## | ||
36 | { 'union': 'BlockExportOptions', | ||
37 | @@ -XXX,XX +XXX,XX @@ | ||
38 | '*iothread': 'str', | ||
39 | 'node-name': 'str', | ||
40 | '*writable': 'bool', | ||
41 | - '*writethrough': 'bool' }, | ||
42 | + '*writethrough': 'bool', | ||
43 | + '*allow-inactive': 'bool' }, | ||
44 | 'discriminator': 'type', | ||
45 | 'data': { | ||
46 | 'nbd': 'BlockExportOptionsNbd', | ||
47 | diff --git a/include/block/export.h b/include/block/export.h | ||
48 | index XXXXXXX..XXXXXXX 100644 | ||
49 | --- a/include/block/export.h | ||
50 | +++ b/include/block/export.h | ||
51 | @@ -XXX,XX +XXX,XX @@ typedef struct BlockExportDriver { | ||
52 | */ | ||
53 | size_t instance_size; | ||
54 | |||
55 | + /* True if the export type supports running on an inactive node */ | ||
56 | + bool supports_inactive; | ||
57 | + | ||
58 | /* Creates and starts a new block export */ | ||
59 | int (*create)(BlockExport *, BlockExportOptions *, Error **); | ||
60 | |||
61 | diff --git a/block/export/export.c b/block/export/export.c | ||
62 | index XXXXXXX..XXXXXXX 100644 | ||
63 | --- a/block/export/export.c | ||
64 | +++ b/block/export/export.c | ||
65 | @@ -XXX,XX +XXX,XX @@ static const BlockExportDriver *blk_exp_find_driver(BlockExportType type) | ||
66 | BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) | ||
22 | { | 67 | { |
23 | char uuid[37]; | 68 | bool fixed_iothread = export->has_fixed_iothread && export->fixed_iothread; |
24 | @@ -XXX,XX +XXX,XX @@ static void vdi_header_print(VdiHeader *header) | 69 | + bool allow_inactive = export->has_allow_inactive && export->allow_inactive; |
25 | logout("block extra 0x%04x\n", header->block_extra); | 70 | const BlockExportDriver *drv; |
26 | logout("blocks tot. 0x%04x\n", header->blocks_in_image); | 71 | BlockExport *exp = NULL; |
27 | logout("blocks all. 0x%04x\n", header->blocks_allocated); | 72 | BlockDriverState *bs; |
28 | - uuid_unparse(header->uuid_image, uuid); | 73 | @@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) |
29 | + qemu_uuid_unparse(&header->uuid_image, uuid); | 74 | } |
30 | logout("uuid image %s\n", uuid); | ||
31 | - uuid_unparse(header->uuid_last_snap, uuid); | ||
32 | + qemu_uuid_unparse(&header->uuid_last_snap, uuid); | ||
33 | logout("uuid snap %s\n", uuid); | ||
34 | - uuid_unparse(header->uuid_link, uuid); | ||
35 | + qemu_uuid_unparse(&header->uuid_link, uuid); | ||
36 | logout("uuid link %s\n", uuid); | ||
37 | - uuid_unparse(header->uuid_parent, uuid); | ||
38 | + qemu_uuid_unparse(&header->uuid_parent, uuid); | ||
39 | logout("uuid parent %s\n", uuid); | ||
40 | } | ||
41 | -#endif | ||
42 | |||
43 | static int coroutine_fn vdi_co_check(BlockDriverState *bs, BdrvCheckResult *res, | ||
44 | BdrvCheckMode fix) | ||
45 | @@ -XXX,XX +XXX,XX @@ static int vdi_open(BlockDriverState *bs, QDict *options, int flags, | ||
46 | } | 75 | } |
47 | 76 | ||
48 | vdi_header_to_cpu(&header); | 77 | - /* |
49 | -#if defined(CONFIG_VDI_DEBUG) | 78 | - * Block exports are used for non-shared storage migration. Make sure |
50 | - vdi_header_print(&header); | 79 | - * that BDRV_O_INACTIVE is cleared and the image is ready for write |
51 | -#endif | 80 | - * access since the export could be available before migration handover. |
52 | + if (VDI_DEBUG) { | 81 | - * ctx was acquired in the caller. |
53 | + vdi_header_print(&header); | 82 | - */ |
83 | bdrv_graph_rdlock_main_loop(); | ||
84 | - ret = bdrv_activate(bs, errp); | ||
85 | - if (ret < 0) { | ||
86 | - bdrv_graph_rdunlock_main_loop(); | ||
87 | - goto fail; | ||
88 | + if (allow_inactive) { | ||
89 | + if (!drv->supports_inactive) { | ||
90 | + error_setg(errp, "Export type does not support inactive exports"); | ||
91 | + bdrv_graph_rdunlock_main_loop(); | ||
92 | + goto fail; | ||
93 | + } | ||
94 | + } else { | ||
95 | + /* | ||
96 | + * Block exports are used for non-shared storage migration. Make sure | ||
97 | + * that BDRV_O_INACTIVE is cleared and the image is ready for write | ||
98 | + * access since the export could be available before migration handover. | ||
99 | + */ | ||
100 | + ret = bdrv_activate(bs, errp); | ||
101 | + if (ret < 0) { | ||
102 | + bdrv_graph_rdunlock_main_loop(); | ||
103 | + goto fail; | ||
104 | + } | ||
105 | } | ||
106 | bdrv_graph_rdunlock_main_loop(); | ||
107 | |||
108 | @@ -XXX,XX +XXX,XX @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp) | ||
109 | if (!fixed_iothread) { | ||
110 | blk_set_allow_aio_context_change(blk, true); | ||
111 | } | ||
112 | + if (allow_inactive) { | ||
113 | + blk_set_force_allow_inactivate(blk); | ||
54 | + } | 114 | + } |
55 | 115 | ||
56 | if (header.disk_size > VDI_DISK_SIZE_MAX) { | 116 | ret = blk_insert_bs(blk, bs, errp); |
57 | error_setg(errp, "Unsupported VDI image size (size is 0x%" PRIx64 | ||
58 | @@ -XXX,XX +XXX,XX @@ static int coroutine_fn vdi_co_do_create(BlockdevCreateOptions *create_options, | ||
59 | qemu_uuid_generate(&header.uuid_image); | ||
60 | qemu_uuid_generate(&header.uuid_last_snap); | ||
61 | /* There is no need to set header.uuid_link or header.uuid_parent here. */ | ||
62 | -#if defined(CONFIG_VDI_DEBUG) | ||
63 | - vdi_header_print(&header); | ||
64 | -#endif | ||
65 | + if (VDI_DEBUG) { | ||
66 | + vdi_header_print(&header); | ||
67 | + } | ||
68 | vdi_header_to_le(&header); | ||
69 | ret = blk_pwrite(blk, offset, &header, sizeof(header), 0); | ||
70 | if (ret < 0) { | 117 | if (ret < 0) { |
71 | -- | 118 | -- |
72 | 2.13.6 | 119 | 2.48.1 |
73 | |||
74 | diff view generated by jsdifflib |
1 | From: Alberto Garcia <berto@igalia.com> | 1 | In order to support running an NBD export on inactive nodes, we must |
---|---|---|---|
2 | make sure to return errors for any operations that aren't allowed on | ||
3 | inactive nodes. Reads are the only operation we know we need for | ||
4 | inactive images, so to err on the side of caution, return errors for | ||
5 | everything else, even if some operations could possibly be okay. | ||
2 | 6 | ||
3 | When we try to allocate new clusters we first look for available ones | 7 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
4 | starting from s->free_cluster_index and once we find them we increase | 8 | Acked-by: Fabiano Rosas <farosas@suse.de> |
5 | their reference counts. Before we get to call update_refcount() to do | 9 | Message-ID: <20250204211407.381505-14-kwolf@redhat.com> |
6 | this last step s->free_cluster_index is already pointing to the next | 10 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> |
7 | cluster after the ones we are trying to allocate. | ||
8 | |||
9 | During update_refcount() it may happen however that we also need to | ||
10 | allocate a new refcount block in order to store the refcounts of these | ||
11 | new clusters (and to complicate things further that may also require | ||
12 | us to grow the refcount table). After all this we don't know if the | ||
13 | clusters that we originally tried to allocate are still available, so | ||
14 | we return -EAGAIN to ask the caller to restart the search for free | ||
15 | clusters. | ||
16 | |||
17 | This is what can happen in a common scenario: | ||
18 | |||
19 | 1) We want to allocate a new cluster and we see that cluster N is | ||
20 | free. | ||
21 | |||
22 | 2) We try to increase N's refcount but all refcount blocks are full, | ||
23 | so we allocate a new one at N+1 (where s->free_cluster_index was | ||
24 | pointing at). | ||
25 | |||
26 | 3) Once we're done we return -EAGAIN to look again for a free | ||
27 | cluster, but now s->free_cluster_index points at N+2, so that's | ||
28 | the one we allocate. Cluster N remains unallocated and we have a | ||
29 | hole in the qcow2 file. | ||
30 | |||
31 | This can be reproduced easily: | ||
32 | |||
33 | qemu-img create -f qcow2 -o cluster_size=512 hd.qcow2 1M | ||
34 | qemu-io -c 'write 0 124k' hd.qcow2 | ||
35 | |||
36 | After this the image has 132608 bytes (256 clusters), and the refcount | ||
37 | block is full. If we write 512 more bytes it should allocate two new | ||
38 | clusters: the data cluster itself and a new refcount block. | ||
39 | |||
40 | qemu-io -c 'write 124k 512' hd.qcow2 | ||
41 | |||
42 | However the image has now three new clusters (259 in total), and the | ||
43 | first one of them is empty (and unallocated): | ||
44 | |||
45 | dd if=hd.qcow2 bs=512c skip=256 count=1 | hexdump -C | ||
46 | |||
47 | If we write larger amounts of data in the last step instead of the 512 | ||
48 | bytes used in this example we can create larger holes in the qcow2 | ||
49 | file. | ||
50 | |||
51 | What this patch does is reset s->free_cluster_index to its previous | ||
52 | value when alloc_refcount_block() returns -EAGAIN. This way the caller | ||
53 | will try to allocate again the original clusters if they are still | ||
54 | free. | ||
55 | |||
56 | The output of iotest 026 also needs to be updated because now that | ||
57 | images have no holes some tests fail at a different point and the | ||
58 | number of leaked clusters is different. | ||
59 | |||
60 | Signed-off-by: Alberto Garcia <berto@igalia.com> | ||
61 | Reviewed-by: Eric Blake <eblake@redhat.com> | 11 | Reviewed-by: Eric Blake <eblake@redhat.com> |
62 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 12 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
63 | --- | 13 | --- |
64 | block/qcow2-refcount.c | 7 +++++++ | 14 | nbd/server.c | 17 +++++++++++++++++ |
65 | tests/qemu-iotests/026.out | 6 +++--- | 15 | 1 file changed, 17 insertions(+) |
66 | tests/qemu-iotests/121 | 20 ++++++++++++++++++++ | ||
67 | tests/qemu-iotests/121.out | 10 ++++++++++ | ||
68 | 4 files changed, 40 insertions(+), 3 deletions(-) | ||
69 | 16 | ||
70 | diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c | 17 | diff --git a/nbd/server.c b/nbd/server.c |
71 | index XXXXXXX..XXXXXXX 100644 | 18 | index XXXXXXX..XXXXXXX 100644 |
72 | --- a/block/qcow2-refcount.c | 19 | --- a/nbd/server.c |
73 | +++ b/block/qcow2-refcount.c | 20 | +++ b/nbd/server.c |
74 | @@ -XXX,XX +XXX,XX @@ static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs, | 21 | @@ -XXX,XX +XXX,XX @@ static void nbd_export_delete(BlockExport *blk_exp) |
75 | qcow2_cache_put(s->refcount_block_cache, &refcount_block); | 22 | const BlockExportDriver blk_exp_nbd = { |
76 | } | 23 | .type = BLOCK_EXPORT_TYPE_NBD, |
77 | ret = alloc_refcount_block(bs, cluster_index, &refcount_block); | 24 | .instance_size = sizeof(NBDExport), |
78 | + /* If the caller needs to restart the search for free clusters, | 25 | + .supports_inactive = true, |
79 | + * try the same ones first to see if they're still free. */ | 26 | .create = nbd_export_create, |
80 | + if (ret == -EAGAIN) { | 27 | .delete = nbd_export_delete, |
81 | + if (s->free_cluster_index > (start >> s->cluster_bits)) { | 28 | .request_shutdown = nbd_export_request_shutdown, |
82 | + s->free_cluster_index = (start >> s->cluster_bits); | 29 | @@ -XXX,XX +XXX,XX @@ static coroutine_fn int nbd_handle_request(NBDClient *client, |
83 | + } | 30 | NBDExport *exp = client->exp; |
31 | char *msg; | ||
32 | size_t i; | ||
33 | + bool inactive; | ||
34 | + | ||
35 | + WITH_GRAPH_RDLOCK_GUARD() { | ||
36 | + inactive = bdrv_is_inactive(blk_bs(exp->common.blk)); | ||
37 | + if (inactive) { | ||
38 | + switch (request->type) { | ||
39 | + case NBD_CMD_READ: | ||
40 | + /* These commands are allowed on inactive nodes */ | ||
41 | + break; | ||
42 | + default: | ||
43 | + /* Return an error for the rest */ | ||
44 | + return nbd_send_generic_reply(client, request, -EPERM, | ||
45 | + "export is inactive", errp); | ||
84 | + } | 46 | + } |
85 | if (ret < 0) { | 47 | + } |
86 | goto fail; | 48 | + } |
87 | } | 49 | |
88 | diff --git a/tests/qemu-iotests/026.out b/tests/qemu-iotests/026.out | 50 | switch (request->type) { |
89 | index XXXXXXX..XXXXXXX 100644 | 51 | case NBD_CMD_CACHE: |
90 | --- a/tests/qemu-iotests/026.out | ||
91 | +++ b/tests/qemu-iotests/026.out | ||
92 | @@ -XXX,XX +XXX,XX @@ Failed to flush the L2 table cache: No space left on device | ||
93 | Failed to flush the refcount block cache: No space left on device | ||
94 | write failed: No space left on device | ||
95 | |||
96 | -11 leaked clusters were found on the image. | ||
97 | +10 leaked clusters were found on the image. | ||
98 | This means waste of disk space, but no harm to data. | ||
99 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824 | ||
100 | |||
101 | @@ -XXX,XX +XXX,XX @@ Failed to flush the L2 table cache: No space left on device | ||
102 | Failed to flush the refcount block cache: No space left on device | ||
103 | write failed: No space left on device | ||
104 | |||
105 | -11 leaked clusters were found on the image. | ||
106 | +10 leaked clusters were found on the image. | ||
107 | This means waste of disk space, but no harm to data. | ||
108 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824 | ||
109 | |||
110 | @@ -XXX,XX +XXX,XX @@ Failed to flush the L2 table cache: No space left on device | ||
111 | Failed to flush the refcount block cache: No space left on device | ||
112 | write failed: No space left on device | ||
113 | |||
114 | -11 leaked clusters were found on the image. | ||
115 | +10 leaked clusters were found on the image. | ||
116 | This means waste of disk space, but no harm to data. | ||
117 | Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1073741824 | ||
118 | |||
119 | diff --git a/tests/qemu-iotests/121 b/tests/qemu-iotests/121 | ||
120 | index XXXXXXX..XXXXXXX 100755 | ||
121 | --- a/tests/qemu-iotests/121 | ||
122 | +++ b/tests/qemu-iotests/121 | ||
123 | @@ -XXX,XX +XXX,XX @@ $QEMU_IO -c 'write 63M 130K' "$TEST_IMG" | _filter_qemu_io | ||
124 | |||
125 | _check_test_img | ||
126 | |||
127 | +echo | ||
128 | +echo '=== Allocating a new refcount block must not leave holes in the image ===' | ||
129 | +echo | ||
130 | + | ||
131 | +IMGOPTS='cluster_size=512,refcount_bits=16' _make_test_img 1M | ||
132 | + | ||
133 | +# This results in an image with 256 used clusters: the qcow2 header, | ||
134 | +# the refcount table, one refcount block, the L1 table, four L2 tables | ||
135 | +# and 248 data clusters | ||
136 | +$QEMU_IO -c 'write 0 124k' "$TEST_IMG" | _filter_qemu_io | ||
137 | + | ||
138 | +# 256 clusters of 512 bytes each give us a 128K image | ||
139 | +stat -c "size=%s (expected 131072)" $TEST_IMG | ||
140 | + | ||
141 | +# All 256 entries of the refcount block are used, so writing a new | ||
142 | +# data cluster also allocates a new refcount block | ||
143 | +$QEMU_IO -c 'write 124k 512' "$TEST_IMG" | _filter_qemu_io | ||
144 | + | ||
145 | +# Two more clusters, the image size should be 129K now | ||
146 | +stat -c "size=%s (expected 132096)" $TEST_IMG | ||
147 | |||
148 | # success, all done | ||
149 | echo | ||
150 | diff --git a/tests/qemu-iotests/121.out b/tests/qemu-iotests/121.out | ||
151 | index XXXXXXX..XXXXXXX 100644 | ||
152 | --- a/tests/qemu-iotests/121.out | ||
153 | +++ b/tests/qemu-iotests/121.out | ||
154 | @@ -XXX,XX +XXX,XX @@ wrote 133120/133120 bytes at offset 66060288 | ||
155 | 130 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
156 | No errors were found on the image. | ||
157 | |||
158 | +=== Allocating a new refcount block must not leave holes in the image === | ||
159 | + | ||
160 | +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=1048576 | ||
161 | +wrote 126976/126976 bytes at offset 0 | ||
162 | +124 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
163 | +size=131072 (expected 131072) | ||
164 | +wrote 512/512 bytes at offset 126976 | ||
165 | +512 bytes, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
166 | +size=132096 (expected 132096) | ||
167 | + | ||
168 | *** done | ||
169 | -- | 52 | -- |
170 | 2.13.6 | 53 | 2.48.1 |
171 | |||
172 | diff view generated by jsdifflib |
1 | We want to test resizing even for luks. The only change that is needed | 1 | The open-coded form of this filter has been copied into enough tests |
---|---|---|---|
2 | is to explicitly zero out new space for luks because it's undefined. | 2 | that it's better to move it into iotests.py. |
3 | 3 | ||
4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
5 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
5 | Reviewed-by: Eric Blake <eblake@redhat.com> | 6 | Reviewed-by: Eric Blake <eblake@redhat.com> |
6 | Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> | 7 | Message-ID: <20250204211407.381505-15-kwolf@redhat.com> |
8 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
7 | --- | 10 | --- |
8 | tests/qemu-iotests/025 | 9 ++++++++- | 11 | tests/qemu-iotests/iotests.py | 4 ++++ |
9 | 1 file changed, 8 insertions(+), 1 deletion(-) | 12 | tests/qemu-iotests/041 | 4 +--- |
13 | tests/qemu-iotests/165 | 4 +--- | ||
14 | tests/qemu-iotests/tests/copy-before-write | 3 +-- | ||
15 | tests/qemu-iotests/tests/migrate-bitmaps-test | 7 +++---- | ||
16 | 5 files changed, 10 insertions(+), 12 deletions(-) | ||
10 | 17 | ||
11 | diff --git a/tests/qemu-iotests/025 b/tests/qemu-iotests/025 | 18 | diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py |
19 | index XXXXXXX..XXXXXXX 100644 | ||
20 | --- a/tests/qemu-iotests/iotests.py | ||
21 | +++ b/tests/qemu-iotests/iotests.py | ||
22 | @@ -XXX,XX +XXX,XX @@ def _filter(_key, value): | ||
23 | def filter_nbd_exports(output: str) -> str: | ||
24 | return re.sub(r'((min|opt|max) block): [0-9]+', r'\1: XXX', output) | ||
25 | |||
26 | +def filter_qtest(output: str) -> str: | ||
27 | + output = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', output) | ||
28 | + output = re.sub(r'\n?\[I \+\d+\.\d+\] CLOSED\n?$', '', output) | ||
29 | + return output | ||
30 | |||
31 | Msg = TypeVar('Msg', Dict[str, Any], List[Any], str) | ||
32 | |||
33 | diff --git a/tests/qemu-iotests/041 b/tests/qemu-iotests/041 | ||
12 | index XXXXXXX..XXXXXXX 100755 | 34 | index XXXXXXX..XXXXXXX 100755 |
13 | --- a/tests/qemu-iotests/025 | 35 | --- a/tests/qemu-iotests/041 |
14 | +++ b/tests/qemu-iotests/025 | 36 | +++ b/tests/qemu-iotests/041 |
15 | @@ -XXX,XX +XXX,XX @@ trap "_cleanup; exit \$status" 0 1 2 3 15 | 37 | @@ -XXX,XX +XXX,XX @@ class TestRepairQuorum(iotests.QMPTestCase): |
16 | . ./common.filter | 38 | |
17 | . ./common.pattern | 39 | # Check the full error message now |
18 | 40 | self.vm.shutdown() | |
19 | -_supported_fmt raw qcow2 qed | 41 | - log = self.vm.get_log() |
20 | +_supported_fmt raw qcow2 qed luks | 42 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) |
21 | _supported_proto file sheepdog rbd nfs | 43 | + log = iotests.filter_qtest(self.vm.get_log()) |
22 | _supported_os Linux | 44 | log = re.sub(r'^Formatting.*\n', '', log) |
23 | 45 | - log = re.sub(r'\n\[I \+\d+\.\d+\] CLOSED\n?$', '', log) | |
24 | @@ -XXX,XX +XXX,XX @@ length | 46 | log = re.sub(r'^%s: ' % os.path.basename(iotests.qemu_prog), '', log) |
25 | EOF | 47 | |
26 | _check_test_img | 48 | self.assertEqual(log, |
27 | 49 | diff --git a/tests/qemu-iotests/165 b/tests/qemu-iotests/165 | |
28 | +# bdrv_truncate() doesn't zero the new space, so we need to do that explicitly. | 50 | index XXXXXXX..XXXXXXX 100755 |
29 | +# We still want to test automatic zeroing for other formats even though | 51 | --- a/tests/qemu-iotests/165 |
30 | +# bdrv_truncate() doesn't guarantee it. | 52 | +++ b/tests/qemu-iotests/165 |
31 | +if [ "$IMGFMT" == "luks" ]; then | 53 | @@ -XXX,XX +XXX,XX @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase): |
32 | + $QEMU_IO -c "write -z $small_size $((big_size - small_size))" "$TEST_IMG" > /dev/null | 54 | self.vm.shutdown() |
33 | +fi | 55 | |
34 | + | 56 | #catch 'Persistent bitmaps are lost' possible error |
35 | echo | 57 | - log = self.vm.get_log() |
36 | echo "=== Verifying image size after reopen" | 58 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) |
37 | $QEMU_IO -c "length" "$TEST_IMG" | 59 | - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) |
60 | + log = iotests.filter_qtest(self.vm.get_log()) | ||
61 | if log: | ||
62 | print(log) | ||
63 | |||
64 | diff --git a/tests/qemu-iotests/tests/copy-before-write b/tests/qemu-iotests/tests/copy-before-write | ||
65 | index XXXXXXX..XXXXXXX 100755 | ||
66 | --- a/tests/qemu-iotests/tests/copy-before-write | ||
67 | +++ b/tests/qemu-iotests/tests/copy-before-write | ||
68 | @@ -XXX,XX +XXX,XX @@ class TestCbwError(iotests.QMPTestCase): | ||
69 | |||
70 | self.vm.shutdown() | ||
71 | log = self.vm.get_log() | ||
72 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) | ||
73 | - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) | ||
74 | + log = iotests.filter_qtest(log) | ||
75 | log = iotests.filter_qemu_io(log) | ||
76 | return log | ||
77 | |||
78 | diff --git a/tests/qemu-iotests/tests/migrate-bitmaps-test b/tests/qemu-iotests/tests/migrate-bitmaps-test | ||
79 | index XXXXXXX..XXXXXXX 100755 | ||
80 | --- a/tests/qemu-iotests/tests/migrate-bitmaps-test | ||
81 | +++ b/tests/qemu-iotests/tests/migrate-bitmaps-test | ||
82 | @@ -XXX,XX +XXX,XX @@ class TestDirtyBitmapMigration(iotests.QMPTestCase): | ||
83 | |||
84 | # catch 'Could not reopen qcow2 layer: Bitmap already exists' | ||
85 | # possible error | ||
86 | - log = self.vm_a.get_log() | ||
87 | - log = re.sub(r'^\[I \d+\.\d+\] OPENED\n', '', log) | ||
88 | - log = re.sub(r'^(wrote .* bytes at offset .*\n.*KiB.*ops.*sec.*\n){3}', | ||
89 | + log = iotests.filter_qtest(self.vm_a.get_log()) | ||
90 | + log = re.sub(r'^(wrote .* bytes at offset .*\n' | ||
91 | + r'.*KiB.*ops.*sec.*\n?){3}', | ||
92 | '', log) | ||
93 | - log = re.sub(r'\[I \+\d+\.\d+\] CLOSED\n?$', '', log) | ||
94 | self.assertEqual(log, '') | ||
95 | |||
96 | # test that bitmap is still persistent | ||
38 | -- | 97 | -- |
39 | 2.13.6 | 98 | 2.48.1 |
40 | |||
41 | diff view generated by jsdifflib |
1 | Test that it's possible to migrate a VM that uses an image on shared | ||
---|---|---|---|
2 | storage through qemu-storage-daemon. | ||
3 | |||
4 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
5 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
6 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
7 | Message-ID: <20250204211407.381505-16-kwolf@redhat.com> | ||
8 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
1 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 9 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
2 | --- | 10 | --- |
3 | tests/qemu-iotests/212 | 326 +++++++++++++++++++++++++++++++++++++++++++++ | 11 | tests/qemu-iotests/tests/qsd-migrate | 140 +++++++++++++++++++++++ |
4 | tests/qemu-iotests/212.out | 111 +++++++++++++++ | 12 | tests/qemu-iotests/tests/qsd-migrate.out | 59 ++++++++++ |
5 | tests/qemu-iotests/group | 1 + | 13 | 2 files changed, 199 insertions(+) |
6 | 3 files changed, 438 insertions(+) | 14 | create mode 100755 tests/qemu-iotests/tests/qsd-migrate |
7 | create mode 100755 tests/qemu-iotests/212 | 15 | create mode 100644 tests/qemu-iotests/tests/qsd-migrate.out |
8 | create mode 100644 tests/qemu-iotests/212.out | ||
9 | 16 | ||
10 | diff --git a/tests/qemu-iotests/212 b/tests/qemu-iotests/212 | 17 | diff --git a/tests/qemu-iotests/tests/qsd-migrate b/tests/qemu-iotests/tests/qsd-migrate |
11 | new file mode 100755 | 18 | new file mode 100755 |
12 | index XXXXXXX..XXXXXXX | 19 | index XXXXXXX..XXXXXXX |
13 | --- /dev/null | 20 | --- /dev/null |
14 | +++ b/tests/qemu-iotests/212 | 21 | +++ b/tests/qemu-iotests/tests/qsd-migrate |
15 | @@ -XXX,XX +XXX,XX @@ | 22 | @@ -XXX,XX +XXX,XX @@ |
16 | +#!/bin/bash | 23 | +#!/usr/bin/env python3 |
17 | +# | 24 | +# group: rw quick |
18 | +# Test parallels and file image creation | 25 | +# |
19 | +# | 26 | +# Copyright (C) Red Hat, Inc. |
20 | +# Copyright (C) 2018 Red Hat, Inc. | ||
21 | +# | 27 | +# |
22 | +# This program is free software; you can redistribute it and/or modify | 28 | +# This program is free software; you can redistribute it and/or modify |
23 | +# it under the terms of the GNU General Public License as published by | 29 | +# it under the terms of the GNU General Public License as published by |
24 | +# the Free Software Foundation; either version 2 of the License, or | 30 | +# the Free Software Foundation; either version 2 of the License, or |
25 | +# (at your option) any later version. | 31 | +# (at your option) any later version. |
... | ... | ||
30 | +# GNU General Public License for more details. | 36 | +# GNU General Public License for more details. |
31 | +# | 37 | +# |
32 | +# You should have received a copy of the GNU General Public License | 38 | +# You should have received a copy of the GNU General Public License |
33 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | 39 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
34 | +# | 40 | +# |
35 | + | 41 | +# Creator/Owner: Kevin Wolf <kwolf@redhat.com> |
36 | +# creator | 42 | + |
37 | +owner=kwolf@redhat.com | 43 | +import iotests |
38 | + | 44 | + |
39 | +seq=`basename $0` | 45 | +from iotests import filter_qemu_io, filter_qtest |
40 | +echo "QA output created by $seq" | 46 | + |
41 | + | 47 | +iotests.script_initialize(supported_fmts=['generic'], |
42 | +here=`pwd` | 48 | + supported_protocols=['file'], |
43 | +status=1 # failure is the default! | 49 | + supported_platforms=['linux']) |
44 | + | 50 | + |
45 | +# get standard environment, filters and checks | 51 | +with iotests.FilePath('disk.img') as path, \ |
46 | +. ./common.rc | 52 | + iotests.FilePath('nbd-src.sock', base_dir=iotests.sock_dir) as nbd_src, \ |
47 | +. ./common.filter | 53 | + iotests.FilePath('nbd-dst.sock', base_dir=iotests.sock_dir) as nbd_dst, \ |
48 | + | 54 | + iotests.FilePath('migrate.sock', base_dir=iotests.sock_dir) as mig_sock, \ |
49 | +_supported_fmt parallels | 55 | + iotests.VM(path_suffix="-src") as vm_src, \ |
50 | +_supported_proto file | 56 | + iotests.VM(path_suffix="-dst") as vm_dst: |
51 | +_supported_os Linux | 57 | + |
52 | + | 58 | + img_size = '10M' |
53 | +function do_run_qemu() | 59 | + |
54 | +{ | 60 | + iotests.log('Preparing disk...') |
55 | + echo Testing: "$@" | 61 | + iotests.qemu_img_create('-f', iotests.imgfmt, path, img_size) |
56 | + $QEMU -nographic -qmp stdio -serial none "$@" | 62 | + |
57 | + echo | 63 | + iotests.log('Launching source QSD...') |
58 | +} | 64 | + qsd_src = iotests.QemuStorageDaemon( |
59 | + | 65 | + '--blockdev', f'file,node-name=disk-file,filename={path}', |
60 | +function run_qemu() | 66 | + '--blockdev', f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt', |
61 | +{ | 67 | + '--nbd-server', f'addr.type=unix,addr.path={nbd_src}', |
62 | + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp \ | 68 | + '--export', 'nbd,id=exp0,node-name=disk-fmt,writable=true,' |
63 | + | _filter_qemu | _filter_imgfmt \ | 69 | + 'allow-inactive=true', |
64 | + | _filter_actual_image_size | 70 | + qmp=True, |
65 | +} | 71 | + ) |
66 | + | 72 | + |
67 | +echo | 73 | + iotests.log('Launching source VM...') |
68 | +echo "=== Successful image creation (defaults) ===" | 74 | + vm_src.add_args('-blockdev', f'nbd,node-name=disk,server.type=unix,' |
69 | +echo | 75 | + f'server.path={nbd_src},export=disk-fmt') |
70 | + | 76 | + vm_src.add_args('-device', 'virtio-blk,drive=disk,id=virtio0') |
71 | +size=$((128 * 1024 * 1024)) | 77 | + vm_src.launch() |
72 | + | 78 | + |
73 | +run_qemu <<EOF | 79 | + iotests.log('Launching destination QSD...') |
74 | +{ "execute": "qmp_capabilities" } | 80 | + qsd_dst = iotests.QemuStorageDaemon( |
75 | +{ "execute": "x-blockdev-create", | 81 | + '--blockdev', f'file,node-name=disk-file,filename={path},active=off', |
76 | + "arguments": { | 82 | + '--blockdev', f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt,' |
77 | + "driver": "file", | 83 | + f'active=off', |
78 | + "filename": "$TEST_IMG", | 84 | + '--nbd-server', f'addr.type=unix,addr.path={nbd_dst}', |
79 | + "size": 0 | 85 | + '--export', 'nbd,id=exp0,node-name=disk-fmt,writable=true,' |
80 | + } | 86 | + 'allow-inactive=true', |
81 | +} | 87 | + qmp=True, |
82 | +{ "execute": "blockdev-add", | 88 | + instance_id='b', |
83 | + "arguments": { | 89 | + ) |
84 | + "driver": "file", | 90 | + |
85 | + "node-name": "imgfile", | 91 | + iotests.log('Launching destination VM...') |
86 | + "filename": "$TEST_IMG" | 92 | + vm_dst.add_args('-blockdev', f'nbd,node-name=disk,server.type=unix,' |
87 | + } | 93 | + f'server.path={nbd_dst},export=disk-fmt') |
88 | +} | 94 | + vm_dst.add_args('-device', 'virtio-blk,drive=disk,id=virtio0') |
89 | +{ "execute": "x-blockdev-create", | 95 | + vm_dst.add_args('-incoming', f'unix:{mig_sock}') |
90 | + "arguments": { | 96 | + vm_dst.launch() |
91 | + "driver": "$IMGFMT", | 97 | + |
92 | + "file": "imgfile", | 98 | + iotests.log('\nTest I/O on the source') |
93 | + "size": $size | 99 | + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'write -P 0x11 0 4k', |
94 | + } | 100 | + use_log=True, qdev=True) |
95 | +} | 101 | + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', |
96 | +{ "execute": "quit" } | 102 | + use_log=True, qdev=True) |
97 | +EOF | 103 | + |
98 | + | 104 | + iotests.log('\nStarting migration...') |
99 | +_img_info --format-specific | _filter_img_info --format-specific | 105 | + |
100 | + | 106 | + mig_caps = [ |
101 | +echo | 107 | + {'capability': 'events', 'state': True}, |
102 | +echo "=== Successful image creation (explicit defaults) ===" | 108 | + {'capability': 'pause-before-switchover', 'state': True}, |
103 | +echo | 109 | + ] |
104 | + | 110 | + vm_src.qmp_log('migrate-set-capabilities', capabilities=mig_caps) |
105 | +# Choose a different size to show that we got a new image | 111 | + vm_dst.qmp_log('migrate-set-capabilities', capabilities=mig_caps) |
106 | +size=$((64 * 1024 * 1024)) | 112 | + vm_src.qmp_log('migrate', uri=f'unix:{mig_sock}', |
107 | + | 113 | + filters=[iotests.filter_qmp_testfiles]) |
108 | +run_qemu <<EOF | 114 | + |
109 | +{ "execute": "qmp_capabilities" } | 115 | + vm_src.event_wait('MIGRATION', |
110 | +{ "execute": "x-blockdev-create", | 116 | + match={'data': {'status': 'pre-switchover'}}) |
111 | + "arguments": { | 117 | + |
112 | + "driver": "file", | 118 | + iotests.log('\nPre-switchover: Reconfigure QSD instances') |
113 | + "filename": "$TEST_IMG", | 119 | + |
114 | + "size": 0 | 120 | + iotests.log(qsd_src.qmp('blockdev-set-active', {'active': False})) |
115 | + } | 121 | + |
116 | +} | 122 | + # Reading is okay from both sides while the image is inactive. Note that |
117 | +{ "execute": "x-blockdev-create", | 123 | + # the destination may have stale data until it activates the image, though. |
118 | + "arguments": { | 124 | + vm_src.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', |
119 | + "driver": "$IMGFMT", | 125 | + use_log=True, qdev=True) |
120 | + "file": { | 126 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read 0 4k', |
121 | + "driver": "file", | 127 | + use_log=True, qdev=True) |
122 | + "filename": "$TEST_IMG" | 128 | + |
123 | + }, | 129 | + iotests.log(qsd_dst.qmp('blockdev-set-active', {'active': True})) |
124 | + "size": $size, | 130 | + |
125 | + "cluster-size": 1048576 | 131 | + iotests.log('\nCompleting migration...') |
126 | + } | 132 | + |
127 | +} | 133 | + vm_src.qmp_log('migrate-continue', state='pre-switchover') |
128 | +{ "execute": "quit" } | 134 | + vm_dst.event_wait('MIGRATION', match={'data': {'status': 'completed'}}) |
129 | +EOF | 135 | + |
130 | + | 136 | + iotests.log('\nTest I/O on the destination') |
131 | +_img_info --format-specific | _filter_img_info --format-specific | 137 | + |
132 | + | 138 | + # Now the destination must see what the source wrote |
133 | +echo | 139 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x11 0 4k', |
134 | +echo "=== Successful image creation (with non-default options) ===" | 140 | + use_log=True, qdev=True) |
135 | +echo | 141 | + |
136 | + | 142 | + # And be able to overwrite it |
137 | +# Choose a different size to show that we got a new image | 143 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'write -P 0x22 0 4k', |
138 | +size=$((32 * 1024 * 1024)) | 144 | + use_log=True, qdev=True) |
139 | + | 145 | + vm_dst.hmp_qemu_io('virtio0/virtio-backend', 'read -P 0x22 0 4k', |
140 | +run_qemu <<EOF | 146 | + use_log=True, qdev=True) |
141 | +{ "execute": "qmp_capabilities" } | 147 | + |
142 | +{ "execute": "x-blockdev-create", | 148 | + iotests.log('\nDone') |
143 | + "arguments": { | 149 | + |
144 | + "driver": "file", | 150 | + vm_src.shutdown() |
145 | + "filename": "$TEST_IMG", | 151 | + iotests.log('\n--- vm_src log ---') |
146 | + "size": 0 | 152 | + log = vm_src.get_log() |
147 | + } | 153 | + if log: |
148 | +} | 154 | + iotests.log(log, [filter_qtest, filter_qemu_io]) |
149 | +{ "execute": "x-blockdev-create", | 155 | + qsd_src.stop() |
150 | + "arguments": { | 156 | + |
151 | + "driver": "$IMGFMT", | 157 | + vm_dst.shutdown() |
152 | + "file": { | 158 | + iotests.log('\n--- vm_dst log ---') |
153 | + "driver": "file", | 159 | + log = vm_dst.get_log() |
154 | + "filename": "$TEST_IMG" | 160 | + if log: |
155 | + }, | 161 | + iotests.log(log, [filter_qtest, filter_qemu_io]) |
156 | + "size": $size, | 162 | + qsd_dst.stop() |
157 | + "cluster-size": 65536 | 163 | diff --git a/tests/qemu-iotests/tests/qsd-migrate.out b/tests/qemu-iotests/tests/qsd-migrate.out |
158 | + } | ||
159 | +} | ||
160 | +{ "execute": "quit" } | ||
161 | +EOF | ||
162 | + | ||
163 | +_img_info --format-specific | _filter_img_info --format-specific | ||
164 | + | ||
165 | +echo | ||
166 | +echo "=== Invalid BlockdevRef ===" | ||
167 | +echo | ||
168 | + | ||
169 | +run_qemu <<EOF | ||
170 | +{ "execute": "qmp_capabilities" } | ||
171 | +{ "execute": "x-blockdev-create", | ||
172 | + "arguments": { | ||
173 | + "driver": "$IMGFMT", | ||
174 | + "file": "this doesn't exist", | ||
175 | + "size": $size | ||
176 | + } | ||
177 | +} | ||
178 | +{ "execute": "quit" } | ||
179 | +EOF | ||
180 | + | ||
181 | +echo | ||
182 | +echo "=== Zero size ===" | ||
183 | +echo | ||
184 | + | ||
185 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
186 | +{ "execute": "qmp_capabilities" } | ||
187 | +{ "execute": "x-blockdev-create", | ||
188 | + "arguments": { | ||
189 | + "driver": "$IMGFMT", | ||
190 | + "file": "node0", | ||
191 | + "size": 0 | ||
192 | + } | ||
193 | +} | ||
194 | +{ "execute": "quit" } | ||
195 | +EOF | ||
196 | + | ||
197 | +_img_info | _filter_img_info | ||
198 | + | ||
199 | +echo | ||
200 | +echo "=== Maximum size ===" | ||
201 | +echo | ||
202 | + | ||
203 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
204 | +{ "execute": "qmp_capabilities" } | ||
205 | +{ "execute": "x-blockdev-create", | ||
206 | + "arguments": { | ||
207 | + "driver": "$IMGFMT", | ||
208 | + "file": "node0", | ||
209 | + "size": 4503599627369984 | ||
210 | + } | ||
211 | +} | ||
212 | +{ "execute": "quit" } | ||
213 | +EOF | ||
214 | + | ||
215 | +_img_info | _filter_img_info | ||
216 | + | ||
217 | +echo | ||
218 | +echo "=== Invalid sizes ===" | ||
219 | +echo | ||
220 | + | ||
221 | +# TODO Negative image sizes aren't handled correctly, but this is a problem | ||
222 | +# with QAPI's implementation of the 'size' type and affects other commands as | ||
223 | +# well. Once this is fixed, we may want to add a test case here. | ||
224 | + | ||
225 | +# 1. Misaligned image size | ||
226 | +# 2. 2^64 - 512 | ||
227 | +# 3. 2^63 = 8 EB (qemu-img enforces image sizes less than this) | ||
228 | +# 4. 2^63 - 512 (generally valid, but with the image header the file will | ||
229 | +# exceed 63 bits) | ||
230 | +# 5. 2^52 (512 bytes more than maximum image size) | ||
231 | + | ||
232 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
233 | +{ "execute": "qmp_capabilities" } | ||
234 | +{ "execute": "x-blockdev-create", | ||
235 | + "arguments": { | ||
236 | + "driver": "$IMGFMT", | ||
237 | + "file": "node0", | ||
238 | + "size": 1234 | ||
239 | + } | ||
240 | +} | ||
241 | +{ "execute": "x-blockdev-create", | ||
242 | + "arguments": { | ||
243 | + "driver": "$IMGFMT", | ||
244 | + "file": "node0", | ||
245 | + "size": 18446744073709551104 | ||
246 | + } | ||
247 | +} | ||
248 | +{ "execute": "x-blockdev-create", | ||
249 | + "arguments": { | ||
250 | + "driver": "$IMGFMT", | ||
251 | + "file": "node0", | ||
252 | + "size": 9223372036854775808 | ||
253 | + } | ||
254 | +} | ||
255 | +{ "execute": "x-blockdev-create", | ||
256 | + "arguments": { | ||
257 | + "driver": "$IMGFMT", | ||
258 | + "file": "node0", | ||
259 | + "size": 9223372036854775296 | ||
260 | + } | ||
261 | +} | ||
262 | +{ "execute": "x-blockdev-create", | ||
263 | + "arguments": { | ||
264 | + "driver": "$IMGFMT", | ||
265 | + "file": "node0", | ||
266 | + "size": 4503599627370497 | ||
267 | + } | ||
268 | +} | ||
269 | +{ "execute": "quit" } | ||
270 | +EOF | ||
271 | + | ||
272 | +echo | ||
273 | +echo "=== Invalid cluster size ===" | ||
274 | +echo | ||
275 | + | ||
276 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | ||
277 | +{ "execute": "qmp_capabilities" } | ||
278 | +{ "execute": "x-blockdev-create", | ||
279 | + "arguments": { | ||
280 | + "driver": "$IMGFMT", | ||
281 | + "file": "node0", | ||
282 | + "size": 67108864, | ||
283 | + "cluster-size": 1234 | ||
284 | + } | ||
285 | +} | ||
286 | +{ "execute": "x-blockdev-create", | ||
287 | + "arguments": { | ||
288 | + "driver": "$IMGFMT", | ||
289 | + "file": "node0", | ||
290 | + "size": 67108864, | ||
291 | + "cluster-size": 128 | ||
292 | + } | ||
293 | +} | ||
294 | +{ "execute": "x-blockdev-create", | ||
295 | + "arguments": { | ||
296 | + "driver": "$IMGFMT", | ||
297 | + "file": "node0", | ||
298 | + "size": 67108864, | ||
299 | + "cluster-size": 4294967296 | ||
300 | + } | ||
301 | +} | ||
302 | +{ "execute": "x-blockdev-create", | ||
303 | + "arguments": { | ||
304 | + "driver": "$IMGFMT", | ||
305 | + "file": "node0", | ||
306 | + "size": 67108864, | ||
307 | + "cluster-size": 9223372036854775808 | ||
308 | + } | ||
309 | +} | ||
310 | +{ "execute": "x-blockdev-create", | ||
311 | + "arguments": { | ||
312 | + "driver": "$IMGFMT", | ||
313 | + "file": "node0", | ||
314 | + "size": 67108864, | ||
315 | + "cluster-size": 18446744073709551104 | ||
316 | + } | ||
317 | +} | ||
318 | +{ "execute": "x-blockdev-create", | ||
319 | + "arguments": { | ||
320 | + "driver": "$IMGFMT", | ||
321 | + "file": "node0", | ||
322 | + "size": 67108864, | ||
323 | + "cluster-size": 0 | ||
324 | + } | ||
325 | +} | ||
326 | +{ "execute": "x-blockdev-create", | ||
327 | + "arguments": { | ||
328 | + "driver": "$IMGFMT", | ||
329 | + "file": "node0", | ||
330 | + "size": 281474976710656, | ||
331 | + "cluster-size": 512 | ||
332 | + } | ||
333 | +} | ||
334 | +{ "execute": "quit" } | ||
335 | +EOF | ||
336 | + | ||
337 | + | ||
338 | +# success, all done | ||
339 | +echo "*** done" | ||
340 | +rm -f $seq.full | ||
341 | +status=0 | ||
342 | diff --git a/tests/qemu-iotests/212.out b/tests/qemu-iotests/212.out | ||
343 | new file mode 100644 | 164 | new file mode 100644 |
344 | index XXXXXXX..XXXXXXX | 165 | index XXXXXXX..XXXXXXX |
345 | --- /dev/null | 166 | --- /dev/null |
346 | +++ b/tests/qemu-iotests/212.out | 167 | +++ b/tests/qemu-iotests/tests/qsd-migrate.out |
347 | @@ -XXX,XX +XXX,XX @@ | 168 | @@ -XXX,XX +XXX,XX @@ |
348 | +QA output created by 212 | 169 | +Preparing disk... |
349 | + | 170 | +Launching source QSD... |
350 | +=== Successful image creation (defaults) === | 171 | +Launching source VM... |
351 | + | 172 | +Launching destination QSD... |
352 | +Testing: | 173 | +Launching destination VM... |
353 | +QMP_VERSION | 174 | + |
354 | +{"return": {}} | 175 | +Test I/O on the source |
355 | +{"return": {}} | 176 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"write -P 0x11 0 4k\""}} |
356 | +{"return": {}} | 177 | +{"return": ""} |
357 | +{"return": {}} | 178 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} |
358 | +{"return": {}} | 179 | +{"return": ""} |
359 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 180 | + |
360 | + | 181 | +Starting migration... |
361 | +image: TEST_DIR/t.IMGFMT | 182 | +{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [{"capability": "events", "state": true}, {"capability": "pause-before-switchover", "state": true}]}} |
362 | +file format: IMGFMT | 183 | +{"return": {}} |
363 | +virtual size: 128M (134217728 bytes) | 184 | +{"execute": "migrate-set-capabilities", "arguments": {"capabilities": [{"capability": "events", "state": true}, {"capability": "pause-before-switchover", "state": true}]}} |
364 | + | 185 | +{"return": {}} |
365 | +=== Successful image creation (explicit defaults) === | 186 | +{"execute": "migrate", "arguments": {"uri": "unix:SOCK_DIR/PID-migrate.sock"}} |
366 | + | 187 | +{"return": {}} |
367 | +Testing: | 188 | + |
368 | +QMP_VERSION | 189 | +Pre-switchover: Reconfigure QSD instances |
369 | +{"return": {}} | 190 | +{"return": {}} |
370 | +{"return": {}} | 191 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} |
371 | +{"return": {}} | 192 | +{"return": ""} |
372 | +{"return": {}} | 193 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read 0 4k\""}} |
373 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 194 | +{"return": ""} |
374 | + | 195 | +{"return": {}} |
375 | +image: TEST_DIR/t.IMGFMT | 196 | + |
376 | +file format: IMGFMT | 197 | +Completing migration... |
377 | +virtual size: 64M (67108864 bytes) | 198 | +{"execute": "migrate-continue", "arguments": {"state": "pre-switchover"}} |
378 | + | 199 | +{"return": {}} |
379 | +=== Successful image creation (with non-default options) === | 200 | + |
380 | + | 201 | +Test I/O on the destination |
381 | +Testing: | 202 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x11 0 4k\""}} |
382 | +QMP_VERSION | 203 | +{"return": ""} |
383 | +{"return": {}} | 204 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"write -P 0x22 0 4k\""}} |
384 | +{"return": {}} | 205 | +{"return": ""} |
385 | +{"return": {}} | 206 | +{"execute": "human-monitor-command", "arguments": {"command-line": "qemu-io -d virtio0/virtio-backend \"read -P 0x22 0 4k\""}} |
386 | +{"return": {}} | 207 | +{"return": ""} |
387 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 208 | + |
388 | + | 209 | +Done |
389 | +image: TEST_DIR/t.IMGFMT | 210 | + |
390 | +file format: IMGFMT | 211 | +--- vm_src log --- |
391 | +virtual size: 32M (33554432 bytes) | 212 | +wrote 4096/4096 bytes at offset 0 |
392 | + | 213 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
393 | +=== Invalid BlockdevRef === | 214 | +read 4096/4096 bytes at offset 0 |
394 | + | 215 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
395 | +Testing: | 216 | +read 4096/4096 bytes at offset 0 |
396 | +QMP_VERSION | 217 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
397 | +{"return": {}} | 218 | + |
398 | +{"error": {"class": "GenericError", "desc": "Cannot find device=this doesn't exist nor node_name=this doesn't exist"}} | 219 | +--- vm_dst log --- |
399 | +{"return": {}} | 220 | +read 4096/4096 bytes at offset 0 |
400 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 221 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
401 | + | 222 | +read 4096/4096 bytes at offset 0 |
402 | + | 223 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
403 | +=== Zero size === | 224 | +wrote 4096/4096 bytes at offset 0 |
404 | + | 225 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
405 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | 226 | +read 4096/4096 bytes at offset 0 |
406 | +QMP_VERSION | 227 | +4 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) |
407 | +{"return": {}} | ||
408 | +{"return": {}} | ||
409 | +{"return": {}} | ||
410 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
411 | + | ||
412 | +image: TEST_DIR/t.IMGFMT | ||
413 | +file format: IMGFMT | ||
414 | +virtual size: 0 (0 bytes) | ||
415 | + | ||
416 | +=== Maximum size === | ||
417 | + | ||
418 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
419 | +QMP_VERSION | ||
420 | +{"return": {}} | ||
421 | +{"return": {}} | ||
422 | +{"return": {}} | ||
423 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
424 | + | ||
425 | +image: TEST_DIR/t.IMGFMT | ||
426 | +file format: IMGFMT | ||
427 | +virtual size: 4096T (4503599627369984 bytes) | ||
428 | + | ||
429 | +=== Invalid sizes === | ||
430 | + | ||
431 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
432 | +QMP_VERSION | ||
433 | +{"return": {}} | ||
434 | +{"error": {"class": "GenericError", "desc": "Image size must be a multiple of 512 bytes"}} | ||
435 | +{"error": {"class": "GenericError", "desc": "Image size is too large for this cluster size"}} | ||
436 | +{"error": {"class": "GenericError", "desc": "Image size is too large for this cluster size"}} | ||
437 | +{"error": {"class": "GenericError", "desc": "Image size is too large for this cluster size"}} | ||
438 | +{"error": {"class": "GenericError", "desc": "Image size is too large for this cluster size"}} | ||
439 | +{"return": {}} | ||
440 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
441 | + | ||
442 | + | ||
443 | +=== Invalid cluster size === | ||
444 | + | ||
445 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | ||
446 | +QMP_VERSION | ||
447 | +{"return": {}} | ||
448 | +{"error": {"class": "GenericError", "desc": "Cluster size must be a multiple of 512 bytes"}} | ||
449 | +{"error": {"class": "GenericError", "desc": "Cluster size must be a multiple of 512 bytes"}} | ||
450 | +{"error": {"class": "GenericError", "desc": "Cluster size is too large"}} | ||
451 | +{"error": {"class": "GenericError", "desc": "Cluster size is too large"}} | ||
452 | +{"error": {"class": "GenericError", "desc": "Cluster size is too large"}} | ||
453 | +{"error": {"class": "GenericError", "desc": "Image size is too large for this cluster size"}} | ||
454 | +{"error": {"class": "GenericError", "desc": "Image size is too large for this cluster size"}} | ||
455 | +{"return": {}} | ||
456 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | ||
457 | + | ||
458 | +*** done | ||
459 | diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group | ||
460 | index XXXXXXX..XXXXXXX 100644 | ||
461 | --- a/tests/qemu-iotests/group | ||
462 | +++ b/tests/qemu-iotests/group | ||
463 | @@ -XXX,XX +XXX,XX @@ | ||
464 | 209 rw auto quick | ||
465 | 210 rw auto | ||
466 | 211 rw auto quick | ||
467 | +212 rw auto quick | ||
468 | -- | 228 | -- |
469 | 2.13.6 | 229 | 2.48.1 |
470 | |||
471 | diff view generated by jsdifflib |
1 | This tests different types of operations on inactive block nodes | ||
---|---|---|---|
2 | (including graph changes, block jobs and NBD exports) to make sure that | ||
3 | users manually activating and inactivating nodes doesn't break things. | ||
4 | |||
5 | Support for inactive nodes in other export types will have to come with | ||
6 | separate test cases because they have different dependencies like blkio | ||
7 | or root permissions and we don't want to disable this basic test when | ||
8 | they are not fulfilled. | ||
9 | |||
1 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 10 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
11 | Acked-by: Fabiano Rosas <farosas@suse.de> | ||
12 | Message-ID: <20250204211407.381505-17-kwolf@redhat.com> | ||
2 | Reviewed-by: Eric Blake <eblake@redhat.com> | 13 | Reviewed-by: Eric Blake <eblake@redhat.com> |
14 | Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
15 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | ||
3 | --- | 16 | --- |
4 | tests/qemu-iotests/211 | 246 +++++++++++++++++++++++++++++++++++++++++++++ | 17 | tests/qemu-iotests/iotests.py | 4 + |
5 | tests/qemu-iotests/211.out | 97 ++++++++++++++++++ | 18 | tests/qemu-iotests/tests/inactive-node-nbd | 303 ++++++++++++++++++ |
6 | tests/qemu-iotests/group | 1 + | 19 | .../qemu-iotests/tests/inactive-node-nbd.out | 239 ++++++++++++++ |
7 | 3 files changed, 344 insertions(+) | 20 | 3 files changed, 546 insertions(+) |
8 | create mode 100755 tests/qemu-iotests/211 | 21 | create mode 100755 tests/qemu-iotests/tests/inactive-node-nbd |
9 | create mode 100644 tests/qemu-iotests/211.out | 22 | create mode 100644 tests/qemu-iotests/tests/inactive-node-nbd.out |
10 | 23 | ||
11 | diff --git a/tests/qemu-iotests/211 b/tests/qemu-iotests/211 | 24 | diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py |
25 | index XXXXXXX..XXXXXXX 100644 | ||
26 | --- a/tests/qemu-iotests/iotests.py | ||
27 | +++ b/tests/qemu-iotests/iotests.py | ||
28 | @@ -XXX,XX +XXX,XX @@ def add_incoming(self, addr): | ||
29 | self._args.append(addr) | ||
30 | return self | ||
31 | |||
32 | + def add_paused(self): | ||
33 | + self._args.append('-S') | ||
34 | + return self | ||
35 | + | ||
36 | def hmp(self, command_line: str, use_log: bool = False) -> QMPMessage: | ||
37 | cmd = 'human-monitor-command' | ||
38 | kwargs: Dict[str, Any] = {'command-line': command_line} | ||
39 | diff --git a/tests/qemu-iotests/tests/inactive-node-nbd b/tests/qemu-iotests/tests/inactive-node-nbd | ||
12 | new file mode 100755 | 40 | new file mode 100755 |
13 | index XXXXXXX..XXXXXXX | 41 | index XXXXXXX..XXXXXXX |
14 | --- /dev/null | 42 | --- /dev/null |
15 | +++ b/tests/qemu-iotests/211 | 43 | +++ b/tests/qemu-iotests/tests/inactive-node-nbd |
16 | @@ -XXX,XX +XXX,XX @@ | 44 | @@ -XXX,XX +XXX,XX @@ |
17 | +#!/bin/bash | 45 | +#!/usr/bin/env python3 |
46 | +# group: rw quick | ||
18 | +# | 47 | +# |
19 | +# Test VDI and file image creation | 48 | +# Copyright (C) Red Hat, Inc. |
20 | +# | ||
21 | +# Copyright (C) 2018 Red Hat, Inc. | ||
22 | +# | 49 | +# |
23 | +# This program is free software; you can redistribute it and/or modify | 50 | +# This program is free software; you can redistribute it and/or modify |
24 | +# it under the terms of the GNU General Public License as published by | 51 | +# it under the terms of the GNU General Public License as published by |
25 | +# the Free Software Foundation; either version 2 of the License, or | 52 | +# the Free Software Foundation; either version 2 of the License, or |
26 | +# (at your option) any later version. | 53 | +# (at your option) any later version. |
... | ... | ||
31 | +# GNU General Public License for more details. | 58 | +# GNU General Public License for more details. |
32 | +# | 59 | +# |
33 | +# You should have received a copy of the GNU General Public License | 60 | +# You should have received a copy of the GNU General Public License |
34 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. | 61 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
35 | +# | 62 | +# |
36 | + | 63 | +# Creator/Owner: Kevin Wolf <kwolf@redhat.com> |
37 | +# creator | 64 | + |
38 | +owner=kwolf@redhat.com | 65 | +import iotests |
39 | + | 66 | + |
40 | +seq=`basename $0` | 67 | +from iotests import QemuIoInteractive |
41 | +echo "QA output created by $seq" | 68 | +from iotests import filter_qemu_io, filter_qtest, filter_qmp_testfiles |
42 | + | 69 | + |
43 | +here=`pwd` | 70 | +iotests.script_initialize(supported_fmts=['generic'], |
44 | +status=1 # failure is the default! | 71 | + supported_protocols=['file'], |
45 | + | 72 | + supported_platforms=['linux']) |
46 | +# get standard environment, filters and checks | 73 | + |
47 | +. ./common.rc | 74 | +def get_export(node_name='disk-fmt', allow_inactive=None): |
48 | +. ./common.filter | 75 | + exp = { |
49 | + | 76 | + 'id': 'exp0', |
50 | +_supported_fmt vdi | 77 | + 'type': 'nbd', |
51 | +_supported_proto file | 78 | + 'node-name': node_name, |
52 | +_supported_os Linux | 79 | + 'writable': True, |
53 | + | 80 | + } |
54 | +function do_run_qemu() | 81 | + |
55 | +{ | 82 | + if allow_inactive is not None: |
56 | + echo Testing: "$@" | 83 | + exp['allow-inactive'] = allow_inactive |
57 | + $QEMU -nographic -qmp stdio -serial none "$@" | 84 | + |
58 | + echo | 85 | + return exp |
59 | +} | 86 | + |
60 | + | 87 | +def node_is_active(_vm, node_name): |
61 | +function run_qemu() | 88 | + nodes = _vm.cmd('query-named-block-nodes', flat=True) |
62 | +{ | 89 | + node = next(n for n in nodes if n['node-name'] == node_name) |
63 | + do_run_qemu "$@" 2>&1 | _filter_testdir | _filter_qmp \ | 90 | + return node['active'] |
64 | + | _filter_qemu | _filter_imgfmt \ | 91 | + |
65 | + | _filter_actual_image_size | 92 | +with iotests.FilePath('disk.img') as path, \ |
66 | +} | 93 | + iotests.FilePath('snap.qcow2') as snap_path, \ |
67 | + | 94 | + iotests.FilePath('snap2.qcow2') as snap2_path, \ |
68 | +echo | 95 | + iotests.FilePath('target.img') as target_path, \ |
69 | +echo "=== Successful image creation (defaults) ===" | 96 | + iotests.FilePath('nbd.sock', base_dir=iotests.sock_dir) as nbd_sock, \ |
70 | +echo | 97 | + iotests.VM() as vm: |
71 | + | 98 | + |
72 | +size=$((128 * 1024 * 1024)) | 99 | + img_size = '10M' |
73 | + | 100 | + |
74 | +run_qemu <<EOF | 101 | + iotests.log('Preparing disk...') |
75 | +{ "execute": "qmp_capabilities" } | 102 | + iotests.qemu_img_create('-f', iotests.imgfmt, path, img_size) |
76 | +{ "execute": "x-blockdev-create", | 103 | + iotests.qemu_img_create('-f', iotests.imgfmt, target_path, img_size) |
77 | + "arguments": { | 104 | + |
78 | + "driver": "file", | 105 | + iotests.qemu_img_create('-f', 'qcow2', '-b', path, '-F', iotests.imgfmt, |
79 | + "filename": "$TEST_IMG", | 106 | + snap_path) |
80 | + "size": 0 | 107 | + iotests.qemu_img_create('-f', 'qcow2', '-b', snap_path, '-F', 'qcow2', |
81 | + } | 108 | + snap2_path) |
82 | +} | 109 | + |
83 | +{ "execute": "blockdev-add", | 110 | + iotests.log('Launching VM...') |
84 | + "arguments": { | 111 | + vm.add_blockdev(f'file,node-name=disk-file,filename={path}') |
85 | + "driver": "file", | 112 | + vm.add_blockdev(f'{iotests.imgfmt},file=disk-file,node-name=disk-fmt,' |
86 | + "node-name": "imgfile", | 113 | + 'active=off') |
87 | + "filename": "$TEST_IMG" | 114 | + vm.add_blockdev(f'file,node-name=target-file,filename={target_path}') |
88 | + } | 115 | + vm.add_blockdev(f'{iotests.imgfmt},file=target-file,node-name=target-fmt') |
89 | +} | 116 | + vm.add_blockdev(f'file,node-name=snap-file,filename={snap_path}') |
90 | +{ "execute": "x-blockdev-create", | 117 | + vm.add_blockdev(f'file,node-name=snap2-file,filename={snap2_path}') |
91 | + "arguments": { | 118 | + |
92 | + "driver": "$IMGFMT", | 119 | + # Actually running the VM activates all images |
93 | + "file": "imgfile", | 120 | + vm.add_paused() |
94 | + "size": $size | 121 | + |
95 | + } | 122 | + vm.launch() |
96 | +} | 123 | + vm.qmp_log('nbd-server-start', |
97 | +{ "execute": "quit" } | 124 | + addr={'type': 'unix', 'data':{'path': nbd_sock}}, |
98 | +EOF | 125 | + filters=[filter_qmp_testfiles]) |
99 | + | 126 | + |
100 | +_img_info --format-specific | _filter_img_info --format-specific | 127 | + iotests.log('\n=== Creating export of inactive node ===') |
101 | +$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map | 128 | + |
102 | + | 129 | + iotests.log('\nExports activate nodes without allow-inactive') |
103 | +echo | 130 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
104 | +echo "=== Successful image creation (explicit defaults) ===" | 131 | + vm.qmp_log('block-export-add', **get_export()) |
105 | +echo | 132 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
106 | + | 133 | + vm.qmp_log('query-block-exports') |
107 | +# Choose a different size to show that we got a new image | 134 | + vm.qmp_log('block-export-del', id='exp0') |
108 | +size=$((64 * 1024 * 1024)) | 135 | + vm.event_wait('BLOCK_EXPORT_DELETED') |
109 | + | 136 | + vm.qmp_log('query-block-exports') |
110 | +run_qemu <<EOF | 137 | + |
111 | +{ "execute": "qmp_capabilities" } | 138 | + iotests.log('\nExports activate nodes with allow-inactive=false') |
112 | +{ "execute": "x-blockdev-create", | 139 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) |
113 | + "arguments": { | 140 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
114 | + "driver": "file", | 141 | + vm.qmp_log('block-export-add', **get_export(allow_inactive=False)) |
115 | + "filename": "$TEST_IMG", | 142 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
116 | + "size": 0 | 143 | + vm.qmp_log('query-block-exports') |
117 | + } | 144 | + vm.qmp_log('block-export-del', id='exp0') |
118 | +} | 145 | + vm.event_wait('BLOCK_EXPORT_DELETED') |
119 | +{ "execute": "x-blockdev-create", | 146 | + vm.qmp_log('query-block-exports') |
120 | + "arguments": { | 147 | + |
121 | + "driver": "$IMGFMT", | 148 | + iotests.log('\nExport leaves nodes inactive with allow-inactive=true') |
122 | + "file": { | 149 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) |
123 | + "driver": "file", | 150 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
124 | + "filename": "$TEST_IMG" | 151 | + vm.qmp_log('block-export-add', **get_export(allow_inactive=True)) |
125 | + }, | 152 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
126 | + "size": $size, | 153 | + vm.qmp_log('query-block-exports') |
127 | + "preallocation": "off" | 154 | + vm.qmp_log('block-export-del', id='exp0') |
128 | + } | 155 | + vm.event_wait('BLOCK_EXPORT_DELETED') |
129 | +} | 156 | + vm.qmp_log('query-block-exports') |
130 | +{ "execute": "quit" } | 157 | + |
131 | +EOF | 158 | + iotests.log('\n=== Inactivating node with existing export ===') |
132 | + | 159 | + |
133 | +_img_info --format-specific | _filter_img_info --format-specific | 160 | + iotests.log('\nInactivating nodes with an export fails without ' |
134 | +$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map | 161 | + 'allow-inactive') |
135 | + | 162 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) |
136 | +echo | 163 | + vm.qmp_log('block-export-add', **get_export(node_name='disk-fmt')) |
137 | +echo "=== Successful image creation (with non-default options) ===" | 164 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) |
138 | +echo | 165 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
139 | + | 166 | + vm.qmp_log('query-block-exports') |
140 | +# Choose a different size to show that we got a new image | 167 | + vm.qmp_log('block-export-del', id='exp0') |
141 | +size=$((32 * 1024 * 1024)) | 168 | + vm.event_wait('BLOCK_EXPORT_DELETED') |
142 | + | 169 | + vm.qmp_log('query-block-exports') |
143 | +run_qemu <<EOF | 170 | + |
144 | +{ "execute": "qmp_capabilities" } | 171 | + iotests.log('\nInactivating nodes with an export fails with ' |
145 | +{ "execute": "x-blockdev-create", | 172 | + 'allow-inactive=false') |
146 | + "arguments": { | 173 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) |
147 | + "driver": "file", | 174 | + vm.qmp_log('block-export-add', |
148 | + "filename": "$TEST_IMG", | 175 | + **get_export(node_name='disk-fmt', allow_inactive=False)) |
149 | + "size": 0 | 176 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) |
150 | + } | 177 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
151 | +} | 178 | + vm.qmp_log('query-block-exports') |
152 | +{ "execute": "x-blockdev-create", | 179 | + vm.qmp_log('block-export-del', id='exp0') |
153 | + "arguments": { | 180 | + vm.event_wait('BLOCK_EXPORT_DELETED') |
154 | + "driver": "$IMGFMT", | 181 | + vm.qmp_log('query-block-exports') |
155 | + "file": { | 182 | + |
156 | + "driver": "file", | 183 | + iotests.log('\nInactivating nodes with an export works with ' |
157 | + "filename": "$TEST_IMG" | 184 | + 'allow-inactive=true') |
158 | + }, | 185 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) |
159 | + "size": $size, | 186 | + vm.qmp_log('block-export-add', |
160 | + "preallocation": "metadata" | 187 | + **get_export(node_name='disk-fmt', allow_inactive=True)) |
161 | + } | 188 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) |
162 | +} | 189 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
163 | +{ "execute": "quit" } | 190 | + vm.qmp_log('query-block-exports') |
164 | +EOF | 191 | + vm.qmp_log('block-export-del', id='exp0') |
165 | + | 192 | + vm.event_wait('BLOCK_EXPORT_DELETED') |
166 | +_img_info --format-specific | _filter_img_info --format-specific | 193 | + vm.qmp_log('query-block-exports') |
167 | +$QEMU_IMG map --output=json "$TEST_IMG" | _filter_qemu_img_map | 194 | + |
168 | + | 195 | + iotests.log('\n=== Inactive nodes with parent ===') |
169 | +echo | 196 | + |
170 | +echo "=== Invalid BlockdevRef ===" | 197 | + iotests.log('\nInactivating nodes with an active parent fails') |
171 | +echo | 198 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) |
172 | + | 199 | + vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) |
173 | +run_qemu <<EOF | 200 | + iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) |
174 | +{ "execute": "qmp_capabilities" } | 201 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
175 | +{ "execute": "x-blockdev-create", | 202 | + |
176 | + "arguments": { | 203 | + iotests.log('\nInactivating nodes with an inactive parent works') |
177 | + "driver": "$IMGFMT", | 204 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=False) |
178 | + "file": "this doesn't exist", | 205 | + vm.qmp_log('blockdev-set-active', node_name='disk-file', active=False) |
179 | + "size": $size | 206 | + iotests.log('disk-file active: %s' % node_is_active(vm, 'disk-file')) |
180 | + } | 207 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
181 | +} | 208 | + |
182 | +{ "execute": "quit" } | 209 | + iotests.log('\nCreating active parent node with an inactive child fails') |
183 | +EOF | 210 | + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', |
184 | + | 211 | + node_name='disk-filter') |
185 | +echo | 212 | + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', |
186 | +echo "=== Zero size ===" | 213 | + node_name='disk-filter', active=True) |
187 | +echo | 214 | + |
188 | + | 215 | + iotests.log('\nCreating inactive parent node with an inactive child works') |
189 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | 216 | + vm.qmp_log('blockdev-add', driver='raw', file='disk-fmt', |
190 | +{ "execute": "qmp_capabilities" } | 217 | + node_name='disk-filter', active=False) |
191 | +{ "execute": "x-blockdev-create", | 218 | + vm.qmp_log('blockdev-del', node_name='disk-filter') |
192 | + "arguments": { | 219 | + |
193 | + "driver": "$IMGFMT", | 220 | + iotests.log('\n=== Resizing an inactive node ===') |
194 | + "file": "node0", | 221 | + vm.qmp_log('block_resize', node_name='disk-fmt', size=16*1024*1024) |
195 | + "size": 0 | 222 | + |
196 | + } | 223 | + iotests.log('\n=== Taking a snapshot of an inactive node ===') |
197 | +} | 224 | + |
198 | +{ "execute": "quit" } | 225 | + iotests.log('\nActive overlay over inactive backing file automatically ' |
199 | +EOF | 226 | + 'makes both inactive for compatibility') |
200 | + | 227 | + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', |
201 | +_img_info | _filter_img_info | 228 | + file='snap-file', backing=None) |
202 | + | 229 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
203 | +echo | 230 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) |
204 | +echo "=== Maximum size ===" | 231 | + vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') |
205 | +echo | 232 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
206 | + | 233 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) |
207 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | 234 | + vm.qmp_log('blockdev-del', node_name='snap-fmt') |
208 | +{ "execute": "qmp_capabilities" } | 235 | + |
209 | +{ "execute": "x-blockdev-create", | 236 | + iotests.log('\nInactive overlay over inactive backing file just works') |
210 | + "arguments": { | 237 | + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap-fmt', |
211 | + "driver": "$IMGFMT", | 238 | + file='snap-file', backing=None, active=False) |
212 | + "file": "node0", | 239 | + vm.qmp_log('blockdev-snapshot', node='disk-fmt', overlay='snap-fmt') |
213 | + "size": 562949819203584 | 240 | + |
214 | + } | 241 | + iotests.log('\n=== Block jobs with inactive nodes ===') |
215 | +} | 242 | + |
216 | +{ "execute": "quit" } | 243 | + iotests.log('\nStreaming into an inactive node') |
217 | +EOF | 244 | + vm.qmp_log('block-stream', device='snap-fmt', |
218 | + | 245 | + filters=[iotests.filter_qmp_generated_node_ids]) |
219 | +_img_info | _filter_img_info | 246 | + |
220 | + | 247 | + iotests.log('\nCommitting an inactive root node (active commit)') |
221 | +echo | 248 | + vm.qmp_log('block-commit', job_id='job0', device='snap-fmt', |
222 | +echo "=== Invalid sizes ===" | 249 | + filters=[iotests.filter_qmp_generated_node_ids]) |
223 | +echo | 250 | + |
224 | + | 251 | + iotests.log('\nCommitting an inactive intermediate node to inactive base') |
225 | +# TODO Negative image sizes aren't handled correctly, but this is a problem | 252 | + vm.qmp_log('blockdev-add', driver='qcow2', node_name='snap2-fmt', |
226 | +# with QAPI's implementation of the 'size' type and affects other commands as | 253 | + file='snap2-file', backing='snap-fmt', active=False) |
227 | +# well. Once this is fixed, we may want to add a test case here. | 254 | + |
228 | + | 255 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
229 | +# 1. 2^64 - 512 | 256 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) |
230 | +# 2. 2^63 = 8 EB (qemu-img enforces image sizes less than this) | 257 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) |
231 | +# 3. 0x1fffff8000001 (one byte more than maximum image size for VDI) | 258 | + |
232 | + | 259 | + vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', |
233 | +run_qemu -blockdev driver=file,filename="$TEST_IMG",node-name=node0 <<EOF | 260 | + top_node='snap-fmt', |
234 | +{ "execute": "qmp_capabilities" } | 261 | + filters=[iotests.filter_qmp_generated_node_ids]) |
235 | +{ "execute": "x-blockdev-create", | 262 | + |
236 | + "arguments": { | 263 | + iotests.log('\nCommitting an inactive intermediate node to active base') |
237 | + "driver": "$IMGFMT", | 264 | + vm.qmp_log('blockdev-set-active', node_name='disk-fmt', active=True) |
238 | + "file": "node0", | 265 | + vm.qmp_log('block-commit', job_id='job0', device='snap2-fmt', |
239 | + "size": 18446744073709551104 | 266 | + top_node='snap-fmt', |
240 | + } | 267 | + filters=[iotests.filter_qmp_generated_node_ids]) |
241 | +} | 268 | + |
242 | +{ "execute": "x-blockdev-create", | 269 | + iotests.log('\nMirror from inactive source to active target') |
243 | + "arguments": { | 270 | + vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', |
244 | + "driver": "$IMGFMT", | 271 | + target='target-fmt', sync='full', |
245 | + "file": "node0", | 272 | + filters=[iotests.filter_qmp_generated_node_ids]) |
246 | + "size": 9223372036854775808 | 273 | + |
247 | + } | 274 | + iotests.log('\nMirror from active source to inactive target') |
248 | +} | 275 | + |
249 | +{ "execute": "x-blockdev-create", | 276 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
250 | + "arguments": { | 277 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) |
251 | + "driver": "$IMGFMT", | 278 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) |
252 | + "file": "node0", | 279 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) |
253 | + "size": 562949819203585 | 280 | + |
254 | + } | 281 | + # Activating snap2-fmt recursively activates the whole backing chain |
255 | +} | 282 | + vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=True) |
256 | +{ "execute": "quit" } | 283 | + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) |
257 | +EOF | 284 | + |
258 | + | 285 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) |
259 | +# success, all done | 286 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) |
260 | +echo "*** done" | 287 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) |
261 | +rm -f $seq.full | 288 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) |
262 | +status=0 | 289 | + |
263 | diff --git a/tests/qemu-iotests/211.out b/tests/qemu-iotests/211.out | 290 | + vm.qmp_log('blockdev-mirror', job_id='job0', device='snap2-fmt', |
291 | + target='target-fmt', sync='full', | ||
292 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
293 | + | ||
294 | + iotests.log('\nBackup from active source to inactive target') | ||
295 | + | ||
296 | + vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', | ||
297 | + target='target-fmt', sync='full', | ||
298 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
299 | + | ||
300 | + iotests.log('\nBackup from inactive source to active target') | ||
301 | + | ||
302 | + # Inactivating snap2-fmt recursively inactivates the whole backing chain | ||
303 | + vm.qmp_log('blockdev-set-active', node_name='snap2-fmt', active=False) | ||
304 | + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=True) | ||
305 | + | ||
306 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
307 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
308 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) | ||
309 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) | ||
310 | + | ||
311 | + vm.qmp_log('blockdev-backup', job_id='job0', device='snap2-fmt', | ||
312 | + target='target-fmt', sync='full', | ||
313 | + filters=[iotests.filter_qmp_generated_node_ids]) | ||
314 | + | ||
315 | + iotests.log('\n=== Accessing export on inactive node ===') | ||
316 | + | ||
317 | + # Use the target node because it has the right image format and isn't the | ||
318 | + # (read-only) backing file of a qcow2 node | ||
319 | + vm.qmp_log('blockdev-set-active', node_name='target-fmt', active=False) | ||
320 | + vm.qmp_log('block-export-add', | ||
321 | + **get_export(node_name='target-fmt', allow_inactive=True)) | ||
322 | + | ||
323 | + # The read should succeed, everything else should fail gracefully | ||
324 | + qemu_io = QemuIoInteractive('-f', 'raw', | ||
325 | + f'nbd+unix:///target-fmt?socket={nbd_sock}') | ||
326 | + iotests.log(qemu_io.cmd('read 0 64k'), filters=[filter_qemu_io]) | ||
327 | + iotests.log(qemu_io.cmd('write 0 64k'), filters=[filter_qemu_io]) | ||
328 | + iotests.log(qemu_io.cmd('write -z 0 64k'), filters=[filter_qemu_io]) | ||
329 | + iotests.log(qemu_io.cmd('write -zu 0 64k'), filters=[filter_qemu_io]) | ||
330 | + iotests.log(qemu_io.cmd('discard 0 64k'), filters=[filter_qemu_io]) | ||
331 | + iotests.log(qemu_io.cmd('flush'), filters=[filter_qemu_io]) | ||
332 | + iotests.log(qemu_io.cmd('map'), filters=[filter_qemu_io]) | ||
333 | + qemu_io.close() | ||
334 | + | ||
335 | + iotests.log('\n=== Resuming VM activates all images ===') | ||
336 | + vm.qmp_log('cont') | ||
337 | + | ||
338 | + iotests.log('disk-fmt active: %s' % node_is_active(vm, 'disk-fmt')) | ||
339 | + iotests.log('snap-fmt active: %s' % node_is_active(vm, 'snap-fmt')) | ||
340 | + iotests.log('snap2-fmt active: %s' % node_is_active(vm, 'snap2-fmt')) | ||
341 | + iotests.log('target-fmt active: %s' % node_is_active(vm, 'target-fmt')) | ||
342 | + | ||
343 | + iotests.log('\nShutting down...') | ||
344 | + vm.shutdown() | ||
345 | + log = vm.get_log() | ||
346 | + if log: | ||
347 | + iotests.log(log, [filter_qtest, filter_qemu_io]) | ||
348 | diff --git a/tests/qemu-iotests/tests/inactive-node-nbd.out b/tests/qemu-iotests/tests/inactive-node-nbd.out | ||
264 | new file mode 100644 | 349 | new file mode 100644 |
265 | index XXXXXXX..XXXXXXX | 350 | index XXXXXXX..XXXXXXX |
266 | --- /dev/null | 351 | --- /dev/null |
267 | +++ b/tests/qemu-iotests/211.out | 352 | +++ b/tests/qemu-iotests/tests/inactive-node-nbd.out |
268 | @@ -XXX,XX +XXX,XX @@ | 353 | @@ -XXX,XX +XXX,XX @@ |
269 | +QA output created by 211 | 354 | +Preparing disk... |
270 | + | 355 | +Launching VM... |
271 | +=== Successful image creation (defaults) === | 356 | +{"execute": "nbd-server-start", "arguments": {"addr": {"data": {"path": "SOCK_DIR/PID-nbd.sock"}, "type": "unix"}}} |
272 | + | 357 | +{"return": {}} |
273 | +Testing: | 358 | + |
274 | +QMP_VERSION | 359 | +=== Creating export of inactive node === |
275 | +{"return": {}} | 360 | + |
276 | +{"return": {}} | 361 | +Exports activate nodes without allow-inactive |
277 | +{"return": {}} | 362 | +disk-fmt active: False |
278 | +{"return": {}} | 363 | +{"execute": "block-export-add", "arguments": {"id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} |
279 | +{"return": {}} | 364 | +{"return": {}} |
280 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 365 | +disk-fmt active: True |
281 | + | 366 | +{"execute": "query-block-exports", "arguments": {}} |
282 | +image: TEST_DIR/t.IMGFMT | 367 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} |
283 | +file format: IMGFMT | 368 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} |
284 | +virtual size: 128M (134217728 bytes) | 369 | +{"return": {}} |
285 | +[{ "start": 0, "length": 134217728, "depth": 0, "zero": true, "data": false}] | 370 | +{"execute": "query-block-exports", "arguments": {}} |
286 | + | 371 | +{"return": []} |
287 | +=== Successful image creation (explicit defaults) === | 372 | + |
288 | + | 373 | +Exports activate nodes with allow-inactive=false |
289 | +Testing: | 374 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} |
290 | +QMP_VERSION | 375 | +{"return": {}} |
291 | +{"return": {}} | 376 | +disk-fmt active: False |
292 | +{"return": {}} | 377 | +{"execute": "block-export-add", "arguments": {"allow-inactive": false, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} |
293 | +{"return": {}} | 378 | +{"return": {}} |
294 | +{"return": {}} | 379 | +disk-fmt active: True |
295 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 380 | +{"execute": "query-block-exports", "arguments": {}} |
296 | + | 381 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} |
297 | +image: TEST_DIR/t.IMGFMT | 382 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} |
298 | +file format: IMGFMT | 383 | +{"return": {}} |
299 | +virtual size: 64M (67108864 bytes) | 384 | +{"execute": "query-block-exports", "arguments": {}} |
300 | +[{ "start": 0, "length": 67108864, "depth": 0, "zero": true, "data": false}] | 385 | +{"return": []} |
301 | + | 386 | + |
302 | +=== Successful image creation (with non-default options) === | 387 | +Export leaves nodes inactive with allow-inactive=true |
303 | + | 388 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} |
304 | +Testing: | 389 | +{"return": {}} |
305 | +QMP_VERSION | 390 | +disk-fmt active: False |
306 | +{"return": {}} | 391 | +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} |
307 | +{"return": {}} | 392 | +{"return": {}} |
308 | +{"return": {}} | 393 | +disk-fmt active: False |
309 | +{"return": {}} | 394 | +{"execute": "query-block-exports", "arguments": {}} |
310 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 395 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} |
311 | + | 396 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} |
312 | +image: TEST_DIR/t.IMGFMT | 397 | +{"return": {}} |
313 | +file format: IMGFMT | 398 | +{"execute": "query-block-exports", "arguments": {}} |
314 | +virtual size: 32M (33554432 bytes) | 399 | +{"return": []} |
315 | +[{ "start": 0, "length": 3072, "depth": 0, "zero": false, "data": true, "offset": OFFSET}, | 400 | + |
316 | +{ "start": 3072, "length": 33551360, "depth": 0, "zero": true, "data": true, "offset": OFFSET}] | 401 | +=== Inactivating node with existing export === |
317 | + | 402 | + |
318 | +=== Invalid BlockdevRef === | 403 | +Inactivating nodes with an export fails without allow-inactive |
319 | + | 404 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} |
320 | +Testing: | 405 | +{"return": {}} |
321 | +QMP_VERSION | 406 | +{"execute": "block-export-add", "arguments": {"id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} |
322 | +{"return": {}} | 407 | +{"return": {}} |
323 | +{"error": {"class": "GenericError", "desc": "Cannot find device=this doesn't exist nor node_name=this doesn't exist"}} | 408 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} |
324 | +{"return": {}} | 409 | +{"error": {"class": "GenericError", "desc": "Failed to inactivate node: Operation not permitted"}} |
325 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 410 | +disk-fmt active: True |
326 | + | 411 | +{"execute": "query-block-exports", "arguments": {}} |
327 | + | 412 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} |
328 | +=== Zero size === | 413 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} |
329 | + | 414 | +{"return": {}} |
330 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | 415 | +{"execute": "query-block-exports", "arguments": {}} |
331 | +QMP_VERSION | 416 | +{"return": []} |
332 | +{"return": {}} | 417 | + |
333 | +{"return": {}} | 418 | +Inactivating nodes with an export fails with allow-inactive=false |
334 | +{"return": {}} | 419 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} |
335 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 420 | +{"return": {}} |
336 | + | 421 | +{"execute": "block-export-add", "arguments": {"allow-inactive": false, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} |
337 | +image: TEST_DIR/t.IMGFMT | 422 | +{"return": {}} |
338 | +file format: IMGFMT | 423 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} |
339 | +virtual size: 0 (0 bytes) | 424 | +{"error": {"class": "GenericError", "desc": "Failed to inactivate node: Operation not permitted"}} |
340 | + | 425 | +disk-fmt active: True |
341 | +=== Maximum size === | 426 | +{"execute": "query-block-exports", "arguments": {}} |
342 | + | 427 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} |
343 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | 428 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} |
344 | +QMP_VERSION | 429 | +{"return": {}} |
345 | +{"return": {}} | 430 | +{"execute": "query-block-exports", "arguments": {}} |
346 | +{"return": {}} | 431 | +{"return": []} |
347 | +{"return": {}} | 432 | + |
348 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 433 | +Inactivating nodes with an export works with allow-inactive=true |
349 | + | 434 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} |
350 | +image: TEST_DIR/t.IMGFMT | 435 | +{"return": {}} |
351 | +file format: IMGFMT | 436 | +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "disk-fmt", "type": "nbd", "writable": true}} |
352 | +virtual size: 512T (562949819203584 bytes) | 437 | +{"return": {}} |
353 | + | 438 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} |
354 | +=== Invalid sizes === | 439 | +{"return": {}} |
355 | + | 440 | +disk-fmt active: False |
356 | +Testing: -blockdev driver=file,filename=TEST_DIR/t.IMGFMT,node-name=node0 | 441 | +{"execute": "query-block-exports", "arguments": {}} |
357 | +QMP_VERSION | 442 | +{"return": [{"id": "exp0", "node-name": "disk-fmt", "shutting-down": false, "type": "nbd"}]} |
358 | +{"return": {}} | 443 | +{"execute": "block-export-del", "arguments": {"id": "exp0"}} |
359 | +{"error": {"class": "GenericError", "desc": "Unsupported VDI image size (size is 0xfffffffffffffe00, max supported is 0x1fffff8000000)"}} | 444 | +{"return": {}} |
360 | +{"error": {"class": "GenericError", "desc": "Unsupported VDI image size (size is 0x8000000000000000, max supported is 0x1fffff8000000)"}} | 445 | +{"execute": "query-block-exports", "arguments": {}} |
361 | +{"error": {"class": "GenericError", "desc": "Unsupported VDI image size (size is 0x1fffff8000001, max supported is 0x1fffff8000000)"}} | 446 | +{"return": []} |
362 | +{"return": {}} | 447 | + |
363 | +{"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} | 448 | +=== Inactive nodes with parent === |
364 | + | 449 | + |
365 | +*** done | 450 | +Inactivating nodes with an active parent fails |
366 | diff --git a/tests/qemu-iotests/group b/tests/qemu-iotests/group | 451 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} |
367 | index XXXXXXX..XXXXXXX 100644 | 452 | +{"return": {}} |
368 | --- a/tests/qemu-iotests/group | 453 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-file"}} |
369 | +++ b/tests/qemu-iotests/group | 454 | +{"error": {"class": "GenericError", "desc": "Node has active parent node"}} |
370 | @@ -XXX,XX +XXX,XX @@ | 455 | +disk-file active: True |
371 | 208 rw auto quick | 456 | +disk-fmt active: True |
372 | 209 rw auto quick | 457 | + |
373 | 210 rw auto | 458 | +Inactivating nodes with an inactive parent works |
374 | +211 rw auto quick | 459 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-fmt"}} |
460 | +{"return": {}} | ||
461 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "disk-file"}} | ||
462 | +{"return": {}} | ||
463 | +disk-file active: False | ||
464 | +disk-fmt active: False | ||
465 | + | ||
466 | +Creating active parent node with an inactive child fails | ||
467 | +{"execute": "blockdev-add", "arguments": {"driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} | ||
468 | +{"error": {"class": "GenericError", "desc": "Inactive 'disk-fmt' can't be a file child of active 'disk-filter'"}} | ||
469 | +{"execute": "blockdev-add", "arguments": {"active": true, "driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} | ||
470 | +{"error": {"class": "GenericError", "desc": "Inactive 'disk-fmt' can't be a file child of active 'disk-filter'"}} | ||
471 | + | ||
472 | +Creating inactive parent node with an inactive child works | ||
473 | +{"execute": "blockdev-add", "arguments": {"active": false, "driver": "raw", "file": "disk-fmt", "node-name": "disk-filter"}} | ||
474 | +{"return": {}} | ||
475 | +{"execute": "blockdev-del", "arguments": {"node-name": "disk-filter"}} | ||
476 | +{"return": {}} | ||
477 | + | ||
478 | +=== Resizing an inactive node === | ||
479 | +{"execute": "block_resize", "arguments": {"node-name": "disk-fmt", "size": 16777216}} | ||
480 | +{"error": {"class": "GenericError", "desc": "Permission 'resize' unavailable on inactive node"}} | ||
481 | + | ||
482 | +=== Taking a snapshot of an inactive node === | ||
483 | + | ||
484 | +Active overlay over inactive backing file automatically makes both inactive for compatibility | ||
485 | +{"execute": "blockdev-add", "arguments": {"backing": null, "driver": "qcow2", "file": "snap-file", "node-name": "snap-fmt"}} | ||
486 | +{"return": {}} | ||
487 | +disk-fmt active: False | ||
488 | +snap-fmt active: True | ||
489 | +{"execute": "blockdev-snapshot", "arguments": {"node": "disk-fmt", "overlay": "snap-fmt"}} | ||
490 | +{"return": {}} | ||
491 | +disk-fmt active: False | ||
492 | +snap-fmt active: False | ||
493 | +{"execute": "blockdev-del", "arguments": {"node-name": "snap-fmt"}} | ||
494 | +{"return": {}} | ||
495 | + | ||
496 | +Inactive overlay over inactive backing file just works | ||
497 | +{"execute": "blockdev-add", "arguments": {"active": false, "backing": null, "driver": "qcow2", "file": "snap-file", "node-name": "snap-fmt"}} | ||
498 | +{"return": {}} | ||
499 | +{"execute": "blockdev-snapshot", "arguments": {"node": "disk-fmt", "overlay": "snap-fmt"}} | ||
500 | +{"return": {}} | ||
501 | + | ||
502 | +=== Block jobs with inactive nodes === | ||
503 | + | ||
504 | +Streaming into an inactive node | ||
505 | +{"execute": "block-stream", "arguments": {"device": "snap-fmt"}} | ||
506 | +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'snap-fmt' can't be a file child of active 'NODE_NAME'"}} | ||
507 | + | ||
508 | +Committing an inactive root node (active commit) | ||
509 | +{"execute": "block-commit", "arguments": {"device": "snap-fmt", "job-id": "job0"}} | ||
510 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
511 | + | ||
512 | +Committing an inactive intermediate node to inactive base | ||
513 | +{"execute": "blockdev-add", "arguments": {"active": false, "backing": "snap-fmt", "driver": "qcow2", "file": "snap2-file", "node-name": "snap2-fmt"}} | ||
514 | +{"return": {}} | ||
515 | +disk-fmt active: False | ||
516 | +snap-fmt active: False | ||
517 | +snap2-fmt active: False | ||
518 | +{"execute": "block-commit", "arguments": {"device": "snap2-fmt", "job-id": "job0", "top-node": "snap-fmt"}} | ||
519 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
520 | + | ||
521 | +Committing an inactive intermediate node to active base | ||
522 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "disk-fmt"}} | ||
523 | +{"return": {}} | ||
524 | +{"execute": "block-commit", "arguments": {"device": "snap2-fmt", "job-id": "job0", "top-node": "snap-fmt"}} | ||
525 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
526 | + | ||
527 | +Mirror from inactive source to active target | ||
528 | +{"execute": "blockdev-mirror", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
529 | +{"error": {"class": "GenericError", "desc": "Inactive 'snap2-fmt' can't be a backing child of active 'NODE_NAME'"}} | ||
530 | + | ||
531 | +Mirror from active source to inactive target | ||
532 | +disk-fmt active: True | ||
533 | +snap-fmt active: False | ||
534 | +snap2-fmt active: False | ||
535 | +target-fmt active: True | ||
536 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "snap2-fmt"}} | ||
537 | +{"return": {}} | ||
538 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "target-fmt"}} | ||
539 | +{"return": {}} | ||
540 | +disk-fmt active: True | ||
541 | +snap-fmt active: True | ||
542 | +snap2-fmt active: True | ||
543 | +target-fmt active: False | ||
544 | +{"execute": "blockdev-mirror", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
545 | +{"error": {"class": "GenericError", "desc": "Permission 'write' unavailable on inactive node"}} | ||
546 | + | ||
547 | +Backup from active source to inactive target | ||
548 | +{"execute": "blockdev-backup", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
549 | +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'target-fmt' can't be a target child of active 'NODE_NAME'"}} | ||
550 | + | ||
551 | +Backup from inactive source to active target | ||
552 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "snap2-fmt"}} | ||
553 | +{"return": {}} | ||
554 | +{"execute": "blockdev-set-active", "arguments": {"active": true, "node-name": "target-fmt"}} | ||
555 | +{"return": {}} | ||
556 | +disk-fmt active: False | ||
557 | +snap-fmt active: False | ||
558 | +snap2-fmt active: False | ||
559 | +target-fmt active: True | ||
560 | +{"execute": "blockdev-backup", "arguments": {"device": "snap2-fmt", "job-id": "job0", "sync": "full", "target": "target-fmt"}} | ||
561 | +{"error": {"class": "GenericError", "desc": "Could not create node: Inactive 'snap2-fmt' can't be a file child of active 'NODE_NAME'"}} | ||
562 | + | ||
563 | +=== Accessing export on inactive node === | ||
564 | +{"execute": "blockdev-set-active", "arguments": {"active": false, "node-name": "target-fmt"}} | ||
565 | +{"return": {}} | ||
566 | +{"execute": "block-export-add", "arguments": {"allow-inactive": true, "id": "exp0", "node-name": "target-fmt", "type": "nbd", "writable": true}} | ||
567 | +{"return": {}} | ||
568 | +read 65536/65536 bytes at offset 0 | ||
569 | +64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) | ||
570 | + | ||
571 | +write failed: Operation not permitted | ||
572 | + | ||
573 | +write failed: Operation not permitted | ||
574 | + | ||
575 | +write failed: Operation not permitted | ||
576 | + | ||
577 | +discard failed: Operation not permitted | ||
578 | + | ||
579 | + | ||
580 | +qemu-io: Failed to get allocation status: Operation not permitted | ||
581 | + | ||
582 | + | ||
583 | +=== Resuming VM activates all images === | ||
584 | +{"execute": "cont", "arguments": {}} | ||
585 | +{"return": {}} | ||
586 | +disk-fmt active: True | ||
587 | +snap-fmt active: True | ||
588 | +snap2-fmt active: True | ||
589 | +target-fmt active: True | ||
590 | + | ||
591 | +Shutting down... | ||
592 | + | ||
375 | -- | 593 | -- |
376 | 2.13.6 | 594 | 2.48.1 |
377 | |||
378 | diff view generated by jsdifflib |
1 | From: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 1 | From: Stefan Hajnoczi <stefanha@redhat.com> |
---|---|---|---|
2 | 2 | ||
3 | The protocol_name field is used when selecting a driver via protocol | 3 | BLOCK_OP_TYPE_DATAPLANE prevents BlockDriverState from being used by |
4 | syntax (i.e. <protocol_name>:<filename:options:...>). Drivers that are | 4 | virtio-blk/virtio-scsi with IOThread. Commit b112a65c52aa ("block: |
5 | only selected explicitly (e.g. driver=replication,mode=primary,...) | 5 | declare blockjobs and dataplane friends!") eliminated the main reason |
6 | should not have a protocol_name. | 6 | for this blocker in 2014. |
7 | 7 | ||
8 | This patch removes the protocol_name field from the brdv_replication | 8 | Nowadays the block layer supports I/O from multiple AioContexts, so |
9 | structure so that attempts to invoke this driver using protocol syntax | 9 | there is even less reason to block IOThread users. Any legitimate |
10 | will fail gracefully: | 10 | reasons related to interference would probably also apply to |
11 | non-IOThread users. | ||
11 | 12 | ||
12 | $ qemu-img info replication:foo | 13 | The only remaining users are bdrv_op_unblock(BLOCK_OP_TYPE_DATAPLANE) |
13 | qemu-img: Could not open 'replication:': Unknown protocol 'replication' | 14 | calls after bdrv_op_block_all(). If we remove BLOCK_OP_TYPE_DATAPLANE |
15 | their behavior doesn't change. | ||
14 | 16 | ||
15 | Buglink: https://bugs.launchpad.net/qemu/+bug/1726733 | 17 | Existing bdrv_op_block_all() callers that don't explicitly unblock |
16 | Signed-off-by: Fabiano Rosas <farosas@linux.vnet.ibm.com> | 18 | BLOCK_OP_TYPE_DATAPLANE seem to do so simply because no one bothered to |
17 | Reviewed-by: Max Reitz <mreitz@redhat.com> | 19 | rather than because it is necessary to keep BLOCK_OP_TYPE_DATAPLANE |
20 | blocked. | ||
21 | |||
22 | Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
23 | Message-ID: <20250203182529.269066-1-stefanha@redhat.com> | ||
24 | Reviewed-by: Eric Blake <eblake@redhat.com> | ||
25 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
18 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> | 26 | Signed-off-by: Kevin Wolf <kwolf@redhat.com> |
19 | --- | 27 | --- |
20 | replication.h | 1 - | 28 | include/block/block-common.h | 1 - |
21 | block/replication.c | 1 - | 29 | block/replication.c | 1 - |
22 | 2 files changed, 2 deletions(-) | 30 | blockjob.c | 2 -- |
31 | hw/block/virtio-blk.c | 9 --------- | ||
32 | hw/scsi/virtio-scsi.c | 3 --- | ||
33 | 5 files changed, 16 deletions(-) | ||
23 | 34 | ||
24 | diff --git a/replication.h b/replication.h | 35 | diff --git a/include/block/block-common.h b/include/block/block-common.h |
25 | index XXXXXXX..XXXXXXX 100644 | 36 | index XXXXXXX..XXXXXXX 100644 |
26 | --- a/replication.h | 37 | --- a/include/block/block-common.h |
27 | +++ b/replication.h | 38 | +++ b/include/block/block-common.h |
28 | @@ -XXX,XX +XXX,XX @@ typedef struct ReplicationState ReplicationState; | 39 | @@ -XXX,XX +XXX,XX @@ typedef enum BlockOpType { |
29 | * | 40 | BLOCK_OP_TYPE_CHANGE, |
30 | * BlockDriver bdrv_replication = { | 41 | BLOCK_OP_TYPE_COMMIT_SOURCE, |
31 | * .format_name = "replication", | 42 | BLOCK_OP_TYPE_COMMIT_TARGET, |
32 | - * .protocol_name = "replication", | 43 | - BLOCK_OP_TYPE_DATAPLANE, |
33 | * .instance_size = sizeof(BDRVReplicationState), | 44 | BLOCK_OP_TYPE_DRIVE_DEL, |
34 | * | 45 | BLOCK_OP_TYPE_EJECT, |
35 | * .bdrv_open = replication_open, | 46 | BLOCK_OP_TYPE_EXTERNAL_SNAPSHOT, |
36 | diff --git a/block/replication.c b/block/replication.c | 47 | diff --git a/block/replication.c b/block/replication.c |
37 | index XXXXXXX..XXXXXXX 100644 | 48 | index XXXXXXX..XXXXXXX 100644 |
38 | --- a/block/replication.c | 49 | --- a/block/replication.c |
39 | +++ b/block/replication.c | 50 | +++ b/block/replication.c |
40 | @@ -XXX,XX +XXX,XX @@ static void replication_stop(ReplicationState *rs, bool failover, Error **errp) | 51 | @@ -XXX,XX +XXX,XX @@ static void replication_start(ReplicationState *rs, ReplicationMode mode, |
41 | 52 | return; | |
42 | BlockDriver bdrv_replication = { | 53 | } |
43 | .format_name = "replication", | 54 | bdrv_op_block_all(top_bs, s->blocker); |
44 | - .protocol_name = "replication", | 55 | - bdrv_op_unblock(top_bs, BLOCK_OP_TYPE_DATAPLANE, s->blocker); |
45 | .instance_size = sizeof(BDRVReplicationState), | 56 | |
46 | 57 | bdrv_graph_wrunlock(); | |
47 | .bdrv_open = replication_open, | 58 | |
59 | diff --git a/blockjob.c b/blockjob.c | ||
60 | index XXXXXXX..XXXXXXX 100644 | ||
61 | --- a/blockjob.c | ||
62 | +++ b/blockjob.c | ||
63 | @@ -XXX,XX +XXX,XX @@ void *block_job_create(const char *job_id, const BlockJobDriver *driver, | ||
64 | goto fail; | ||
65 | } | ||
66 | |||
67 | - bdrv_op_unblock(bs, BLOCK_OP_TYPE_DATAPLANE, job->blocker); | ||
68 | - | ||
69 | if (!block_job_set_speed(job, speed, errp)) { | ||
70 | goto fail; | ||
71 | } | ||
72 | diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c | ||
73 | index XXXXXXX..XXXXXXX 100644 | ||
74 | --- a/hw/block/virtio-blk.c | ||
75 | +++ b/hw/block/virtio-blk.c | ||
76 | @@ -XXX,XX +XXX,XX @@ static bool virtio_blk_vq_aio_context_init(VirtIOBlock *s, Error **errp) | ||
77 | error_setg(errp, "ioeventfd is required for iothread"); | ||
78 | return false; | ||
79 | } | ||
80 | - | ||
81 | - /* | ||
82 | - * If ioeventfd is (re-)enabled while the guest is running there could | ||
83 | - * be block jobs that can conflict. | ||
84 | - */ | ||
85 | - if (blk_op_is_blocked(conf->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) { | ||
86 | - error_prepend(errp, "cannot start virtio-blk ioeventfd: "); | ||
87 | - return false; | ||
88 | - } | ||
89 | } | ||
90 | |||
91 | s->vq_aio_context = g_new(AioContext *, conf->num_queues); | ||
92 | diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c | ||
93 | index XXXXXXX..XXXXXXX 100644 | ||
94 | --- a/hw/scsi/virtio-scsi.c | ||
95 | +++ b/hw/scsi/virtio-scsi.c | ||
96 | @@ -XXX,XX +XXX,XX @@ static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev, | ||
97 | int ret; | ||
98 | |||
99 | if (s->ctx && !s->dataplane_fenced) { | ||
100 | - if (blk_op_is_blocked(sd->conf.blk, BLOCK_OP_TYPE_DATAPLANE, errp)) { | ||
101 | - return; | ||
102 | - } | ||
103 | ret = blk_set_aio_context(sd->conf.blk, s->ctx, errp); | ||
104 | if (ret < 0) { | ||
105 | return; | ||
48 | -- | 106 | -- |
49 | 2.13.6 | 107 | 2.48.1 |
50 | |||
51 | diff view generated by jsdifflib |