Block layer patches

- mirror: Fix active mirror deadlock
 - replication: Fix crashes due to operations on wrong BdrvChild
 - configure: Add option to use driver whitelist even in tools
 - vvfat: Fix crash when opening image read-write
 - export: Fix crash in error path with fixed-iothread=false
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCAAvFiEE3D3rFZqa+V09dFb+fwmycsiPL9YFAmD25yMRHGt3b2xmQHJl
 ZGhhdC5jb20ACgkQfwmycsiPL9aABA//d3/0gZjgQu6/cyH6y1CL/xMTIv4W+vq2
 3J/jrzEFpP0Jfx/8RrrUcS8kp0MOQ7xyU1CtN/lkM7l7LKoKmV0uSe1pav/sK2xv
 qVCOikAIouMZB1Mc2QTbGMgJA3aVUPod76f3YR4f0fGpsr9jODowpYAQ+coHzx/N
 51nL57A8VXqEjIQnV5+k3sCHiXI5Sdkwp+FFqTs9CGNOr4qyKMZKKrMWbIrlm5bn
 rs/b6mzUsfVP2y61wtDQLIomxY/VHE5oix6eEMvmvIq3tT5fICYoQSS2J6c7Qj2J
 l83P/NwAxu7EzGBAEvbr0nriu2NVRrKYkUso9/+0D4Y0xboOwTwgvKzK4sbdXPuH
 +XIY3S3LIcnX0UMKAdYYAdPf9UJBVQLTq58fBpPKvEcWNQwuKEezaBBZ19+rbBWm
 bC3Z8sxMrFla7/Elecv5PcJSXAsbK9+6m4ljhQMuN05KSABV8BbnU/6r+iNsoo+E
 K187Y4J4GPYED44CRyMcm6pTxJGKs13zMUSfTFpDiWOdx17/qZCdOSwCafvsap5b
 3VvMsfOgUfDrJ+ChaVAnMZpB556Nt83SuczqmEkZvq3xoH6t/NKPnH+rVGIll3Cc
 VyAAb7Sf9PADRKg/HPYUjt3axkM3ErEPpK7AUu+a756Ln+TAhMpARwjhPrpemkxQ
 sOJNFe3Ay+I=
 =3G16
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kevin/tags/for-upstream' into staging

Block layer patches

- mirror: Fix active mirror deadlock
- replication: Fix crashes due to operations on wrong BdrvChild
- configure: Add option to use driver whitelist even in tools
- vvfat: Fix crash when opening image read-write
- export: Fix crash in error path with fixed-iothread=false

# gpg: Signature made Tue 20 Jul 2021 16:09:23 BST
# gpg:                using RSA key DC3DEB159A9AF95D3D7456FE7F09B272C88F2FD6
# gpg:                issuer "kwolf@redhat.com"
# gpg: Good signature from "Kevin Wolf <kwolf@redhat.com>" [full]
# Primary key fingerprint: DC3D EB15 9A9A F95D 3D74  56FE 7F09 B272 C88F 2FD6

* remotes/kevin/tags/for-upstream:
  iotests/307: Test iothread conflict for exports
  block/export: Conditionally ignore set-context error
  block/vvfat: fix: drop backing
  replication: Remove workaround
  replication: Properly attach children
  replication: Reduce usage of s->hidden_disk and s->secondary_disk
  replication: Remove s->active_disk
  block: Add option to use driver whitelist even in tools
  block/mirror: fix active mirror dead-lock in mirror_wait_on_conflicts
  iotest 151: add test-case that shows active mirror dead-lock
  block/mirror: set .co for active-write MirrorOp objects

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2021-07-20 16:59:33 +01:00
commit c04b4d9e6b
11 changed files with 182 additions and 89 deletions

View file

