191e7af394
Add missing job synchronization in the unit tests, with explicit locks. We are deliberately using _locked functions wrapped by a guard instead of a normal call because the normal call will be removed in future, as the only usage is limited to the tests. In other words, if a function like job_pause() is/will be only used in tests to avoid: WITH_JOB_LOCK_GUARD(){ job_pause_locked(); } then it is not worth keeping job_pause(), and just use the guard. Note: at this stage, job_{lock/unlock} and job lock guard macros are *nop*. Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com> Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru> Reviewed-by: Kevin Wolf <kwolf@redhat.com> Message-Id: <20220926093214.506243-10-eesposit@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
909 lines
27 KiB
C
909 lines
27 KiB
C
/*
|
|
* Block tests for iothreads
|
|
*
|
|
* Copyright (c) 2018 Kevin Wolf <kwolf@redhat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "block/block.h"
|
|
#include "block/blockjob_int.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qmp/qdict.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "iothread.h"
|
|
|
|
static int coroutine_fn bdrv_test_co_preadv(BlockDriverState *bs,
|
|
int64_t offset, int64_t bytes,
|
|
QEMUIOVector *qiov,
|
|
BdrvRequestFlags flags)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int coroutine_fn bdrv_test_co_pwritev(BlockDriverState *bs,
|
|
int64_t offset, int64_t bytes,
|
|
QEMUIOVector *qiov,
|
|
BdrvRequestFlags flags)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int coroutine_fn bdrv_test_co_pdiscard(BlockDriverState *bs,
|
|
int64_t offset, int64_t bytes)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int coroutine_fn
|
|
bdrv_test_co_truncate(BlockDriverState *bs, int64_t offset, bool exact,
|
|
PreallocMode prealloc, BdrvRequestFlags flags,
|
|
Error **errp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int coroutine_fn bdrv_test_co_block_status(BlockDriverState *bs,
|
|
bool want_zero,
|
|
int64_t offset, int64_t count,
|
|
int64_t *pnum, int64_t *map,
|
|
BlockDriverState **file)
|
|
{
|
|
*pnum = count;
|
|
return 0;
|
|
}
|
|
|
|
static BlockDriver bdrv_test = {
|
|
.format_name = "test",
|
|
.instance_size = 1,
|
|
|
|
.bdrv_co_preadv = bdrv_test_co_preadv,
|
|
.bdrv_co_pwritev = bdrv_test_co_pwritev,
|
|
.bdrv_co_pdiscard = bdrv_test_co_pdiscard,
|
|
.bdrv_co_truncate = bdrv_test_co_truncate,
|
|
.bdrv_co_block_status = bdrv_test_co_block_status,
|
|
};
|
|
|
|
static void test_sync_op_pread(BdrvChild *c)
|
|
{
|
|
uint8_t buf[512];
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = bdrv_pread(c, 0, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = bdrv_pread(c, -2, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_pwrite(BdrvChild *c)
|
|
{
|
|
uint8_t buf[512] = { 0 };
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = bdrv_pwrite(c, 0, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = bdrv_pwrite(c, -2, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_pread(BlockBackend *blk)
|
|
{
|
|
uint8_t buf[512];
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = blk_pread(blk, 0, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_pread(blk, -2, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_pwrite(BlockBackend *blk)
|
|
{
|
|
uint8_t buf[512] = { 0 };
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = blk_pwrite(blk, 0, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_pwrite(blk, -2, sizeof(buf), buf, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_preadv(BlockBackend *blk)
|
|
{
|
|
uint8_t buf[512];
|
|
QEMUIOVector qiov = QEMU_IOVEC_INIT_BUF(qiov, buf, sizeof(buf));
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = blk_preadv(blk, 0, sizeof(buf), &qiov, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_preadv(blk, -2, sizeof(buf), &qiov, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_pwritev(BlockBackend *blk)
|
|
{
|
|
uint8_t buf[512] = { 0 };
|
|
QEMUIOVector qiov = QEMU_IOVEC_INIT_BUF(qiov, buf, sizeof(buf));
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = blk_pwritev(blk, 0, sizeof(buf), &qiov, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_pwritev(blk, -2, sizeof(buf), &qiov, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_preadv_part(BlockBackend *blk)
|
|
{
|
|
uint8_t buf[512];
|
|
QEMUIOVector qiov = QEMU_IOVEC_INIT_BUF(qiov, buf, sizeof(buf));
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = blk_preadv_part(blk, 0, sizeof(buf), &qiov, 0, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_preadv_part(blk, -2, sizeof(buf), &qiov, 0, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_pwritev_part(BlockBackend *blk)
|
|
{
|
|
uint8_t buf[512] = { 0 };
|
|
QEMUIOVector qiov = QEMU_IOVEC_INIT_BUF(qiov, buf, sizeof(buf));
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = blk_pwritev_part(blk, 0, sizeof(buf), &qiov, 0, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_pwritev_part(blk, -2, sizeof(buf), &qiov, 0, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_pwrite_compressed(BlockBackend *blk)
|
|
{
|
|
uint8_t buf[512] = { 0 };
|
|
int ret;
|
|
|
|
/* Late error: Not supported */
|
|
ret = blk_pwrite_compressed(blk, 0, sizeof(buf), buf);
|
|
g_assert_cmpint(ret, ==, -ENOTSUP);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_pwrite_compressed(blk, -2, sizeof(buf), buf);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_pwrite_zeroes(BlockBackend *blk)
|
|
{
|
|
int ret;
|
|
|
|
/* Success */
|
|
ret = blk_pwrite_zeroes(blk, 0, 512, 0);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_pwrite_zeroes(blk, -2, 512, 0);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_load_vmstate(BdrvChild *c)
|
|
{
|
|
uint8_t buf[512];
|
|
int ret;
|
|
|
|
/* Error: Driver does not support snapshots */
|
|
ret = bdrv_load_vmstate(c->bs, buf, 0, sizeof(buf));
|
|
g_assert_cmpint(ret, ==, -ENOTSUP);
|
|
}
|
|
|
|
static void test_sync_op_save_vmstate(BdrvChild *c)
|
|
{
|
|
uint8_t buf[512] = { 0 };
|
|
int ret;
|
|
|
|
/* Error: Driver does not support snapshots */
|
|
ret = bdrv_save_vmstate(c->bs, buf, 0, sizeof(buf));
|
|
g_assert_cmpint(ret, ==, -ENOTSUP);
|
|
}
|
|
|
|
static void test_sync_op_pdiscard(BdrvChild *c)
|
|
{
|
|
int ret;
|
|
|
|
/* Normal success path */
|
|
c->bs->open_flags |= BDRV_O_UNMAP;
|
|
ret = bdrv_pdiscard(c, 0, 512);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early success: UNMAP not supported */
|
|
c->bs->open_flags &= ~BDRV_O_UNMAP;
|
|
ret = bdrv_pdiscard(c, 0, 512);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = bdrv_pdiscard(c, -2, 512);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_blk_pdiscard(BlockBackend *blk)
|
|
{
|
|
int ret;
|
|
|
|
/* Early success: UNMAP not supported */
|
|
ret = blk_pdiscard(blk, 0, 512);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_pdiscard(blk, -2, 512);
|
|
g_assert_cmpint(ret, ==, -EIO);
|
|
}
|
|
|
|
static void test_sync_op_truncate(BdrvChild *c)
|
|
{
|
|
int ret;
|
|
|
|
/* Normal success path */
|
|
ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, 0, NULL);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = bdrv_truncate(c, -2, false, PREALLOC_MODE_OFF, 0, NULL);
|
|
g_assert_cmpint(ret, ==, -EINVAL);
|
|
|
|
/* Error: Read-only image */
|
|
c->bs->open_flags &= ~BDRV_O_RDWR;
|
|
|
|
ret = bdrv_truncate(c, 65536, false, PREALLOC_MODE_OFF, 0, NULL);
|
|
g_assert_cmpint(ret, ==, -EACCES);
|
|
|
|
c->bs->open_flags |= BDRV_O_RDWR;
|
|
}
|
|
|
|
static void test_sync_op_blk_truncate(BlockBackend *blk)
|
|
{
|
|
int ret;
|
|
|
|
/* Normal success path */
|
|
ret = blk_truncate(blk, 65536, false, PREALLOC_MODE_OFF, 0, NULL);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early error: Negative offset */
|
|
ret = blk_truncate(blk, -2, false, PREALLOC_MODE_OFF, 0, NULL);
|
|
g_assert_cmpint(ret, ==, -EINVAL);
|
|
}
|
|
|
|
static void test_sync_op_block_status(BdrvChild *c)
|
|
{
|
|
int ret;
|
|
int64_t n;
|
|
|
|
/* Normal success path */
|
|
ret = bdrv_is_allocated(c->bs, 0, 65536, &n);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early success: No driver support */
|
|
bdrv_test.bdrv_co_block_status = NULL;
|
|
ret = bdrv_is_allocated(c->bs, 0, 65536, &n);
|
|
g_assert_cmpint(ret, ==, 1);
|
|
|
|
/* Early success: bytes = 0 */
|
|
ret = bdrv_is_allocated(c->bs, 0, 0, &n);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early success: Offset > image size*/
|
|
ret = bdrv_is_allocated(c->bs, 0x1000000, 0x1000000, &n);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
}
|
|
|
|
static void test_sync_op_flush(BdrvChild *c)
|
|
{
|
|
int ret;
|
|
|
|
/* Normal success path */
|
|
ret = bdrv_flush(c->bs);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early success: Read-only image */
|
|
c->bs->open_flags &= ~BDRV_O_RDWR;
|
|
|
|
ret = bdrv_flush(c->bs);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
c->bs->open_flags |= BDRV_O_RDWR;
|
|
}
|
|
|
|
static void test_sync_op_blk_flush(BlockBackend *blk)
|
|
{
|
|
BlockDriverState *bs = blk_bs(blk);
|
|
int ret;
|
|
|
|
/* Normal success path */
|
|
ret = blk_flush(blk);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
/* Early success: Read-only image */
|
|
bs->open_flags &= ~BDRV_O_RDWR;
|
|
|
|
ret = blk_flush(blk);
|
|
g_assert_cmpint(ret, ==, 0);
|
|
|
|
bs->open_flags |= BDRV_O_RDWR;
|
|
}
|
|
|
|
static void test_sync_op_check(BdrvChild *c)
|
|
{
|
|
BdrvCheckResult result;
|
|
int ret;
|
|
|
|
/* Error: Driver does not implement check */
|
|
ret = bdrv_check(c->bs, &result, 0);
|
|
g_assert_cmpint(ret, ==, -ENOTSUP);
|
|
}
|
|
|
|
static void test_sync_op_activate(BdrvChild *c)
|
|
{
|
|
/* Early success: Image is not inactive */
|
|
bdrv_activate(c->bs, NULL);
|
|
}
|
|
|
|
|
|
typedef struct SyncOpTest {
|
|
const char *name;
|
|
void (*fn)(BdrvChild *c);
|
|
void (*blkfn)(BlockBackend *blk);
|
|
} SyncOpTest;
|
|
|
|
const SyncOpTest sync_op_tests[] = {
|
|
{
|
|
.name = "/sync-op/pread",
|
|
.fn = test_sync_op_pread,
|
|
.blkfn = test_sync_op_blk_pread,
|
|
}, {
|
|
.name = "/sync-op/pwrite",
|
|
.fn = test_sync_op_pwrite,
|
|
.blkfn = test_sync_op_blk_pwrite,
|
|
}, {
|
|
.name = "/sync-op/preadv",
|
|
.fn = NULL,
|
|
.blkfn = test_sync_op_blk_preadv,
|
|
}, {
|
|
.name = "/sync-op/pwritev",
|
|
.fn = NULL,
|
|
.blkfn = test_sync_op_blk_pwritev,
|
|
}, {
|
|
.name = "/sync-op/preadv_part",
|
|
.fn = NULL,
|
|
.blkfn = test_sync_op_blk_preadv_part,
|
|
}, {
|
|
.name = "/sync-op/pwritev_part",
|
|
.fn = NULL,
|
|
.blkfn = test_sync_op_blk_pwritev_part,
|
|
}, {
|
|
.name = "/sync-op/pwrite_compressed",
|
|
.fn = NULL,
|
|
.blkfn = test_sync_op_blk_pwrite_compressed,
|
|
}, {
|
|
.name = "/sync-op/pwrite_zeroes",
|
|
.fn = NULL,
|
|
.blkfn = test_sync_op_blk_pwrite_zeroes,
|
|
}, {
|
|
.name = "/sync-op/load_vmstate",
|
|
.fn = test_sync_op_load_vmstate,
|
|
}, {
|
|
.name = "/sync-op/save_vmstate",
|
|
.fn = test_sync_op_save_vmstate,
|
|
}, {
|
|
.name = "/sync-op/pdiscard",
|
|
.fn = test_sync_op_pdiscard,
|
|
.blkfn = test_sync_op_blk_pdiscard,
|
|
}, {
|
|
.name = "/sync-op/truncate",
|
|
.fn = test_sync_op_truncate,
|
|
.blkfn = test_sync_op_blk_truncate,
|
|
}, {
|
|
.name = "/sync-op/block_status",
|
|
.fn = test_sync_op_block_status,
|
|
}, {
|
|
.name = "/sync-op/flush",
|
|
.fn = test_sync_op_flush,
|
|
.blkfn = test_sync_op_blk_flush,
|
|
}, {
|
|
.name = "/sync-op/check",
|
|
.fn = test_sync_op_check,
|
|
}, {
|
|
.name = "/sync-op/activate",
|
|
.fn = test_sync_op_activate,
|
|
},
|
|
};
|
|
|
|
/* Test synchronous operations that run in a different iothread, so we have to
|
|
* poll for the coroutine there to return. */
|
|
static void test_sync_op(const void *opaque)
|
|
{
|
|
const SyncOpTest *t = opaque;
|
|
IOThread *iothread = iothread_new();
|
|
AioContext *ctx = iothread_get_aio_context(iothread);
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs;
|
|
BdrvChild *c;
|
|
|
|
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL);
|
|
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
|
|
bs->total_sectors = 65536 / BDRV_SECTOR_SIZE;
|
|
blk_insert_bs(blk, bs, &error_abort);
|
|
c = QLIST_FIRST(&bs->parents);
|
|
|
|
blk_set_aio_context(blk, ctx, &error_abort);
|
|
aio_context_acquire(ctx);
|
|
if (t->fn) {
|
|
t->fn(c);
|
|
}
|
|
if (t->blkfn) {
|
|
t->blkfn(blk);
|
|
}
|
|
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
|
|
aio_context_release(ctx);
|
|
|
|
bdrv_unref(bs);
|
|
blk_unref(blk);
|
|
}
|
|
|
|
typedef struct TestBlockJob {
|
|
BlockJob common;
|
|
bool should_complete;
|
|
int n;
|
|
} TestBlockJob;
|
|
|
|
static int test_job_prepare(Job *job)
|
|
{
|
|
g_assert(qemu_get_current_aio_context() == qemu_get_aio_context());
|
|
return 0;
|
|
}
|
|
|
|
static int coroutine_fn test_job_run(Job *job, Error **errp)
|
|
{
|
|
TestBlockJob *s = container_of(job, TestBlockJob, common.job);
|
|
|
|
job_transition_to_ready(&s->common.job);
|
|
while (!s->should_complete) {
|
|
s->n++;
|
|
g_assert(qemu_get_current_aio_context() == job->aio_context);
|
|
|
|
/* Avoid job_sleep_ns() because it marks the job as !busy. We want to
|
|
* emulate some actual activity (probably some I/O) here so that the
|
|
* drain involved in AioContext switches has to wait for this activity
|
|
* to stop. */
|
|
qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 1000000);
|
|
|
|
job_pause_point(&s->common.job);
|
|
}
|
|
|
|
g_assert(qemu_get_current_aio_context() == job->aio_context);
|
|
return 0;
|
|
}
|
|
|
|
static void test_job_complete(Job *job, Error **errp)
|
|
{
|
|
TestBlockJob *s = container_of(job, TestBlockJob, common.job);
|
|
s->should_complete = true;
|
|
}
|
|
|
|
BlockJobDriver test_job_driver = {
|
|
.job_driver = {
|
|
.instance_size = sizeof(TestBlockJob),
|
|
.free = block_job_free,
|
|
.user_resume = block_job_user_resume,
|
|
.run = test_job_run,
|
|
.complete = test_job_complete,
|
|
.prepare = test_job_prepare,
|
|
},
|
|
};
|
|
|
|
static void test_attach_blockjob(void)
|
|
{
|
|
IOThread *iothread = iothread_new();
|
|
AioContext *ctx = iothread_get_aio_context(iothread);
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs;
|
|
TestBlockJob *tjob;
|
|
|
|
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL, BLK_PERM_ALL);
|
|
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
|
|
blk_insert_bs(blk, bs, &error_abort);
|
|
|
|
tjob = block_job_create("job0", &test_job_driver, NULL, bs,
|
|
0, BLK_PERM_ALL,
|
|
0, 0, NULL, NULL, &error_abort);
|
|
job_start(&tjob->common.job);
|
|
|
|
while (tjob->n == 0) {
|
|
aio_poll(qemu_get_aio_context(), false);
|
|
}
|
|
|
|
blk_set_aio_context(blk, ctx, &error_abort);
|
|
|
|
tjob->n = 0;
|
|
while (tjob->n == 0) {
|
|
aio_poll(qemu_get_aio_context(), false);
|
|
}
|
|
|
|
aio_context_acquire(ctx);
|
|
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
|
|
aio_context_release(ctx);
|
|
|
|
tjob->n = 0;
|
|
while (tjob->n == 0) {
|
|
aio_poll(qemu_get_aio_context(), false);
|
|
}
|
|
|
|
blk_set_aio_context(blk, ctx, &error_abort);
|
|
|
|
tjob->n = 0;
|
|
while (tjob->n == 0) {
|
|
aio_poll(qemu_get_aio_context(), false);
|
|
}
|
|
|
|
aio_context_acquire(ctx);
|
|
WITH_JOB_LOCK_GUARD() {
|
|
job_complete_sync_locked(&tjob->common.job, &error_abort);
|
|
}
|
|
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
|
|
aio_context_release(ctx);
|
|
|
|
bdrv_unref(bs);
|
|
blk_unref(blk);
|
|
}
|
|
|
|
/*
|
|
* Test that changing the AioContext for one node in a tree (here through blk)
|
|
* changes all other nodes as well:
|
|
*
|
|
* blk
|
|
* |
|
|
* | bs_verify [blkverify]
|
|
* | / \
|
|
* | / \
|
|
* bs_a [bdrv_test] bs_b [bdrv_test]
|
|
*
|
|
*/
|
|
static void test_propagate_basic(void)
|
|
{
|
|
IOThread *iothread = iothread_new();
|
|
AioContext *ctx = iothread_get_aio_context(iothread);
|
|
AioContext *main_ctx;
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs_a, *bs_b, *bs_verify;
|
|
QDict *options;
|
|
|
|
/*
|
|
* Create bs_a and its BlockBackend. We cannot take the RESIZE
|
|
* permission because blkverify will not share it on the test
|
|
* image.
|
|
*/
|
|
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL & ~BLK_PERM_RESIZE,
|
|
BLK_PERM_ALL);
|
|
bs_a = bdrv_new_open_driver(&bdrv_test, "bs_a", BDRV_O_RDWR, &error_abort);
|
|
blk_insert_bs(blk, bs_a, &error_abort);
|
|
|
|
/* Create bs_b */
|
|
bs_b = bdrv_new_open_driver(&bdrv_test, "bs_b", BDRV_O_RDWR, &error_abort);
|
|
|
|
/* Create blkverify filter that references both bs_a and bs_b */
|
|
options = qdict_new();
|
|
qdict_put_str(options, "driver", "blkverify");
|
|
qdict_put_str(options, "test", "bs_a");
|
|
qdict_put_str(options, "raw", "bs_b");
|
|
|
|
bs_verify = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
|
|
|
/* Switch the AioContext */
|
|
blk_set_aio_context(blk, ctx, &error_abort);
|
|
g_assert(blk_get_aio_context(blk) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs_a) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs_b) == ctx);
|
|
|
|
/* Switch the AioContext back */
|
|
main_ctx = qemu_get_aio_context();
|
|
aio_context_acquire(ctx);
|
|
blk_set_aio_context(blk, main_ctx, &error_abort);
|
|
aio_context_release(ctx);
|
|
g_assert(blk_get_aio_context(blk) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs_a) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs_verify) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs_b) == main_ctx);
|
|
|
|
bdrv_unref(bs_verify);
|
|
bdrv_unref(bs_b);
|
|
bdrv_unref(bs_a);
|
|
blk_unref(blk);
|
|
}
|
|
|
|
/*
|
|
* Test that diamonds in the graph don't lead to endless recursion:
|
|
*
|
|
* blk
|
|
* |
|
|
* bs_verify [blkverify]
|
|
* / \
|
|
* / \
|
|
* bs_b [raw] bs_c[raw]
|
|
* \ /
|
|
* \ /
|
|
* bs_a [bdrv_test]
|
|
*/
|
|
static void test_propagate_diamond(void)
|
|
{
|
|
IOThread *iothread = iothread_new();
|
|
AioContext *ctx = iothread_get_aio_context(iothread);
|
|
AioContext *main_ctx;
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs_a, *bs_b, *bs_c, *bs_verify;
|
|
QDict *options;
|
|
|
|
/* Create bs_a */
|
|
bs_a = bdrv_new_open_driver(&bdrv_test, "bs_a", BDRV_O_RDWR, &error_abort);
|
|
|
|
/* Create bs_b and bc_c */
|
|
options = qdict_new();
|
|
qdict_put_str(options, "driver", "raw");
|
|
qdict_put_str(options, "file", "bs_a");
|
|
qdict_put_str(options, "node-name", "bs_b");
|
|
bs_b = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
|
|
|
options = qdict_new();
|
|
qdict_put_str(options, "driver", "raw");
|
|
qdict_put_str(options, "file", "bs_a");
|
|
qdict_put_str(options, "node-name", "bs_c");
|
|
bs_c = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
|
|
|
/* Create blkverify filter that references both bs_b and bs_c */
|
|
options = qdict_new();
|
|
qdict_put_str(options, "driver", "blkverify");
|
|
qdict_put_str(options, "test", "bs_b");
|
|
qdict_put_str(options, "raw", "bs_c");
|
|
|
|
bs_verify = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
|
/*
|
|
* Do not take the RESIZE permission: This would require the same
|
|
* from bs_c and thus from bs_a; however, blkverify will not share
|
|
* it on bs_b, and thus it will not be available for bs_a.
|
|
*/
|
|
blk = blk_new(qemu_get_aio_context(), BLK_PERM_ALL & ~BLK_PERM_RESIZE,
|
|
BLK_PERM_ALL);
|
|
blk_insert_bs(blk, bs_verify, &error_abort);
|
|
|
|
/* Switch the AioContext */
|
|
blk_set_aio_context(blk, ctx, &error_abort);
|
|
g_assert(blk_get_aio_context(blk) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs_verify) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs_a) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs_b) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs_c) == ctx);
|
|
|
|
/* Switch the AioContext back */
|
|
main_ctx = qemu_get_aio_context();
|
|
aio_context_acquire(ctx);
|
|
blk_set_aio_context(blk, main_ctx, &error_abort);
|
|
aio_context_release(ctx);
|
|
g_assert(blk_get_aio_context(blk) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs_verify) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs_a) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs_b) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs_c) == main_ctx);
|
|
|
|
blk_unref(blk);
|
|
bdrv_unref(bs_verify);
|
|
bdrv_unref(bs_c);
|
|
bdrv_unref(bs_b);
|
|
bdrv_unref(bs_a);
|
|
}
|
|
|
|
static void test_propagate_mirror(void)
|
|
{
|
|
IOThread *iothread = iothread_new();
|
|
AioContext *ctx = iothread_get_aio_context(iothread);
|
|
AioContext *main_ctx = qemu_get_aio_context();
|
|
BlockDriverState *src, *target, *filter;
|
|
BlockBackend *blk;
|
|
Job *job;
|
|
Error *local_err = NULL;
|
|
|
|
/* Create src and target*/
|
|
src = bdrv_new_open_driver(&bdrv_test, "src", BDRV_O_RDWR, &error_abort);
|
|
target = bdrv_new_open_driver(&bdrv_test, "target", BDRV_O_RDWR,
|
|
&error_abort);
|
|
|
|
/* Start a mirror job */
|
|
mirror_start("job0", src, target, NULL, JOB_DEFAULT, 0, 0, 0,
|
|
MIRROR_SYNC_MODE_NONE, MIRROR_OPEN_BACKING_CHAIN, false,
|
|
BLOCKDEV_ON_ERROR_REPORT, BLOCKDEV_ON_ERROR_REPORT,
|
|
false, "filter_node", MIRROR_COPY_MODE_BACKGROUND,
|
|
&error_abort);
|
|
WITH_JOB_LOCK_GUARD() {
|
|
job = job_get_locked("job0");
|
|
}
|
|
filter = bdrv_find_node("filter_node");
|
|
|
|
/* Change the AioContext of src */
|
|
bdrv_try_set_aio_context(src, ctx, &error_abort);
|
|
g_assert(bdrv_get_aio_context(src) == ctx);
|
|
g_assert(bdrv_get_aio_context(target) == ctx);
|
|
g_assert(bdrv_get_aio_context(filter) == ctx);
|
|
g_assert(job->aio_context == ctx);
|
|
|
|
/* Change the AioContext of target */
|
|
aio_context_acquire(ctx);
|
|
bdrv_try_set_aio_context(target, main_ctx, &error_abort);
|
|
aio_context_release(ctx);
|
|
g_assert(bdrv_get_aio_context(src) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(target) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(filter) == main_ctx);
|
|
|
|
/* With a BlockBackend on src, changing target must fail */
|
|
blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
|
|
blk_insert_bs(blk, src, &error_abort);
|
|
|
|
bdrv_try_set_aio_context(target, ctx, &local_err);
|
|
error_free_or_abort(&local_err);
|
|
|
|
g_assert(blk_get_aio_context(blk) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(src) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(target) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(filter) == main_ctx);
|
|
|
|
/* ...unless we explicitly allow it */
|
|
aio_context_acquire(ctx);
|
|
blk_set_allow_aio_context_change(blk, true);
|
|
bdrv_try_set_aio_context(target, ctx, &error_abort);
|
|
aio_context_release(ctx);
|
|
|
|
g_assert(blk_get_aio_context(blk) == ctx);
|
|
g_assert(bdrv_get_aio_context(src) == ctx);
|
|
g_assert(bdrv_get_aio_context(target) == ctx);
|
|
g_assert(bdrv_get_aio_context(filter) == ctx);
|
|
|
|
job_cancel_sync_all();
|
|
|
|
aio_context_acquire(ctx);
|
|
blk_set_aio_context(blk, main_ctx, &error_abort);
|
|
bdrv_try_set_aio_context(target, main_ctx, &error_abort);
|
|
aio_context_release(ctx);
|
|
|
|
blk_unref(blk);
|
|
bdrv_unref(src);
|
|
bdrv_unref(target);
|
|
}
|
|
|
|
static void test_attach_second_node(void)
|
|
{
|
|
IOThread *iothread = iothread_new();
|
|
AioContext *ctx = iothread_get_aio_context(iothread);
|
|
AioContext *main_ctx = qemu_get_aio_context();
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs, *filter;
|
|
QDict *options;
|
|
|
|
blk = blk_new(ctx, BLK_PERM_ALL, BLK_PERM_ALL);
|
|
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
|
|
blk_insert_bs(blk, bs, &error_abort);
|
|
|
|
options = qdict_new();
|
|
qdict_put_str(options, "driver", "raw");
|
|
qdict_put_str(options, "file", "base");
|
|
|
|
filter = bdrv_open(NULL, NULL, options, BDRV_O_RDWR, &error_abort);
|
|
g_assert(blk_get_aio_context(blk) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs) == ctx);
|
|
g_assert(bdrv_get_aio_context(filter) == ctx);
|
|
|
|
aio_context_acquire(ctx);
|
|
blk_set_aio_context(blk, main_ctx, &error_abort);
|
|
aio_context_release(ctx);
|
|
g_assert(blk_get_aio_context(blk) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(bs) == main_ctx);
|
|
g_assert(bdrv_get_aio_context(filter) == main_ctx);
|
|
|
|
bdrv_unref(filter);
|
|
bdrv_unref(bs);
|
|
blk_unref(blk);
|
|
}
|
|
|
|
static void test_attach_preserve_blk_ctx(void)
|
|
{
|
|
IOThread *iothread = iothread_new();
|
|
AioContext *ctx = iothread_get_aio_context(iothread);
|
|
BlockBackend *blk;
|
|
BlockDriverState *bs;
|
|
|
|
blk = blk_new(ctx, BLK_PERM_ALL, BLK_PERM_ALL);
|
|
bs = bdrv_new_open_driver(&bdrv_test, "base", BDRV_O_RDWR, &error_abort);
|
|
bs->total_sectors = 65536 / BDRV_SECTOR_SIZE;
|
|
|
|
/* Add node to BlockBackend that has an iothread context assigned */
|
|
blk_insert_bs(blk, bs, &error_abort);
|
|
g_assert(blk_get_aio_context(blk) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs) == ctx);
|
|
|
|
/* Remove the node again */
|
|
aio_context_acquire(ctx);
|
|
blk_remove_bs(blk);
|
|
aio_context_release(ctx);
|
|
g_assert(blk_get_aio_context(blk) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs) == qemu_get_aio_context());
|
|
|
|
/* Re-attach the node */
|
|
blk_insert_bs(blk, bs, &error_abort);
|
|
g_assert(blk_get_aio_context(blk) == ctx);
|
|
g_assert(bdrv_get_aio_context(bs) == ctx);
|
|
|
|
aio_context_acquire(ctx);
|
|
blk_set_aio_context(blk, qemu_get_aio_context(), &error_abort);
|
|
aio_context_release(ctx);
|
|
bdrv_unref(bs);
|
|
blk_unref(blk);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int i;
|
|
|
|
bdrv_init();
|
|
qemu_init_main_loop(&error_abort);
|
|
|
|
g_test_init(&argc, &argv, NULL);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sync_op_tests); i++) {
|
|
const SyncOpTest *t = &sync_op_tests[i];
|
|
g_test_add_data_func(t->name, t, test_sync_op);
|
|
}
|
|
|
|
g_test_add_func("/attach/blockjob", test_attach_blockjob);
|
|
g_test_add_func("/attach/second_node", test_attach_second_node);
|
|
g_test_add_func("/attach/preserve_blk_ctx", test_attach_preserve_blk_ctx);
|
|
g_test_add_func("/propagate/basic", test_propagate_basic);
|
|
g_test_add_func("/propagate/diamond", test_propagate_diamond);
|
|
g_test_add_func("/propagate/mirror", test_propagate_mirror);
|
|
|
|
return g_test_run();
|
|
}
|