nbd patches for 2021-02-12

- let qemu-nbd handle larger backlog of connecting clients
 - fix a few NBD-related iotest failures
 - add block cancellation hook for faster response to NBD failures
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAmAm3cQACgkQp6FrSiUn
 Q2rjIggAmDt3oPxm0lvaMWH8NeQ2FO/sVxWbIbXzJfLn189Fyq3tQdM4x3fO64kZ
 7tCFqPwgfU3S8opYou+Rs+IhzktJ8VuMUasl1g//YVjyTQkUt2Ppx6cAVAqnvE36
 1+5G3On43afFkEcC+oQSeoGuecSMaTdSbjNBu3IGUoK0US3QDO0j4/ywGkVLxw8z
 39GeeQgDrViL8OH5MtczuGPvxeCYsqYAe6zrlKsh+ke8/2Q2WUAOfPZpvfSNJ+Qp
 pXHcwNlZZEVVIpmwmc3jYCfQvGkw//cS8tpZzDx7fB746q1VWGtcWF28SvZfW+PN
 PxrYF/1IO+ho/tX7fsIJE2fbd9QwPQ==
 =bDgk
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2021-02-12' into staging

nbd patches for 2021-02-12

- let qemu-nbd handle larger backlog of connecting clients
- fix a few NBD-related iotest failures
- add block cancellation hook for faster response to NBD failures

# gpg: Signature made Fri 12 Feb 2021 19:57:56 GMT
# gpg:                using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A
# gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full]
# gpg:                 aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full]
# gpg:                 aka "[jpeg image of size 6874]" [full]
# Primary key fingerprint: 71C2 CC22 B1C4 6029 27D2  F3AA A7A1 6B4A 2527 436A

* remotes/ericb/tags/pull-nbd-2021-02-12:
  iotests/264: add backup-cancel test-case
  block/backup: implement .cancel job handler
  iotests/264: add mirror-cancel test-case
  iotests.py: qemu_nbd_popen: remove pid file after use
  iotests/264: move to python unittest
  block/mirror: implement .cancel job handler
  job: add .cancel handler for the driver
  block/raw-format: implement .bdrv_cancel_in_flight handler
  block/nbd: implement .bdrv_cancel_in_flight
  block: add new BlockDriver handler: bdrv_cancel_in_flight
  io: error_prepend() in qio_channel_readv_full_all() causes segfault
  iotests/210: Fix reference output
  qemu-nbd: Permit --shared=0 for unlimited clients
  qemu-nbd: Use SOMAXCONN for socket listen() backlog

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2021-02-12 22:27:47 +00:00
commit abb8b29aff
17 changed files with 185 additions and 66 deletions

View file