@ -6162,6 +6162,9 @@ BlockDriverState *bdrv_find_backing_image(BlockDriverState *bs,
void bdrv_init(void)
{
#ifdef CONFIG_BDRV_WHITELIST_TOOLS
use_bdrv_whitelist = 1;
#endif
module_call_init(MODULE_INIT_BLOCK);
}

View file

@ -111,6 +111,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
if (export->has_iothread) {
IOThread *iothread;
AioContext *new_ctx;
Error **set_context_errp;
iothread = iothread_by_id(export->iothread);
if (!iothread) {
@ -120,7 +121,9 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
new_ctx = iothread_get_aio_context(iothread);
ret = bdrv_try_set_aio_context(bs, new_ctx, errp);
/* Ignore errors with fixed-iothread=false */
set_context_errp = fixed_iothread ? errp : NULL;
ret = bdrv_try_set_aio_context(bs, new_ctx, set_context_errp);
if (ret == 0) {
aio_context_release(ctx);
aio_context_acquire(new_ctx);

View file

@ -107,6 +107,7 @@ struct MirrorOp {
bool is_in_flight;
CoQueue waiting_requests;
Coroutine *co;
MirrorOp *waiting_for_op;
QTAILQ_ENTRY(MirrorOp) next;
};
@ -159,7 +160,18 @@ static void coroutine_fn mirror_wait_on_conflicts(MirrorOp *self,
if (ranges_overlap(self_start_chunk, self_nb_chunks,
op_start_chunk, op_nb_chunks))
{
/*
* If the operation is already (indirectly) waiting for us, or
* will wait for us as soon as it wakes up, then just go on
* (instead of producing a deadlock in the former case).
*/
if (op->waiting_for_op) {
continue;
}
self->waiting_for_op = op;
qemu_co_queue_wait(&op->waiting_requests, NULL);
self->waiting_for_op = NULL;
break;
}
}
@ -1343,6 +1355,7 @@ static MirrorOp *coroutine_fn active_write_prepare(MirrorBlockJob *s,
.bytes = bytes,
.is_active_write = true,
.is_in_flight = true,
.co = qemu_coroutine_self(),
};
qemu_co_queue_init(&op->waiting_requests);
QTAILQ_INSERT_TAIL(&s->ops_in_flight, op, next);

View file

@ -35,7 +35,6 @@ typedef enum {
typedef struct BDRVReplicationState {
ReplicationMode mode;
ReplicationStage stage;
BdrvChild *active_disk;
BlockJob *commit_job;
BdrvChild *hidden_disk;
BdrvChild *secondary_disk;
@ -166,7 +165,12 @@ static void replication_child_perm(BlockDriverState *bs, BdrvChild *c,
uint64_t perm, uint64_t shared,
uint64_t *nperm, uint64_t *nshared)
{
*nperm = BLK_PERM_CONSISTENT_READ;
if (role & BDRV_CHILD_PRIMARY) {
*nperm = BLK_PERM_CONSISTENT_READ;
} else {
*nperm = 0;
}
if ((bs->open_flags & (BDRV_O_INACTIVE | BDRV_O_RDWR)) == BDRV_O_RDWR) {
*nperm |= BLK_PERM_WRITE;
}
@ -307,8 +311,10 @@ out:
return ret;
}
static void secondary_do_checkpoint(BDRVReplicationState *s, Error **errp)
static void secondary_do_checkpoint(BlockDriverState *bs, Error **errp)
{
BDRVReplicationState *s = bs->opaque;
BdrvChild *active_disk = bs->file;
Error *local_err = NULL;
int ret;
@ -323,13 +329,13 @@ static void secondary_do_checkpoint(BDRVReplicationState *s, Error **errp)
return;
}
if (!s->active_disk->bs->drv) {
if (!active_disk->bs->drv) {
error_setg(errp, "Active disk %s is ejected",
s->active_disk->bs->node_name);
active_disk->bs->node_name);
return;
}
ret = bdrv_make_empty(s->active_disk, errp);
ret = bdrv_make_empty(active_disk, errp);
if (ret < 0) {
return;
}
@ -340,17 +346,7 @@ static void secondary_do_checkpoint(BDRVReplicationState *s, Error **errp)
return;
}
BlockBackend *blk = blk_new(qemu_get_current_aio_context(),
BLK_PERM_WRITE, BLK_PERM_ALL);
blk_insert_bs(blk, s->hidden_disk->bs, &local_err);
if (local_err) {
error_propagate(errp, local_err);
blk_unref(blk);
return;
}
ret = blk_make_empty(blk, errp);
blk_unref(blk);
ret = bdrv_make_empty(s->hidden_disk, errp);
if (ret < 0) {
return;
}
@ -365,27 +361,35 @@ static void reopen_backing_file(BlockDriverState *bs, bool writable,
Error **errp)
{
BDRVReplicationState *s = bs->opaque;
BdrvChild *hidden_disk, *secondary_disk;
BlockReopenQueue *reopen_queue = NULL;
/*
* s->hidden_disk and s->secondary_disk may not be set yet, as they will
* only be set after the children are writable.
*/
hidden_disk = bs->file->bs->backing;
secondary_disk = hidden_disk->bs->backing;
if (writable) {
s->orig_hidden_read_only = bdrv_is_read_only(s->hidden_disk->bs);
s->orig_secondary_read_only = bdrv_is_read_only(s->secondary_disk->bs);
s->orig_hidden_read_only = bdrv_is_read_only(hidden_disk->bs);
s->orig_secondary_read_only = bdrv_is_read_only(secondary_disk->bs);
}
bdrv_subtree_drained_begin(s->hidden_disk->bs);
bdrv_subtree_drained_begin(s->secondary_disk->bs);
bdrv_subtree_drained_begin(hidden_disk->bs);
bdrv_subtree_drained_begin(secondary_disk->bs);
if (s->orig_hidden_read_only) {
QDict *opts = qdict_new();
qdict_put_bool(opts, BDRV_OPT_READ_ONLY, !writable);
reopen_queue = bdrv_reopen_queue(reopen_queue, s->hidden_disk->bs,
reopen_queue = bdrv_reopen_queue(reopen_queue, hidden_disk->bs,
opts, true);
}
if (s->orig_secondary_read_only) {
QDict *opts = qdict_new();
qdict_put_bool(opts, BDRV_OPT_READ_ONLY, !writable);
reopen_queue = bdrv_reopen_queue(reopen_queue, s->secondary_disk->bs,
reopen_queue = bdrv_reopen_queue(reopen_queue, secondary_disk->bs,
opts, true);
}
@ -400,8 +404,8 @@ static void reopen_backing_file(BlockDriverState *bs, bool writable,
}
}
bdrv_subtree_drained_end(s->hidden_disk->bs);
bdrv_subtree_drained_end(s->secondary_disk->bs);
bdrv_subtree_drained_end(hidden_disk->bs);
bdrv_subtree_drained_end(secondary_disk->bs);
}
static void backup_job_cleanup(BlockDriverState *bs)
@ -458,6 +462,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
BlockDriverState *bs = rs->opaque;
BDRVReplicationState *s;
BlockDriverState *top_bs;
BdrvChild *active_disk, *hidden_disk, *secondary_disk;
int64_t active_length, hidden_length, disk_length;
AioContext *aio_context;
Error *local_err = NULL;
@ -495,32 +500,31 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
case REPLICATION_MODE_PRIMARY:
break;
case REPLICATION_MODE_SECONDARY:
s->active_disk = bs->file;
if (!s->active_disk || !s->active_disk->bs ||
!s->active_disk->bs->backing) {
active_disk = bs->file;
if (!active_disk || !active_disk->bs || !active_disk->bs->backing) {
error_setg(errp, "Active disk doesn't have backing file");
aio_context_release(aio_context);
return;
}
s->hidden_disk = s->active_disk->bs->backing;
if (!s->hidden_disk->bs || !s->hidden_disk->bs->backing) {
hidden_disk = active_disk->bs->backing;
if (!hidden_disk->bs || !hidden_disk->bs->backing) {
error_setg(errp, "Hidden disk doesn't have backing file");
aio_context_release(aio_context);
return;
}
s->secondary_disk = s->hidden_disk->bs->backing;
if (!s->secondary_disk->bs || !bdrv_has_blk(s->secondary_disk->bs)) {
secondary_disk = hidden_disk->bs->backing;
if (!secondary_disk->bs || !bdrv_has_blk(secondary_disk->bs)) {
error_setg(errp, "The secondary disk doesn't have block backend");
aio_context_release(aio_context);
return;
}
/* verify the length */
active_length = bdrv_getlength(s->active_disk->bs);
hidden_length = bdrv_getlength(s->hidden_disk->bs);
disk_length = bdrv_getlength(s->secondary_disk->bs);
active_length = bdrv_getlength(active_disk->bs);
hidden_length = bdrv_getlength(hidden_disk->bs);
disk_length = bdrv_getlength(secondary_disk->bs);
if (active_length < 0 || hidden_length < 0 || disk_length < 0 ||
active_length != hidden_length || hidden_length != disk_length) {
error_setg(errp, "Active disk, hidden disk, secondary disk's length"
@ -530,10 +534,10 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
}
/* Must be true, or the bdrv_getlength() calls would have failed */
assert(s->active_disk->bs->drv && s->hidden_disk->bs->drv);
assert(active_disk->bs->drv && hidden_disk->bs->drv);
if (!s->active_disk->bs->drv->bdrv_make_empty ||
!s->hidden_disk->bs->drv->bdrv_make_empty) {
if (!active_disk->bs->drv->bdrv_make_empty ||
!hidden_disk->bs->drv->bdrv_make_empty) {
error_setg(errp,
"Active disk or hidden disk doesn't support make_empty");
aio_context_release(aio_context);
@ -548,6 +552,26 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
return;
}
bdrv_ref(hidden_disk->bs);
s->hidden_disk = bdrv_attach_child(bs, hidden_disk->bs, "hidden disk",
&child_of_bds, BDRV_CHILD_DATA,
&local_err);
if (local_err) {
error_propagate(errp, local_err);
aio_context_release(aio_context);
return;
}
bdrv_ref(secondary_disk->bs);
s->secondary_disk = bdrv_attach_child(bs, secondary_disk->bs,
"secondary disk", &child_of_bds,
BDRV_CHILD_DATA, &local_err);
if (local_err) {
error_propagate(errp, local_err);
aio_context_release(aio_context);
return;
}
/* start backup job now */
error_setg(&s->blocker,
"Block device is in use by internal backup job");
@ -586,7 +610,7 @@ static void replication_start(ReplicationState *rs, ReplicationMode mode,
s->stage = BLOCK_REPLICATION_RUNNING;
if (s->mode == REPLICATION_MODE_SECONDARY) {
secondary_do_checkpoint(s, errp);
secondary_do_checkpoint(bs, errp);
}
s->error = 0;
@ -615,7 +639,7 @@ static void replication_do_checkpoint(ReplicationState *rs, Error **errp)
}
if (s->mode == REPLICATION_MODE_SECONDARY) {
secondary_do_checkpoint(s, errp);
secondary_do_checkpoint(bs, errp);
}
aio_context_release(aio_context);
}
@ -652,8 +676,9 @@ static void replication_done(void *opaque, int ret)
if (ret == 0) {
s->stage = BLOCK_REPLICATION_DONE;
s->active_disk = NULL;
bdrv_unref_child(bs, s->secondary_disk);
s->secondary_disk = NULL;
bdrv_unref_child(bs, s->hidden_disk);
s->hidden_disk = NULL;
s->error = 0;
} else {
@ -705,7 +730,7 @@ static void replication_stop(ReplicationState *rs, bool failover, Error **errp)
}
if (!failover) {
secondary_do_checkpoint(s, errp);
secondary_do_checkpoint(bs, errp);
s->stage = BLOCK_REPLICATION_DONE;
aio_context_release(aio_context);
return;
@ -713,7 +738,7 @@ static void replication_stop(ReplicationState *rs, bool failover, Error **errp)
s->stage = BLOCK_REPLICATION_FAILOVER;
s->commit_job = commit_active_start(
NULL, s->active_disk->bs, s->secondary_disk->bs,
NULL, bs->file->bs, s->secondary_disk->bs,
JOB_INTERNAL, 0, BLOCKDEV_ON_ERROR_REPORT,
NULL, replication_done, bs, true, errp);
break;

View file

@ -3098,26 +3098,6 @@ static int coroutine_fn vvfat_co_block_status(BlockDriverState *bs,
return BDRV_BLOCK_DATA;
}
static int coroutine_fn
write_target_commit(BlockDriverState *bs, uint64_t offset, uint64_t bytes,
QEMUIOVector *qiov, int flags)
{
int ret;
BDRVVVFATState* s = *((BDRVVVFATState**) bs->opaque);
qemu_co_mutex_lock(&s->lock);
ret = try_commit(s);
qemu_co_mutex_unlock(&s->lock);
return ret;
}
static BlockDriver vvfat_write_target = {
.format_name = "vvfat_write_target",
.instance_size = sizeof(void*),
.bdrv_co_pwritev = write_target_commit,
};
static void vvfat_qcow_options(BdrvChildRole role, bool parent_is_format,
int *child_flags, QDict *child_options,
int parent_flags, QDict *parent_options)
@ -3133,7 +3113,6 @@ static int enable_write_target(BlockDriverState *bs, Error **errp)
{
BDRVVVFATState *s = bs->opaque;
BlockDriver *bdrv_qcow = NULL;
BlockDriverState *backing;
QemuOpts *opts = NULL;
int ret;
int size = sector2cluster(s, s->sector_count);
@ -3184,13 +3163,6 @@ static int enable_write_target(BlockDriverState *bs, Error **errp)
unlink(s->qcow_filename);
#endif
backing = bdrv_new_open_driver(&vvfat_write_target, NULL, BDRV_O_ALLOW_RDWR,
&error_abort);
*(void**) backing->opaque = s;
bdrv_set_backing_hd(s->bs, backing, &error_abort);
bdrv_unref(backing);
return 0;
err:
@ -3205,17 +3177,10 @@ static void vvfat_child_perm(BlockDriverState *bs, BdrvChild *c,
uint64_t perm, uint64_t shared,
uint64_t *nperm, uint64_t *nshared)
{
if (role & BDRV_CHILD_DATA) {
/* This is a private node, nobody should try to attach to it */
*nperm = BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE;
*nshared = BLK_PERM_WRITE_UNCHANGED;
} else {
assert(role & BDRV_CHILD_COW);
/* The backing file is there so 'commit' can use it. vvfat doesn't
* access it in any way. */
*nperm = 0;
*nshared = BLK_PERM_ALL;
}
assert(role & BDRV_CHILD_DATA);
/* This is a private node, nobody should try to attach to it */
*nperm = BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE;
*nshared = BLK_PERM_WRITE_UNCHANGED;
}
static void vvfat_close(BlockDriverState *bs)

14
configure vendored
View file

@ -243,6 +243,7 @@ cross_prefix=""
audio_drv_list=""
block_drv_rw_whitelist=""
block_drv_ro_whitelist=""
block_drv_whitelist_tools="no"
host_cc="cc"
audio_win_int=""
libs_qga=""
@ -1016,6 +1017,10 @@ for opt do
;;
--block-drv-ro-whitelist=*) block_drv_ro_whitelist=$(echo "$optarg" | sed -e 's/,/ /g')
;;
--enable-block-drv-whitelist-in-tools) block_drv_whitelist_tools="yes"
;;
--disable-block-drv-whitelist-in-tools) block_drv_whitelist_tools="no"
;;
--enable-debug-tcg) debug_tcg="yes"
;;
--disable-debug-tcg) debug_tcg="no"
@ -1800,10 +1805,12 @@ Advanced options (experts only):
--block-drv-whitelist=L Same as --block-drv-rw-whitelist=L
--block-drv-rw-whitelist=L
set block driver read-write whitelist
(affects only QEMU, not qemu-img)
(by default affects only QEMU, not tools like qemu-img)
--block-drv-ro-whitelist=L
set block driver read-only whitelist
(affects only QEMU, not qemu-img)
(by default affects only QEMU, not tools like qemu-img)
--enable-block-drv-whitelist-in-tools
use block whitelist also in tools instead of only QEMU
--enable-trace-backends=B Set trace backend
Available backends: $trace_backend_list
--with-trace-file=NAME Full PATH,NAME of file to store traces
@ -4583,6 +4590,9 @@ if test "$audio_win_int" = "yes" ; then
fi
echo "CONFIG_BDRV_RW_WHITELIST=$block_drv_rw_whitelist" >> $config_host_mak
echo "CONFIG_BDRV_RO_WHITELIST=$block_drv_ro_whitelist" >> $config_host_mak
if test "$block_drv_whitelist_tools" = "yes" ; then
echo "CONFIG_BDRV_WHITELIST_TOOLS=y" >> $config_host_mak
fi
if test "$xfs" = "yes" ; then
echo "CONFIG_XFS=y" >> $config_host_mak
fi

View file

@ -2996,6 +2996,7 @@ summary_info += {'coroutine pool': config_host['CONFIG_COROUTINE_POOL'] == '1
if have_block
summary_info += {'Block whitelist (rw)': config_host['CONFIG_BDRV_RW_WHITELIST']}
summary_info += {'Block whitelist (ro)': config_host['CONFIG_BDRV_RO_WHITELIST']}
summary_info += {'Use block whitelist in tools': config_host.has_key('CONFIG_BDRV_WHITELIST_TOOLS')}
summary_info += {'VirtFS support': have_virtfs}
summary_info += {'build virtiofs daemon': have_virtiofsd}
summary_info += {'Live block migration': config_host.has_key('CONFIG_LIVE_BLOCK_MIGRATION')}

View file

@ -38,8 +38,9 @@ class TestActiveMirror(iotests.QMPTestCase):
'if': 'none',
'node-name': 'source-node',
'driver': iotests.imgfmt,
'file': {'driver': 'file',
'filename': source_img}}
'file': {'driver': 'blkdebug',
'image': {'driver': 'file',
'filename': source_img}}}
blk_target = {'node-name': 'target-node',
'driver': iotests.imgfmt,
@ -141,6 +142,55 @@ class TestActiveMirror(iotests.QMPTestCase):
self.potential_writes_in_flight = False
def testIntersectingActiveIO(self):
# Fill the source image
result = self.vm.hmp_qemu_io('source', 'write -P 1 0 2M')
# Start the block job (very slowly)
result = self.vm.qmp('blockdev-mirror',
job_id='mirror',
filter_node_name='mirror-node',
device='source-node',
target='target-node',
sync='full',
copy_mode='write-blocking',
speed=1)
self.vm.hmp_qemu_io('source', 'break write_aio A')
self.vm.hmp_qemu_io('source', 'aio_write 0 1M') # 1
self.vm.hmp_qemu_io('source', 'wait_break A')
self.vm.hmp_qemu_io('source', 'aio_write 0 2M') # 2
self.vm.hmp_qemu_io('source', 'aio_write 0 2M') # 3
# Now 2 and 3 are in mirror_wait_on_conflicts, waiting for 1
self.vm.hmp_qemu_io('source', 'break write_aio B')
self.vm.hmp_qemu_io('source', 'aio_write 1M 2M') # 4
self.vm.hmp_qemu_io('source', 'wait_break B')
# 4 doesn't wait for 2 and 3, because they didn't yet set
# in_flight_bitmap. So, nothing prevents 4 to go except for our
# break-point B.
self.vm.hmp_qemu_io('source', 'resume A')
# Now we resumed 1, so 2 and 3 goes to the next iteration of while loop
# in mirror_wait_on_conflicts(). They don't exit, as bitmap is dirty
# due to request 4.
# In the past at that point 2 and 3 would wait for each other producing
# a dead-lock. Now this is fixed and they will wait for request 4.
self.vm.hmp_qemu_io('source', 'resume B')
# After resuming 4, one of 2 and 3 goes first and set in_flight_bitmap,
# so the other will wait for it.
result = self.vm.qmp('block-job-set-speed', device='mirror', speed=0)
self.assert_qmp(result, 'return', {})
self.complete_and_wait(drive='mirror')
self.potential_writes_in_flight = False
if __name__ == '__main__':
iotests.main(supported_fmts=['qcow2', 'raw'],

View file

@ -1,5 +1,5 @@
...
....
----------------------------------------------------------------------
Ran 3 tests
Ran 4 tests
OK

View file

@ -41,9 +41,11 @@ with iotests.FilePath('image') as img, \
iotests.log('=== Launch VM ===')
vm.add_object('iothread,id=iothread0')
vm.add_object('iothread,id=iothread1')
vm.add_blockdev(f'file,filename={img},node-name=file')
vm.add_blockdev(f'{iotests.imgfmt},file=file,node-name=fmt')
vm.add_blockdev('raw,file=file,node-name=ro,read-only=on')
vm.add_blockdev('null-co,node-name=null')
vm.add_device(f'id=scsi0,driver=virtio-scsi,iothread=iothread0')
vm.launch()
@ -74,6 +76,19 @@ with iotests.FilePath('image') as img, \
vm.qmp_log('query-block-exports')
iotests.qemu_nbd_list_log('-k', socket)
iotests.log('\n=== Add export with conflicting iothread ===')
vm.qmp_log('device_add', id='sdb', driver='scsi-hd', drive='null')
# Should fail because of fixed-iothread
vm.qmp_log('block-export-add', id='export1', type='nbd', node_name='null',
iothread='iothread1', fixed_iothread=True, writable=True)
# Should ignore the iothread conflict, but then fail because of the
# permission conflict (and not crash)
vm.qmp_log('block-export-add', id='export1', type='nbd', node_name='null',
iothread='iothread1', fixed_iothread=False, writable=True)
iotests.log('\n=== Add a writable export ===')
# This fails because share-rw=off

View file

@ -51,6 +51,14 @@ exports available: 1
base:allocation
=== Add export with conflicting iothread ===
{"execute": "device_add", "arguments": {"drive": "null", "driver": "scsi-hd", "id": "sdb"}}
{"return": {}}
{"execute": "block-export-add", "arguments": {"fixed-iothread": true, "id": "export1", "iothread": "iothread1", "node-name": "null", "type": "nbd", "writable": true}}
{"error": {"class": "GenericError", "desc": "Cannot change iothread of active block backend"}}
{"execute": "block-export-add", "arguments": {"fixed-iothread": false, "id": "export1", "iothread": "iothread1", "node-name": "null", "type": "nbd", "writable": true}}
{"error": {"class": "GenericError", "desc": "Permission conflict on node 'null': permissions 'write' are both required by an unnamed block device (uses node 'null' as 'root' child) and unshared by block device 'sdb' (uses node 'null' as 'root' child)."}}
=== Add a writable export ===
{"execute": "block-export-add", "arguments": {"description": "This is the writable second export", "id": "export1", "name": "export1", "node-name": "fmt", "type": "nbd", "writable": true, "writethrough": true}}
{"error": {"class": "GenericError", "desc": "Permission conflict on node 'fmt': permissions 'write' are both required by an unnamed block device (uses node 'fmt' as 'root' child) and unshared by block device 'sda' (uses node 'fmt' as 'root' child)."}}