tests: Add job commit by drained_end test

Signed-off-by: Max Reitz <mreitz@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Max Reitz 2019-07-19 11:26:10 +02:00 committed by Kevin Wolf
parent 804db8ea00
commit 8e4428106a

View File

@ -1527,6 +1527,122 @@ static void test_set_aio_context(void)
iothread_join(b);
}
typedef struct TestDropBackingBlockJob {
BlockJob common;
bool should_complete;
bool *did_complete;
} TestDropBackingBlockJob;
static int coroutine_fn test_drop_backing_job_run(Job *job, Error **errp)
{
TestDropBackingBlockJob *s =
container_of(job, TestDropBackingBlockJob, common.job);
while (!s->should_complete) {
job_sleep_ns(job, 0);
}
return 0;
}
static void test_drop_backing_job_commit(Job *job)
{
TestDropBackingBlockJob *s =
container_of(job, TestDropBackingBlockJob, common.job);
bdrv_set_backing_hd(blk_bs(s->common.blk), NULL, &error_abort);
*s->did_complete = true;
}
static const BlockJobDriver test_drop_backing_job_driver = {
.job_driver = {
.instance_size = sizeof(TestDropBackingBlockJob),
.free = block_job_free,
.user_resume = block_job_user_resume,
.drain = block_job_drain,
.run = test_drop_backing_job_run,
.commit = test_drop_backing_job_commit,
}
};
/**
* Creates a child node with three parent nodes on it, and then runs a
* block job on the final one, parent-node-2.
*
* (TODO: parent-node-0 currently serves no purpose, but will as of a
* follow-up patch.)
*
* The job is then asked to complete before a section where the child
* is drained.
*
* Ending this section will undrain the child's parents, first
* parent-node-2, then parent-node-1, then parent-node-0 -- the parent
* list is in reverse order of how they were added. Ending the drain
* on parent-node-2 will resume the job, thus completing it and
* scheduling job_exit().
*
* Ending the drain on parent-node-1 will poll the AioContext, which
* lets job_exit() and thus test_drop_backing_job_commit() run. That
* function removes the child as parent-node-2's backing file.
*
* In old (and buggy) implementations, there are two problems with
* that:
* (A) bdrv_drain_invoke() polls for every node that leaves the
* drained section. This means that job_exit() is scheduled
* before the child has left the drained section. Its
* quiesce_counter is therefore still 1 when it is removed from
* parent-node-2.
*
* (B) bdrv_replace_child_noperm() calls drained_end() on the old
* child's parents as many times as the child is quiesced. This
* means it will call drained_end() on parent-node-2 once.
* Because parent-node-2 is no longer quiesced at this point, this
* will fail.
*
* bdrv_replace_child_noperm() therefore must call drained_end() on
* the parent only if it really is still drained because the child is
* drained.
*/
static void test_blockjob_commit_by_drained_end(void)
{
BlockDriverState *bs_child, *bs_parents[3];
TestDropBackingBlockJob *job;
bool job_has_completed = false;
int i;
bs_child = bdrv_new_open_driver(&bdrv_test, "child-node", BDRV_O_RDWR,
&error_abort);
for (i = 0; i < 3; i++) {
char name[32];
snprintf(name, sizeof(name), "parent-node-%i", i);
bs_parents[i] = bdrv_new_open_driver(&bdrv_test, name, BDRV_O_RDWR,
&error_abort);
bdrv_set_backing_hd(bs_parents[i], bs_child, &error_abort);
}
job = block_job_create("job", &test_drop_backing_job_driver, NULL,
bs_parents[2], 0, BLK_PERM_ALL, 0, 0, NULL, NULL,
&error_abort);
job->did_complete = &job_has_completed;
job_start(&job->common.job);
job->should_complete = true;
bdrv_drained_begin(bs_child);
g_assert(!job_has_completed);
bdrv_drained_end(bs_child);
g_assert(job_has_completed);
bdrv_unref(bs_parents[0]);
bdrv_unref(bs_parents[1]);
bdrv_unref(bs_parents[2]);
bdrv_unref(bs_child);
}
int main(int argc, char **argv)
{
int ret;
@ -1610,6 +1726,9 @@ int main(int argc, char **argv)
g_test_add_func("/bdrv-drain/set_aio_context", test_set_aio_context);
g_test_add_func("/bdrv-drain/blockjob/commit_by_drained_end",
test_blockjob_commit_by_drained_end);
ret = g_test_run();
qemu_event_destroy(&done_event);
return ret;