cryptospore / rpms / qemu-kvm

Forked from rpms/qemu-kvm 2 years ago
Clone
ae23c9
From 789fe0e994045478e61395ba88c61a7f07527b25 Mon Sep 17 00:00:00 2001
ae23c9
From: Kevin Wolf <kwolf@redhat.com>
ae23c9
Date: Wed, 10 Oct 2018 20:08:43 +0100
ae23c9
Subject: [PATCH 12/49] test-bdrv-drain: Add test for node deletion
ae23c9
ae23c9
RH-Author: Kevin Wolf <kwolf@redhat.com>
ae23c9
Message-id: <20181010200843.6710-10-kwolf@redhat.com>
ae23c9
Patchwork-id: 82589
ae23c9
O-Subject: [RHEL-8 qemu-kvm PATCH 09/44] test-bdrv-drain: Add test for node deletion
ae23c9
Bugzilla: 1637976
ae23c9
RH-Acked-by: Max Reitz <mreitz@redhat.com>
ae23c9
RH-Acked-by: John Snow <jsnow@redhat.com>
ae23c9
RH-Acked-by: Thomas Huth <thuth@redhat.com>
ae23c9
ae23c9
From: Max Reitz <mreitz@redhat.com>
ae23c9
ae23c9
This patch adds two bdrv-drain tests for what happens if some BDS goes
ae23c9
away during the drainage.
ae23c9
ae23c9
The basic idea is that you have a parent BDS with some child nodes.
ae23c9
Then, you drain one of the children.  Because of that, the party who
ae23c9
actually owns the parent decides to (A) delete it, or (B) detach all its
ae23c9
children from it -- both while the child is still being drained.
ae23c9
ae23c9
A real-world case where this can happen is the mirror block job, which
ae23c9
may exit if you drain one of its children.
ae23c9
ae23c9
Signed-off-by: Max Reitz <mreitz@redhat.com>
ae23c9
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
ae23c9
(cherry picked from commit 4c8158e359d194394c64acd21caf5e3f3f3141c2)
ae23c9
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
ae23c9
Signed-off-by: Danilo C. L. de Paula <ddepaula@redhat.com>
ae23c9
---
ae23c9
 tests/test-bdrv-drain.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++
ae23c9
 1 file changed, 169 insertions(+)
ae23c9
ae23c9
diff --git a/tests/test-bdrv-drain.c b/tests/test-bdrv-drain.c
ae23c9
index 49786ea..8918a94 100644
ae23c9
--- a/tests/test-bdrv-drain.c
ae23c9
+++ b/tests/test-bdrv-drain.c
ae23c9
@@ -792,6 +792,172 @@ static void test_blockjob_drain_subtree(void)
ae23c9
     test_blockjob_common(BDRV_SUBTREE_DRAIN);
ae23c9
 }
ae23c9
 