@ -35,6 +35,7 @@ typedef struct BackupBlockJob {
BlockJob common;
BlockDriverState *backup_top;
BlockDriverState *source_bs;
BlockDriverState *target_bs;
BdrvDirtyBitmap *sync_bitmap;
@ -329,6 +330,13 @@ static void coroutine_fn backup_set_speed(BlockJob *job, int64_t speed)
}
}
static void backup_cancel(Job *job)
{
BackupBlockJob *s = container_of(job, BackupBlockJob, common.job);
bdrv_cancel_in_flight(s->target_bs);
}
static const BlockJobDriver backup_job_driver = {
.job_driver = {
.instance_size = sizeof(BackupBlockJob),
@ -340,6 +348,7 @@ static const BlockJobDriver backup_job_driver = {
.abort = backup_abort,
.clean = backup_clean,
.pause = backup_pause,
.cancel = backup_cancel,
},
.set_speed = backup_set_speed,
};
@ -528,6 +537,7 @@ BlockJob *backup_job_create(const char *job_id, BlockDriverState *bs,
job->backup_top = backup_top;
job->source_bs = bs;
job->target_bs = target;
job->on_source_error = on_source_error;
job->on_target_error = on_target_error;
job->sync_mode = sync_mode;

View file

@ -3460,3 +3460,14 @@ out:
return ret;
}
void bdrv_cancel_in_flight(BlockDriverState *bs)
{
if (!bs || !bs->drv) {
return;
}
if (bs->drv->bdrv_cancel_in_flight) {
bs->drv->bdrv_cancel_in_flight(bs);
}
}

View file

@ -1179,6 +1179,14 @@ static bool mirror_drained_poll(BlockJob *job)
return !!s->in_flight;
}
static void mirror_cancel(Job *job)
{
MirrorBlockJob *s = container_of(job, MirrorBlockJob, common.job);
BlockDriverState *target = blk_bs(s->target);
bdrv_cancel_in_flight(target);
}
static const BlockJobDriver mirror_job_driver = {
.job_driver = {
.instance_size = sizeof(MirrorBlockJob),
@ -1190,6 +1198,7 @@ static const BlockJobDriver mirror_job_driver = {
.abort = mirror_abort,
.pause = mirror_pause,
.complete = mirror_complete,
.cancel = mirror_cancel,
},
.drained_poll = mirror_drained_poll,
};

View file

@ -2458,6 +2458,18 @@ static const char *const nbd_strong_runtime_opts[] = {
NULL
};
static void nbd_cancel_in_flight(BlockDriverState *bs)
{
BDRVNBDState *s = (BDRVNBDState *)bs->opaque;
reconnect_delay_timer_del(s);
if (s->state == NBD_CLIENT_CONNECTING_WAIT) {
s->state = NBD_CLIENT_CONNECTING_NOWAIT;
qemu_co_queue_restart_all(&s->free_sema);
}
}
static BlockDriver bdrv_nbd = {
.format_name = "nbd",
.protocol_name = "nbd",
@ -2484,6 +2496,7 @@ static BlockDriver bdrv_nbd = {
.bdrv_co_block_status = nbd_client_co_block_status,
.bdrv_dirname = nbd_dirname,
.strong_runtime_opts = nbd_strong_runtime_opts,
.bdrv_cancel_in_flight = nbd_cancel_in_flight,
};
static BlockDriver bdrv_nbd_tcp = {
@ -2512,6 +2525,7 @@ static BlockDriver bdrv_nbd_tcp = {
.bdrv_co_block_status = nbd_client_co_block_status,
.bdrv_dirname = nbd_dirname,
.strong_runtime_opts = nbd_strong_runtime_opts,
.bdrv_cancel_in_flight = nbd_cancel_in_flight,
};
static BlockDriver bdrv_nbd_unix = {
@ -2540,6 +2554,7 @@ static BlockDriver bdrv_nbd_unix = {
.bdrv_co_block_status = nbd_client_co_block_status,
.bdrv_dirname = nbd_dirname,
.strong_runtime_opts = nbd_strong_runtime_opts,
.bdrv_cancel_in_flight = nbd_cancel_in_flight,
};
static void bdrv_nbd_init(void)

View file

@ -575,6 +575,11 @@ static const char *const raw_strong_runtime_opts[] = {
NULL
};
static void raw_cancel_in_flight(BlockDriverState *bs)
{
bdrv_cancel_in_flight(bs->file->bs);
}
BlockDriver bdrv_raw = {
.format_name = "raw",
.instance_size = sizeof(BDRVRawState),
@ -608,6 +613,7 @@ BlockDriver bdrv_raw = {
.bdrv_has_zero_init = &raw_has_zero_init,
.strong_runtime_opts = raw_strong_runtime_opts,
.mutable_opts = mutable_opts,
.bdrv_cancel_in_flight = raw_cancel_in_flight,
};
static void bdrv_raw_init(void)

View file

@ -134,7 +134,12 @@ void nbd_server_start(SocketAddress *addr, const char *tls_creds,
qio_net_listener_set_name(nbd_server->listener,
"nbd-listener");
if (qio_net_listener_open_sync(nbd_server->listener, addr, 1, errp) < 0) {
/*
* Because this server is persistent, a backlog of SOMAXCONN is
* better than trying to size it to max_connections.
*/
if (qio_net_listener_open_sync(nbd_server->listener, addr, SOMAXCONN,
errp) < 0) {
goto error;
}

View file

@ -136,8 +136,8 @@ driver options if ``--image-opts`` is specified.
.. option:: -e, --shared=NUM
Allow up to *NUM* clients to share the device (default
``1``). Safe for readers, but for now, consistency is not
guaranteed between multiple writers.
``1``), 0 for unlimited. Safe for readers, but for now,
consistency is not guaranteed between multiple writers.
.. option:: -t, --persistent

View file

@ -849,4 +849,7 @@ int coroutine_fn bdrv_co_copy_range(BdrvChild *src, int64_t src_offset,
BdrvChild *dst, int64_t dst_offset,
int64_t bytes, BdrvRequestFlags read_flags,
BdrvRequestFlags write_flags);
void bdrv_cancel_in_flight(BlockDriverState *bs);
#endif

View file

@ -352,6 +352,15 @@ struct BlockDriver {
bool want_zero, int64_t offset, int64_t bytes, int64_t *pnum,
int64_t *map, BlockDriverState **file);
/*
* This informs the driver that we are no longer interested in the result
* of in-flight requests, so don't waste the time if possible.
*
* One example usage is to avoid waiting for an nbd target node reconnect
* timeout during job-cancel.
*/
void (*bdrv_cancel_in_flight)(BlockDriverState *bs);
/*
* Invalidate any cached meta-data.
*/

View file

@ -251,6 +251,11 @@ struct JobDriver {
*/
void (*clean)(Job *job);
/**
* If the callback is not NULL, it will be invoked in job_cancel_async
*/
void (*cancel)(Job *job);
/** Called when the job is freed */
void (*free)(Job *job);

View file

@ -202,8 +202,7 @@ int qio_channel_readv_full_all(QIOChannel *ioc,
int ret = qio_channel_readv_full_all_eof(ioc, iov, niov, fds, nfds, errp);
if (ret == 0) {
error_prepend(errp,
"Unexpected end-of-file before all data were read.");
error_setg(errp, "Unexpected end-of-file before all data were read");
return -1;
}
if (ret == 1) {

3
job.c
View file

@ -715,6 +715,9 @@ static int job_finalize_single(Job *job)
static void job_cancel_async(Job *job, bool force)
{
if (job->driver->cancel) {
job->driver->cancel(job);
}
if (job->user_paused) {
/* Do not call job_enter here, the caller will handle it. */
if (job->driver->user_resume) {

View file

@ -328,7 +328,7 @@ static void *nbd_client_thread(void *arg)
static int nbd_can_accept(void)
{
return state == RUNNING && nb_fds < shared;
return state == RUNNING && (shared == 0 || nb_fds < shared);
}
static void nbd_update_server_watch(void);
@ -707,7 +707,7 @@ int main(int argc, char **argv)
break;
case 'e':
if (qemu_strtoi(optarg, NULL, 0, &shared) < 0 ||
shared < 1) {
shared < 0) {
error_report("Invalid shared device number '%s'", optarg);
exit(EXIT_FAILURE);
}
@ -964,8 +964,16 @@ int main(int argc, char **argv)
server = qio_net_listener_new();
if (socket_activation == 0) {
int backlog;
if (persistent || shared == 0) {
backlog = SOMAXCONN;
} else {
backlog = MIN(shared, SOMAXCONN);
}
saddr = nbd_build_socket_address(sockpath, bindto, port);
if (qio_net_listener_open_sync(server, saddr, 1, &local_err) < 0) {
if (qio_net_listener_open_sync(server, saddr, backlog,
&local_err) < 0) {
object_unref(OBJECT(server));
error_report_err(local_err);
exit(EXIT_FAILURE);

View file

@ -182,7 +182,7 @@ Job failed: The requested file size is too large
=== Resize image with invalid sizes ===
{"execute": "block_resize", "arguments": {"node-name": "node1", "size": 9223372036854775296}}
{"error": {"class": "GenericError", "desc": "Required too big image size, it must be not greater than 9223372035781033984"}}
{"error": {"class": "GenericError", "desc": "offset(9223372036854775296) exceeds maximum(9223372035781033984)"}}
{"execute": "block_resize", "arguments": {"node-name": "node1", "size": 9223372036854775808}}
{"error": {"class": "GenericError", "desc": "Invalid parameter type for 'size', expected: integer"}}
{"execute": "block_resize", "arguments": {"node-name": "node1", "size": 18446744073709551104}}

View file

@ -20,60 +20,102 @@
#
import time
import os
import iotests
from iotests import qemu_img_create, file_path, qemu_nbd_popen, log
iotests.script_initialize(
supported_fmts=['qcow2'],
)
from iotests import qemu_img_create, file_path, qemu_nbd_popen
disk_a, disk_b, nbd_sock = file_path('disk_a', 'disk_b', 'nbd-sock')
nbd_uri = 'nbd+unix:///?socket=' + nbd_sock
size = 5 * 1024 * 1024
wait_limit = 3.0
wait_step = 0.2
qemu_img_create('-f', iotests.imgfmt, disk_a, str(size))
qemu_img_create('-f', iotests.imgfmt, disk_b, str(size))
with qemu_nbd_popen('-k', nbd_sock, '-f', iotests.imgfmt, disk_b):
vm = iotests.VM().add_drive(disk_a)
vm.launch()
vm.hmp_qemu_io('drive0', 'write 0 {}'.format(size))
class TestNbdReconnect(iotests.QMPTestCase):
def init_vm(self, disk_size):
qemu_img_create('-f', iotests.imgfmt, disk_a, str(disk_size))
qemu_img_create('-f', iotests.imgfmt, disk_b, str(disk_size))
self.vm = iotests.VM().add_drive(disk_a)
self.vm.launch()
self.vm.hmp_qemu_io('drive0', 'write 0 {}'.format(disk_size))
vm.qmp_log('blockdev-add', filters=[iotests.filter_qmp_testfiles],
**{'node_name': 'backup0',
'driver': 'raw',
'file': {'driver': 'nbd',
'server': {'type': 'unix', 'path': nbd_sock},
'reconnect-delay': 10}})
vm.qmp_log('blockdev-backup', device='drive0', sync='full',
target='backup0', speed=(1 * 1024 * 1024))
def tearDown(self):
self.vm.shutdown()
os.remove(disk_a)
os.remove(disk_b)
# Wait for some progress
t = 0.0
while t < wait_limit:
jobs = vm.qmp('query-block-jobs')['return']
if jobs and jobs[0]['offset'] > 0:
break
time.sleep(wait_step)
t += wait_step
def start_job(self, job):
"""Stat job with nbd target and kill the server"""
assert job in ('blockdev-backup', 'blockdev-mirror')
with qemu_nbd_popen('-k', nbd_sock, '-f', iotests.imgfmt, disk_b):
result = self.vm.qmp('blockdev-add',
**{'node_name': 'backup0',
'driver': 'raw',
'file': {'driver': 'nbd',
'server': {'type': 'unix',
'path': nbd_sock},
'reconnect-delay': 10}})
self.assert_qmp(result, 'return', {})
result = self.vm.qmp(job, device='drive0',
sync='full', target='backup0',
speed=(1 * 1024 * 1024))
self.assert_qmp(result, 'return', {})
if jobs and jobs[0]['offset'] > 0:
log('Backup job is started')
# Wait for some progress
t = 0.0
while t < wait_limit:
jobs = self.vm.qmp('query-block-jobs')['return']
if jobs and jobs[0]['offset'] > 0:
break
time.sleep(wait_step)
t += wait_step
jobs = vm.qmp('query-block-jobs')['return']
if jobs and jobs[0]['offset'] < jobs[0]['len']:
log('Backup job is still in progress')
self.assertTrue(jobs and jobs[0]['offset'] > 0) # job started
vm.qmp_log('block-job-set-speed', device='drive0', speed=0)
jobs = self.vm.qmp('query-block-jobs')['return']
# Check that job is still in progress
self.assertTrue(jobs)
self.assertTrue(jobs[0]['offset'] < jobs[0]['len'])
# Emulate server down time for 1 second
time.sleep(1)
result = self.vm.qmp('block-job-set-speed', device='drive0', speed=0)
self.assert_qmp(result, 'return', {})
with qemu_nbd_popen('-k', nbd_sock, '-f', iotests.imgfmt, disk_b):
e = vm.event_wait('BLOCK_JOB_COMPLETED')
log('Backup completed: {}'.format(e['data']['offset']))
vm.qmp_log('blockdev-del', node_name='backup0')
vm.shutdown()
# Emulate server down time for 1 second
time.sleep(1)
def test_backup(self):
size = 5 * 1024 * 1024
self.init_vm(size)
self.start_job('blockdev-backup')
with qemu_nbd_popen('-k', nbd_sock, '-f', iotests.imgfmt, disk_b):
e = self.vm.event_wait('BLOCK_JOB_COMPLETED')
self.assertEqual(e['data']['offset'], size)
result = self.vm.qmp('blockdev-del', node_name='backup0')
self.assert_qmp(result, 'return', {})
def cancel_job(self):
result = self.vm.qmp('block-job-cancel', device='drive0')
self.assert_qmp(result, 'return', {})
start_t = time.time()
self.vm.event_wait('BLOCK_JOB_CANCELLED')
delta_t = time.time() - start_t
self.assertTrue(delta_t < 2.0)
def test_mirror_cancel(self):
# Mirror speed limit doesn't work well enough, it seems that mirror
# will run many parallel requests anyway. MAX_IN_FLIGHT is 16 and
# MAX_IO_BYTES is 1M in mirror.c, so let's use 20M disk.
self.init_vm(20 * 1024 * 1024)
self.start_job('blockdev-mirror')
self.cancel_job()
def test_backup_cancel(self):
self.init_vm(5 * 1024 * 1024)
self.start_job('blockdev-backup')
self.cancel_job()
if __name__ == '__main__':
iotests.main(supported_fmts=['qcow2'])

View file

@ -1,15 +1,5 @@
Start NBD server
{"execute": "blockdev-add", "arguments": {"driver": "raw", "file": {"driver": "nbd", "reconnect-delay": 10, "server": {"path": "TEST_DIR/PID-nbd-sock", "type": "unix"}}, "node-name": "backup0"}}
{"return": {}}
{"execute": "blockdev-backup", "arguments": {"device": "drive0", "speed": 1048576, "sync": "full", "target": "backup0"}}
{"return": {}}
Backup job is started
Kill NBD server
Backup job is still in progress
{"execute": "block-job-set-speed", "arguments": {"device": "drive0", "speed": 0}}
{"return": {}}
Start NBD server
Backup completed: 5242880
{"execute": "blockdev-del", "arguments": {"node-name": "backup0"}}
{"return": {}}
Kill NBD server
...
----------------------------------------------------------------------
Ran 3 tests
OK

View file

@ -296,7 +296,9 @@ def qemu_nbd_list_log(*args: str) -> str:
@contextmanager
def qemu_nbd_popen(*args):
'''Context manager running qemu-nbd within the context'''
pid_file = file_path("pid")
pid_file = file_path("qemu_nbd_popen-nbd-pid-file")
assert not os.path.exists(pid_file)
cmd = list(qemu_nbd_args)
cmd.extend(('--persistent', '--pid-file', pid_file))
@ -314,6 +316,8 @@ def qemu_nbd_popen(*args):
time.sleep(0.01)
yield
finally:
if os.path.exists(pid_file):
os.remove(pid_file)
log('Kill NBD server')
p.kill()
p.wait()