block: Support multiple reopening with x-blockdev-reopen
[ kwolf: Fixed AioContext locking ] Signed-off-by: Alberto Garcia <berto@igalia.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Message-Id: <20210708114709.206487-5-kwolf@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
parent
6cf42ca2f9
commit
3908b7a899
83
blockdev.c
83
blockdev.c
@ -3559,51 +3559,60 @@ fail:
|
|||||||
visit_free(v);
|
visit_free(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qmp_x_blockdev_reopen(BlockdevOptions *options, Error **errp)
|
void qmp_x_blockdev_reopen(BlockdevOptionsList *reopen_list, Error **errp)
|
||||||
{
|
{
|
||||||
BlockDriverState *bs;
|
BlockReopenQueue *queue = NULL;
|
||||||
AioContext *ctx;
|
GSList *drained = NULL;
|
||||||
QObject *obj;
|
|
||||||
Visitor *v = qobject_output_visitor_new(&obj);
|
|
||||||
BlockReopenQueue *queue;
|
|
||||||
QDict *qdict;
|
|
||||||
|
|
||||||
/* Check for the selected node name */
|
/* Add each one of the BDS that we want to reopen to the queue */
|
||||||
if (!options->has_node_name) {
|
for (; reopen_list != NULL; reopen_list = reopen_list->next) {
|
||||||
error_setg(errp, "node-name not specified");
|
BlockdevOptions *options = reopen_list->value;
|
||||||
goto fail;
|
BlockDriverState *bs;
|
||||||
|
AioContext *ctx;
|
||||||
|
QObject *obj;
|
||||||
|
Visitor *v;
|
||||||
|
QDict *qdict;
|
||||||
|
|
||||||
|
/* Check for the selected node name */
|
||||||
|
if (!options->has_node_name) {
|
||||||
|
error_setg(errp, "node-name not specified");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
bs = bdrv_find_node(options->node_name);
|
||||||
|
if (!bs) {
|
||||||
|
error_setg(errp, "Failed to find node with node-name='%s'",
|
||||||
|
options->node_name);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Put all options in a QDict and flatten it */
|
||||||
|
v = qobject_output_visitor_new(&obj);
|
||||||
|
visit_type_BlockdevOptions(v, NULL, &options, &error_abort);
|
||||||
|
visit_complete(v, &obj);
|
||||||
|
visit_free(v);
|
||||||
|
|
||||||
|
qdict = qobject_to(QDict, obj);
|
||||||
|
|
||||||
|
qdict_flatten(qdict);
|
||||||
|
|
||||||
|
ctx = bdrv_get_aio_context(bs);
|
||||||
|
aio_context_acquire(ctx);
|
||||||
|
|
||||||
|
bdrv_subtree_drained_begin(bs);
|
||||||
|
queue = bdrv_reopen_queue(queue, bs, qdict, false);
|
||||||
|
drained = g_slist_prepend(drained, bs);
|
||||||
|
|
||||||
|
aio_context_release(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bs = bdrv_find_node(options->node_name);
|
|
||||||
if (!bs) {
|
|
||||||
error_setg(errp, "Failed to find node with node-name='%s'",
|
|
||||||
options->node_name);
|
|
||||||
goto fail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Put all options in a QDict and flatten it */
|
|
||||||
visit_type_BlockdevOptions(v, NULL, &options, &error_abort);
|
|
||||||
visit_complete(v, &obj);
|
|
||||||
qdict = qobject_to(QDict, obj);
|
|
||||||
|
|
||||||
qdict_flatten(qdict);
|
|
||||||
|
|
||||||
/* Perform the reopen operation */
|
/* Perform the reopen operation */
|
||||||
ctx = bdrv_get_aio_context(bs);
|
|
||||||
aio_context_acquire(ctx);
|
|
||||||
bdrv_subtree_drained_begin(bs);
|
|
||||||
aio_context_release(ctx);
|
|
||||||
|
|
||||||
queue = bdrv_reopen_queue(NULL, bs, qdict, false);
|
|
||||||
bdrv_reopen_multiple(queue, errp);
|
bdrv_reopen_multiple(queue, errp);
|
||||||
|
queue = NULL;
|
||||||
ctx = bdrv_get_aio_context(bs);
|
|
||||||
aio_context_acquire(ctx);
|
|
||||||
bdrv_subtree_drained_end(bs);
|
|
||||||
aio_context_release(ctx);
|
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
visit_free(v);
|
bdrv_reopen_queue_free(queue);
|
||||||
|
g_slist_free_full(drained, (GDestroyNotify) bdrv_subtree_drained_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
void qmp_blockdev_del(const char *node_name, Error **errp)
|
void qmp_blockdev_del(const char *node_name, Error **errp)
|
||||||
|
@ -4221,13 +4221,15 @@
|
|||||||
##
|
##
|
||||||
# @x-blockdev-reopen:
|
# @x-blockdev-reopen:
|
||||||
#
|
#
|
||||||
# Reopens a block device using the given set of options. Any option
|
# Reopens one or more block devices using the given set of options.
|
||||||
# not specified will be reset to its default value regardless of its
|
# Any option not specified will be reset to its default value regardless
|
||||||
# previous status. If an option cannot be changed or a particular
|
# of its previous status. If an option cannot be changed or a particular
|
||||||
# driver does not support reopening then the command will return an
|
# driver does not support reopening then the command will return an
|
||||||
# error.
|
# error. All devices in the list are reopened in one transaction, so
|
||||||
|
# if one of them fails then the whole transaction is cancelled.
|
||||||
#
|
#
|
||||||
# The top-level @node-name option (from BlockdevOptions) must be
|
# The command receives a list of block devices to reopen. For each one
|
||||||
|
# of them, the top-level @node-name option (from BlockdevOptions) must be
|
||||||
# specified and is used to select the block device to be reopened.
|
# specified and is used to select the block device to be reopened.
|
||||||
# Other @node-name options must be either omitted or set to the
|
# Other @node-name options must be either omitted or set to the
|
||||||
# current name of the appropriate node. This command won't change any
|
# current name of the appropriate node. This command won't change any
|
||||||
@ -4247,8 +4249,8 @@
|
|||||||
#
|
#
|
||||||
# 4) NULL: the current child (if any) is detached.
|
# 4) NULL: the current child (if any) is detached.
|
||||||
#
|
#
|
||||||
# Options (1) and (2) are supported in all cases, but at the moment
|
# Options (1) and (2) are supported in all cases. Option (3) is
|
||||||
# only @backing allows replacing or detaching an existing child.
|
# supported for @file and @backing, and option (4) for @backing only.
|
||||||
#
|
#
|
||||||
# Unlike with blockdev-add, the @backing option must always be present
|
# Unlike with blockdev-add, the @backing option must always be present
|
||||||
# unless the node being reopened does not have a backing file and its
|
# unless the node being reopened does not have a backing file and its
|
||||||
@ -4258,7 +4260,7 @@
|
|||||||
# Since: 4.0
|
# Since: 4.0
|
||||||
##
|
##
|
||||||
{ 'command': 'x-blockdev-reopen',
|
{ 'command': 'x-blockdev-reopen',
|
||||||
'data': 'BlockdevOptions', 'boxed': true }
|
'data': { 'options': ['BlockdevOptions'] } }
|
||||||
|
|
||||||
##
|
##
|
||||||
# @blockdev-del:
|
# @blockdev-del:
|
||||||
|
@ -261,9 +261,12 @@ class TestBlockdevMirrorReopen(MirrorBaseClass):
|
|||||||
result = self.vm.qmp('blockdev-add', node_name="backing",
|
result = self.vm.qmp('blockdev-add', node_name="backing",
|
||||||
driver="null-co")
|
driver="null-co")
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
result = self.vm.qmp('x-blockdev-reopen', node_name="target",
|
result = self.vm.qmp('x-blockdev-reopen', options=[{
|
||||||
driver=iotests.imgfmt, file="target-file",
|
'node-name': "target",
|
||||||
backing="backing")
|
'driver': iotests.imgfmt,
|
||||||
|
'file': "target-file",
|
||||||
|
'backing': "backing"
|
||||||
|
}])
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
class TestBlockdevMirrorReopenIothread(TestBlockdevMirrorReopen):
|
class TestBlockdevMirrorReopenIothread(TestBlockdevMirrorReopen):
|
||||||
|
@ -137,7 +137,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
|
|||||||
assert sha256_1 == self.getSha256()
|
assert sha256_1 == self.getSha256()
|
||||||
|
|
||||||
# Reopen to RW
|
# Reopen to RW
|
||||||
result = self.vm.qmp('x-blockdev-reopen', **{
|
result = self.vm.qmp('x-blockdev-reopen', options=[{
|
||||||
'node-name': 'node0',
|
'node-name': 'node0',
|
||||||
'driver': iotests.imgfmt,
|
'driver': iotests.imgfmt,
|
||||||
'file': {
|
'file': {
|
||||||
@ -145,7 +145,7 @@ class TestPersistentDirtyBitmap(iotests.QMPTestCase):
|
|||||||
'filename': disk
|
'filename': disk
|
||||||
},
|
},
|
||||||
'read-only': False
|
'read-only': False
|
||||||
})
|
}])
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
# Check that bitmap is reopened to RW and we can write to it.
|
# Check that bitmap is reopened to RW and we can write to it.
|
||||||
|
@ -85,8 +85,18 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
"Expected output of %d qemu-io commands, found %d" %
|
"Expected output of %d qemu-io commands, found %d" %
|
||||||
(found, self.total_io_cmds))
|
(found, self.total_io_cmds))
|
||||||
|
|
||||||
# Run x-blockdev-reopen with 'opts' but applying 'newopts'
|
# Run x-blockdev-reopen on a list of block devices
|
||||||
# on top of it. The original 'opts' dict is unmodified
|
def reopenMultiple(self, opts, errmsg = None):
|
||||||
|
result = self.vm.qmp('x-blockdev-reopen', conv_keys=False, options=opts)
|
||||||
|
if errmsg:
|
||||||
|
self.assert_qmp(result, 'error/class', 'GenericError')
|
||||||
|
self.assert_qmp(result, 'error/desc', errmsg)
|
||||||
|
else:
|
||||||
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
# Run x-blockdev-reopen on a single block device (specified by
|
||||||
|
# 'opts') but applying 'newopts' on top of it. The original 'opts'
|
||||||
|
# dict is unmodified
|
||||||
def reopen(self, opts, newopts = {}, errmsg = None):
|
def reopen(self, opts, newopts = {}, errmsg = None):
|
||||||
opts = copy.deepcopy(opts)
|
opts = copy.deepcopy(opts)
|
||||||
|
|
||||||
@ -101,12 +111,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
subdict = opts[prefix]
|
subdict = opts[prefix]
|
||||||
subdict[key] = value
|
subdict[key] = value
|
||||||
|
|
||||||
result = self.vm.qmp('x-blockdev-reopen', conv_keys = False, **opts)
|
self.reopenMultiple([ opts ], errmsg)
|
||||||
if errmsg:
|
|
||||||
self.assert_qmp(result, 'error/class', 'GenericError')
|
|
||||||
self.assert_qmp(result, 'error/desc', errmsg)
|
|
||||||
else:
|
|
||||||
self.assert_qmp(result, 'return', {})
|
|
||||||
|
|
||||||
|
|
||||||
# Run query-named-block-nodes and return the specified entry
|
# Run query-named-block-nodes and return the specified entry
|
||||||
@ -142,10 +147,10 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
# We cannot change any of these
|
# We cannot change any of these
|
||||||
self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'")
|
self.reopen(opts, {'node-name': 'not-found'}, "Failed to find node with node-name='not-found'")
|
||||||
self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''")
|
self.reopen(opts, {'node-name': ''}, "Failed to find node with node-name=''")
|
||||||
self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'node-name', expected: string")
|
self.reopen(opts, {'node-name': None}, "Invalid parameter type for 'options[0].node-name', expected: string")
|
||||||
self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'")
|
self.reopen(opts, {'driver': 'raw'}, "Cannot change the option 'driver'")
|
||||||
self.reopen(opts, {'driver': ''}, "Invalid parameter ''")
|
self.reopen(opts, {'driver': ''}, "Invalid parameter ''")
|
||||||
self.reopen(opts, {'driver': None}, "Invalid parameter type for 'driver', expected: string")
|
self.reopen(opts, {'driver': None}, "Invalid parameter type for 'options[0].driver', expected: string")
|
||||||
self.reopen(opts, {'file': 'not-found'}, "Cannot find device='' nor node-name='not-found'")
|
self.reopen(opts, {'file': 'not-found'}, "Cannot find device='' nor node-name='not-found'")
|
||||||
self.reopen(opts, {'file': ''}, "Cannot find device='' nor node-name=''")
|
self.reopen(opts, {'file': ''}, "Cannot find device='' nor node-name=''")
|
||||||
self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef")
|
self.reopen(opts, {'file': None}, "Invalid parameter type for 'file', expected: BlockdevRef")
|
||||||
@ -154,7 +159,7 @@ class TestBlockdevReopen(iotests.QMPTestCase):
|
|||||||
self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'")
|
self.reopen(opts, {'file.filename': hd_path[1]}, "Cannot change the option 'filename'")
|
||||||
self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'")
|
self.reopen(opts, {'file.aio': 'native'}, "Cannot change the option 'aio'")
|
||||||
self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'")
|
self.reopen(opts, {'file.locking': 'off'}, "Cannot change the option 'locking'")
|
||||||
self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'file.filename', expected: string")
|
self.reopen(opts, {'file.filename': None}, "Invalid parameter type for 'options[0].file.filename', expected: string")
|
||||||
|
|
||||||
# node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it
|
# node-name is optional in BlockdevOptions, but x-blockdev-reopen needs it
|
||||||
del opts['node-name']
|
del opts['node-name']
|
||||||
|
@ -63,7 +63,7 @@ vm.get_qmp_events()
|
|||||||
|
|
||||||
del blockdev_opts['file']['size']
|
del blockdev_opts['file']['size']
|
||||||
vm.qmp_log('x-blockdev-reopen', filters=[filter_qmp_testfiles],
|
vm.qmp_log('x-blockdev-reopen', filters=[filter_qmp_testfiles],
|
||||||
**blockdev_opts)
|
options = [ blockdev_opts ])
|
||||||
|
|
||||||
vm.qmp_log('block-job-resume', device='drive0')
|
vm.qmp_log('block-job-resume', device='drive0')
|
||||||
vm.event_wait('JOB_STATUS_CHANGE', timeout=1.0,
|
vm.event_wait('JOB_STATUS_CHANGE', timeout=1.0,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{"return": {}}
|
{"return": {}}
|
||||||
{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "on-target-error": "enospc", "sync": "full", "target": "target"}}
|
{"execute": "blockdev-mirror", "arguments": {"device": "drive0", "on-target-error": "enospc", "sync": "full", "target": "target"}}
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
{"execute": "x-blockdev-reopen", "arguments": {"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}}
|
{"execute": "x-blockdev-reopen", "arguments": {"options": [{"driver": "qcow2", "file": {"driver": "raw", "file": {"driver": "file", "filename": "TEST_DIR/PID-target"}}, "node-name": "target"}]}}
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
{"execute": "block-job-resume", "arguments": {"device": "drive0"}}
|
{"execute": "block-job-resume", "arguments": {"device": "drive0"}}
|
||||||
{"return": {}}
|
{"return": {}}
|
||||||
|
@ -120,8 +120,7 @@ class EncryptionSetupTestCase(iotests.QMPTestCase):
|
|||||||
|
|
||||||
command = 'x-blockdev-reopen' if reOpen else 'blockdev-add'
|
command = 'x-blockdev-reopen' if reOpen else 'blockdev-add'
|
||||||
|
|
||||||
result = vm.qmp(command, **
|
opts = {
|
||||||
{
|
|
||||||
'driver': iotests.imgfmt,
|
'driver': iotests.imgfmt,
|
||||||
'node-name': id,
|
'node-name': id,
|
||||||
'read-only': readOnly,
|
'read-only': readOnly,
|
||||||
@ -131,7 +130,11 @@ class EncryptionSetupTestCase(iotests.QMPTestCase):
|
|||||||
'filename': test_img,
|
'filename': test_img,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
if reOpen:
|
||||||
|
result = vm.qmp(command, options=[opts])
|
||||||
|
else:
|
||||||
|
result = vm.qmp(command, **opts)
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ class TestPreallocateFilter(TestPreallocateBase):
|
|||||||
self.check_big()
|
self.check_big()
|
||||||
|
|
||||||
def test_reopen_opts(self):
|
def test_reopen_opts(self):
|
||||||
result = self.vm.qmp('x-blockdev-reopen', **{
|
result = self.vm.qmp('x-blockdev-reopen', options=[{
|
||||||
'node-name': 'disk',
|
'node-name': 'disk',
|
||||||
'driver': iotests.imgfmt,
|
'driver': iotests.imgfmt,
|
||||||
'file': {
|
'file': {
|
||||||
@ -112,7 +112,7 @@ class TestPreallocateFilter(TestPreallocateBase):
|
|||||||
'filename': disk
|
'filename': disk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}])
|
||||||
self.assert_qmp(result, 'return', {})
|
self.assert_qmp(result, 'return', {})
|
||||||
|
|
||||||
self.vm.hmp_qemu_io('drive0', 'write 0 1M')
|
self.vm.hmp_qemu_io('drive0', 'write 0 1M')
|
||||||
|
@ -41,13 +41,15 @@ log('Trying to remove persistent bitmap from r-o base node, should fail:')
|
|||||||
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
||||||
|
|
||||||
new_base_opts = {
|
new_base_opts = {
|
||||||
'node-name': 'base',
|
'options': [{
|
||||||
'driver': 'qcow2',
|
'node-name': 'base',
|
||||||
'file': {
|
'driver': 'qcow2',
|
||||||
'driver': 'file',
|
'file': {
|
||||||
'filename': base
|
'driver': 'file',
|
||||||
},
|
'filename': base
|
||||||
'read-only': False
|
},
|
||||||
|
'read-only': False
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Don't want to bother with filtering qmp_log for reopen command
|
# Don't want to bother with filtering qmp_log for reopen command
|
||||||
@ -58,7 +60,7 @@ if result != {'return': {}}:
|
|||||||
log('Remove persistent bitmap from base node reopened to RW:')
|
log('Remove persistent bitmap from base node reopened to RW:')
|
||||||
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
vm.qmp_log('block-dirty-bitmap-remove', node='base', name='bitmap0')
|
||||||
|
|
||||||
new_base_opts['read-only'] = True
|
new_base_opts['options'][0]['read-only'] = True
|
||||||
result = vm.qmp('x-blockdev-reopen', **new_base_opts)
|
result = vm.qmp('x-blockdev-reopen', **new_base_opts)
|
||||||
if result != {'return': {}}:
|
if result != {'return': {}}:
|
||||||
log('Failed to reopen: ' + str(result))
|
log('Failed to reopen: ' + str(result))
|
||||||
|
Loading…
Reference in New Issue
Block a user