ae23c9
+
ae23c9
+typedef struct BDRVTestTopState {
ae23c9
+    BdrvChild *wait_child;
ae23c9
+} BDRVTestTopState;
ae23c9
+
ae23c9
+static void bdrv_test_top_close(BlockDriverState *bs)
ae23c9
+{
ae23c9
+    BdrvChild *c, *next_c;
ae23c9
+    QLIST_FOREACH_SAFE(c, &bs->children, next, next_c) {
ae23c9
+        bdrv_unref_child(bs, c);
ae23c9
+    }
ae23c9
+}
ae23c9
+
ae23c9
+static int coroutine_fn bdrv_test_top_co_preadv(BlockDriverState *bs,
ae23c9
+                                                uint64_t offset, uint64_t bytes,
ae23c9
+                                                QEMUIOVector *qiov, int flags)
ae23c9
+{
ae23c9
+    BDRVTestTopState *tts = bs->opaque;
ae23c9
+    return bdrv_co_preadv(tts->wait_child, offset, bytes, qiov, flags);
ae23c9
+}
ae23c9
+
ae23c9
+static BlockDriver bdrv_test_top_driver = {
ae23c9
+    .format_name            = "test_top_driver",
ae23c9
+    .instance_size          = sizeof(BDRVTestTopState),
ae23c9
+
ae23c9
+    .bdrv_close             = bdrv_test_top_close,
ae23c9
+    .bdrv_co_preadv         = bdrv_test_top_co_preadv,
ae23c9
+
ae23c9
+    .bdrv_child_perm        = bdrv_format_default_perms,
ae23c9
+};
ae23c9
+
ae23c9
+typedef struct TestCoDeleteByDrainData {
ae23c9
+    BlockBackend *blk;
ae23c9
+    bool detach_instead_of_delete;
ae23c9
+    bool done;
ae23c9
+} TestCoDeleteByDrainData;
ae23c9
+
ae23c9
+static void coroutine_fn test_co_delete_by_drain(void *opaque)
ae23c9
+{
ae23c9
+    TestCoDeleteByDrainData *dbdd = opaque;
ae23c9
+    BlockBackend *blk = dbdd->blk;
ae23c9
+    BlockDriverState *bs = blk_bs(blk);
ae23c9
+    BDRVTestTopState *tts = bs->opaque;
ae23c9
+    void *buffer = g_malloc(65536);
ae23c9
+    QEMUIOVector qiov;
ae23c9
+    struct iovec iov = {
ae23c9
+        .iov_base = buffer,
ae23c9
+        .iov_len  = 65536,
ae23c9
+    };
ae23c9
+
ae23c9
+    qemu_iovec_init_external(&qiov, &iov, 1);
ae23c9
+
ae23c9
+    /* Pretend some internal write operation from parent to child.
ae23c9
+     * Important: We have to read from the child, not from the parent!
ae23c9
+     * Draining works by first propagating it all up the tree to the
ae23c9
+     * root and then waiting for drainage from root to the leaves
ae23c9
+     * (protocol nodes).  If we have a request waiting on the root,
ae23c9
+     * everything will be drained before we go back down the tree, but
ae23c9
+     * we do not want that.  We want to be in the middle of draining
ae23c9
+     * when this following requests returns. */
ae23c9
+    bdrv_co_preadv(tts->wait_child, 0, 65536, &qiov, 0);
ae23c9
+
ae23c9
+    g_assert_cmpint(bs->refcnt, ==, 1);
ae23c9
+
ae23c9
+    if (!dbdd->detach_instead_of_delete) {
ae23c9
+        blk_unref(blk);
ae23c9
+    } else {
ae23c9
+        BdrvChild *c, *next_c;
ae23c9
+        QLIST_FOREACH_SAFE(c, &bs->children, next, next_c) {
ae23c9
+            bdrv_unref_child(bs, c);
ae23c9
+        }
ae23c9
+    }
ae23c9
+
ae23c9
+    dbdd->done = true;
ae23c9
+}
ae23c9
+
ae23c9
+/**
ae23c9
+ * Test what happens when some BDS has some children, you drain one of
ae23c9
+ * them and this results in the BDS being deleted.
ae23c9
+ *
ae23c9
+ * If @detach_instead_of_delete is set, the BDS is not going to be
ae23c9
+ * deleted but will only detach all of its children.
ae23c9
+ */
ae23c9
+static void do_test_delete_by_drain(bool detach_instead_of_delete)
ae23c9
+{
ae23c9
+    BlockBackend *blk;
ae23c9
+    BlockDriverState *bs, *child_bs, *null_bs;
ae23c9
+    BDRVTestTopState *tts;
ae23c9
+    TestCoDeleteByDrainData dbdd;
ae23c9
+    Coroutine *co;
ae23c9
+
ae23c9
+    bs = bdrv_new_open_driver(&bdrv_test_top_driver, "top", BDRV_O_RDWR,
ae23c9
+                              &error_abort);
ae23c9
+    bs->total_sectors = 65536 >> BDRV_SECTOR_BITS;
ae23c9
+    tts = bs->opaque;
ae23c9
+
ae23c9
+    null_bs = bdrv_open("null-co://", NULL, NULL, BDRV_O_RDWR | BDRV_O_PROTOCOL,
ae23c9
+                        &error_abort);
ae23c9
+    bdrv_attach_child(bs, null_bs, "null-child", &child_file, &error_abort);
ae23c9
+
ae23c9
+    /* This child will be the one to pass to requests through to, and
ae23c9
+     * it will stall until a drain occurs */
ae23c9
+    child_bs = bdrv_new_open_driver(&bdrv_test, "child", BDRV_O_RDWR,
ae23c9
+                                    &error_abort);
ae23c9
+    child_bs->total_sectors = 65536 >> BDRV_SECTOR_BITS;
ae23c9
+    /* Takes our reference to child_bs */
ae23c9
+    tts->wait_child = bdrv_attach_child(bs, child_bs, "wait-child", &child_file,
ae23c9
+                                        &error_abort);
ae23c9
+
ae23c9
+    /* This child is just there to be deleted
ae23c9
+     * (for detach_instead_of_delete == true) */
ae23c9
+    null_bs = bdrv_open("null-co://", NULL, NULL, BDRV_O_RDWR | BDRV_O_PROTOCOL,
ae23c9
+                        &error_abort);
ae23c9
+    bdrv_attach_child(bs, null_bs, "null-child", &child_file, &error_abort);
ae23c9
+
ae23c9
+    blk = blk_new(BLK_PERM_ALL, BLK_PERM_ALL);
ae23c9
+    blk_insert_bs(blk, bs, &error_abort);
ae23c9
+
ae23c9
+    /* Referenced by blk now */
ae23c9
+    bdrv_unref(bs);
ae23c9
+
ae23c9
+    g_assert_cmpint(bs->refcnt, ==, 1);
ae23c9
+    g_assert_cmpint(child_bs->refcnt, ==, 1);
ae23c9
+    g_assert_cmpint(null_bs->refcnt, ==, 1);
ae23c9
+
ae23c9
+
ae23c9
+    dbdd = (TestCoDeleteByDrainData){
ae23c9
+        .blk = blk,
ae23c9
+        .detach_instead_of_delete = detach_instead_of_delete,
ae23c9
+        .done = false,
ae23c9
+    };
ae23c9
+    co = qemu_coroutine_create(test_co_delete_by_drain, &dbdd);
ae23c9
+    qemu_coroutine_enter(co);
ae23c9
+
ae23c9
+    /* Drain the child while the read operation is still pending.
ae23c9
+     * This should result in the operation finishing and
ae23c9
+     * test_co_delete_by_drain() resuming.  Thus, @bs will be deleted
ae23c9
+     * and the coroutine will exit while this drain operation is still
ae23c9
+     * in progress. */
ae23c9
+    bdrv_ref(child_bs);
ae23c9
+    bdrv_drain(child_bs);
ae23c9
+    bdrv_unref(child_bs);
ae23c9
+
ae23c9
+    while (!dbdd.done) {
ae23c9
+        aio_poll(qemu_get_aio_context(), true);
ae23c9
+    }
ae23c9
+
ae23c9
+    if (detach_instead_of_delete) {
ae23c9
+        /* Here, the reference has not passed over to the coroutine,
ae23c9
+         * so we have to delete the BB ourselves */
ae23c9
+        blk_unref(blk);
ae23c9
+    }
ae23c9
+}
ae23c9
+
ae23c9
+
ae23c9
+static void test_delete_by_drain(void)
ae23c9
+{
ae23c9
+    do_test_delete_by_drain(false);
ae23c9
+}
ae23c9
+
ae23c9
+static void test_detach_by_drain(void)
ae23c9
+{
ae23c9
+    do_test_delete_by_drain(true);
ae23c9
+}
ae23c9
+
ae23c9
+
ae23c9
 int main(int argc, char **argv)
ae23c9
 {
ae23c9
     int ret;
ae23c9
@@ -839,6 +1005,9 @@ int main(int argc, char **argv)
ae23c9
     g_test_add_func("/bdrv-drain/blockjob/drain_subtree",
ae23c9
                     test_blockjob_drain_subtree);
ae23c9
 
ae23c9
+    g_test_add_func("/bdrv-drain/deletion", test_delete_by_drain);
ae23c9
+    g_test_add_func("/bdrv-drain/detach", test_detach_by_drain);
ae23c9
+
ae23c9
     ret = g_test_run();
ae23c9
     qemu_event_destroy(&done_event);
ae23c9
     return ret;
ae23c9
-- 
ae23c9
1.8.3.1
ae23c9