qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

test-bdrv-graph-mod.c (15777B)


      1 /*
      2  * Block node graph modifications tests
      3  *
      4  * Copyright (c) 2019-2021 Virtuozzo International GmbH. All rights reserved.
      5  *
      6  * This program is free software; you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation; either version 2 of the License, or
      9  * (at your option) any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     18  *
     19  */
     20 
     21 #include "qemu/osdep.h"
     22 #include "qapi/error.h"
     23 #include "qemu/main-loop.h"
     24 #include "block/block_int.h"
     25 #include "sysemu/block-backend.h"
     26 
     27 static BlockDriver bdrv_pass_through = {
     28     .format_name = "pass-through",
     29     .is_filter = true,
     30     .filtered_child_is_backing = true,
     31     .bdrv_child_perm = bdrv_default_perms,
     32 };
     33 
     34 static void no_perm_default_perms(BlockDriverState *bs, BdrvChild *c,
     35                                          BdrvChildRole role,
     36                                          BlockReopenQueue *reopen_queue,
     37                                          uint64_t perm, uint64_t shared,
     38                                          uint64_t *nperm, uint64_t *nshared)
     39 {
     40     *nperm = 0;
     41     *nshared = BLK_PERM_ALL;
     42 }
     43 
     44 static BlockDriver bdrv_no_perm = {
     45     .format_name = "no-perm",
     46     .supports_backing = true,
     47     .bdrv_child_perm = no_perm_default_perms,
     48 };
     49 
     50 static void exclusive_write_perms(BlockDriverState *bs, BdrvChild *c,
     51                                   BdrvChildRole role,
     52                                   BlockReopenQueue *reopen_queue,
     53                                   uint64_t perm, uint64_t shared,
     54                                   uint64_t *nperm, uint64_t *nshared)
     55 {
     56     *nperm = BLK_PERM_WRITE;
     57     *nshared = BLK_PERM_ALL & ~BLK_PERM_WRITE;
     58 }
     59 
     60 static BlockDriver bdrv_exclusive_writer = {
     61     .format_name = "exclusive-writer",
     62     .is_filter = true,
     63     .filtered_child_is_backing = true,
     64     .bdrv_child_perm = exclusive_write_perms,
     65 };
     66 
     67 static BlockDriverState *no_perm_node(const char *name)
     68 {
     69     return bdrv_new_open_driver(&bdrv_no_perm, name, BDRV_O_RDWR, &error_abort);
     70 }
     71 
     72 static BlockDriverState *pass_through_node(const char *name)
     73 {
     74     return bdrv_new_open_driver(&bdrv_pass_through, name,
     75                                 BDRV_O_RDWR, &error_abort);
     76 }
     77 
     78 static BlockDriverState *exclusive_writer_node(const char *name)
     79 {
     80     return bdrv_new_open_driver(&bdrv_exclusive_writer, name,
     81                                 BDRV_O_RDWR, &error_abort);
     82 }
     83 
     84 /*
     85  * test_update_perm_tree
     86  *
     87  * When checking node for a possibility to update permissions, it's subtree
     88  * should be correctly checked too. New permissions for each node should be
     89  * calculated and checked in context of permissions of other nodes. If we
     90  * check new permissions of the node only in context of old permissions of
     91  * its neighbors, we can finish up with wrong permission graph.
     92  *
     93  * This test firstly create the following graph:
     94  *                                +--------+
     95  *                                |  root  |
     96  *                                +--------+
     97  *                                    |
     98  *                                    | perm: write, read
     99  *                                    | shared: except write
    100  *                                    v
    101  *  +-------------------+           +----------------+
    102  *  | passtrough filter |---------->|  null-co node  |
    103  *  +-------------------+           +----------------+
    104  *
    105  *
    106  * and then, tries to append filter under node. Expected behavior: fail.
    107  * Otherwise we'll get the following picture, with two BdrvChild'ren, having
    108  * write permission to one node, without actually sharing it.
    109  *
    110  *                     +--------+
    111  *                     |  root  |
    112  *                     +--------+
    113  *                         |
    114  *                         | perm: write, read
    115  *                         | shared: except write
    116  *                         v
    117  *                +-------------------+
    118  *                | passtrough filter |
    119  *                +-------------------+
    120  *                       |   |
    121  *     perm: write, read |   | perm: write, read
    122  *  shared: except write |   | shared: except write
    123  *                       v   v
    124  *                +----------------+
    125  *                |  null co node  |
    126  *                +----------------+
    127  */
    128 static void test_update_perm_tree(void)
    129 {
    130     int ret;
    131 
    132     BlockBackend *root = blk_new(qemu_get_aio_context(),
    133                                  BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ,
    134                                  BLK_PERM_ALL & ~BLK_PERM_WRITE);
    135     BlockDriverState *bs = no_perm_node("node");
    136     BlockDriverState *filter = pass_through_node("filter");
    137 
    138     blk_insert_bs(root, bs, &error_abort);
    139 
    140     bdrv_attach_child(filter, bs, "child", &child_of_bds,
    141                       BDRV_CHILD_DATA, &error_abort);
    142 
    143     ret = bdrv_append(filter, bs, NULL);
    144     g_assert_cmpint(ret, <, 0);
    145 
    146     bdrv_unref(filter);
    147     blk_unref(root);
    148 }
    149 
    150 /*
    151  * test_should_update_child
    152  *
    153  * Test that bdrv_replace_node, and concretely should_update_child
    154  * do the right thing, i.e. not creating loops on the graph.
    155  *
    156  * The test does the following:
    157  * 1. initial graph:
    158  *
    159  *   +------+          +--------+
    160  *   | root |          | filter |
    161  *   +------+          +--------+
    162  *      |                  |
    163  *  root|            target|
    164  *      v                  v
    165  *   +------+          +--------+
    166  *   | node |<---------| target |
    167  *   +------+  backing +--------+
    168  *
    169  * 2. Append @filter above @node. If should_update_child works correctly,
    170  * it understands, that backing child of @target should not be updated,
    171  * as it will create a loop on node graph. Resulting picture should
    172  * be the left one, not the right:
    173  *
    174  *     +------+                            +------+
    175  *     | root |                            | root |
    176  *     +------+                            +------+
    177  *        |                                   |
    178  *    root|                               root|
    179  *        v                                   v
    180  *    +--------+   target                 +--------+   target
    181  *    | filter |--------------+           | filter |--------------+
    182  *    +--------+              |           +--------+              |
    183  *        |                   |               |  ^                v
    184  * backing|                   |        backing|  |           +--------+
    185  *        v                   v               |  +-----------| target |
    186  *     +------+          +--------+           v      backing +--------+
    187  *     | node |<---------| target |        +------+
    188  *     +------+  backing +--------+        | node |
    189  *                                         +------+
    190  *
    191  *    (good picture)                       (bad picture)
    192  *
    193  */
    194 static void test_should_update_child(void)
    195 {
    196     BlockBackend *root = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
    197     BlockDriverState *bs = no_perm_node("node");
    198     BlockDriverState *filter = no_perm_node("filter");
    199     BlockDriverState *target = no_perm_node("target");
    200 
    201     blk_insert_bs(root, bs, &error_abort);
    202 
    203     bdrv_set_backing_hd(target, bs, &error_abort);
    204 
    205     g_assert(target->backing->bs == bs);
    206     bdrv_attach_child(filter, target, "target", &child_of_bds,
    207                       BDRV_CHILD_DATA, &error_abort);
    208     bdrv_append(filter, bs, &error_abort);
    209     g_assert(target->backing->bs == bs);
    210 
    211     bdrv_unref(filter);
    212     bdrv_unref(bs);
    213     blk_unref(root);
    214 }
    215 
    216 /*
    217  * test_parallel_exclusive_write
    218  *
    219  * Check that when we replace node, old permissions of the node being removed
    220  * doesn't break the replacement.
    221  */
    222 static void test_parallel_exclusive_write(void)
    223 {
    224     BlockDriverState *top = exclusive_writer_node("top");
    225     BlockDriverState *base = no_perm_node("base");
    226     BlockDriverState *fl1 = pass_through_node("fl1");
    227     BlockDriverState *fl2 = pass_through_node("fl2");
    228 
    229     /*
    230      * bdrv_attach_child() eats child bs reference, so we need two @base
    231      * references for two filters:
    232      */
    233     bdrv_ref(base);
    234 
    235     bdrv_attach_child(top, fl1, "backing", &child_of_bds,
    236                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
    237                       &error_abort);
    238     bdrv_attach_child(fl1, base, "backing", &child_of_bds,
    239                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
    240                       &error_abort);
    241     bdrv_attach_child(fl2, base, "backing", &child_of_bds,
    242                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
    243                       &error_abort);
    244 
    245     bdrv_replace_node(fl1, fl2, &error_abort);
    246 
    247     bdrv_unref(fl2);
    248     bdrv_unref(top);
    249 }
    250 
    251 /*
    252  * write-to-selected node may have several DATA children, one of them may be
    253  * "selected". Exclusive write permission is taken on selected child.
    254  *
    255  * We don't realize write handler itself, as we need only to test how permission
    256  * update works.
    257  */
    258 typedef struct BDRVWriteToSelectedState {
    259     BdrvChild *selected;
    260 } BDRVWriteToSelectedState;
    261 
    262 static void write_to_selected_perms(BlockDriverState *bs, BdrvChild *c,
    263                                     BdrvChildRole role,
    264                                     BlockReopenQueue *reopen_queue,
    265                                     uint64_t perm, uint64_t shared,
    266                                     uint64_t *nperm, uint64_t *nshared)
    267 {
    268     BDRVWriteToSelectedState *s = bs->opaque;
    269 
    270     if (s->selected && c == s->selected) {
    271         *nperm = BLK_PERM_WRITE;
    272         *nshared = BLK_PERM_ALL & ~BLK_PERM_WRITE;
    273     } else {
    274         *nperm = 0;
    275         *nshared = BLK_PERM_ALL;
    276     }
    277 }
    278 
    279 static BlockDriver bdrv_write_to_selected = {
    280     .format_name = "write-to-selected",
    281     .instance_size = sizeof(BDRVWriteToSelectedState),
    282     .bdrv_child_perm = write_to_selected_perms,
    283 };
    284 
    285 
    286 /*
    287  * The following test shows that topological-sort order is required for
    288  * permission update, simple DFS is not enough.
    289  *
    290  * Consider the block driver (write-to-selected) which has two children: one is
    291  * selected so we have exclusive write access to it and for the other one we
    292  * don't need any specific permissions.
    293  *
    294  * And, these two children has a common base child, like this:
    295  *   (additional "top" on top is used in test just because the only public
    296  *    function to update permission should get a specific child to update.
    297  *    Making bdrv_refresh_perms() public just for this test isn't worth it)
    298  *
    299  * ┌─────┐     ┌───────────────────┐     ┌─────┐
    300  * │ fl2 │ ◀── │ write-to-selected │ ◀── │ top │
    301  * └─────┘     └───────────────────┘     └─────┘
    302  *   │           │
    303  *   │           │ w
    304  *   │           ▼
    305  *   │         ┌──────┐
    306  *   │         │ fl1  │
    307  *   │         └──────┘
    308  *   │           │
    309  *   │           │ w
    310  *   │           ▼
    311  *   │         ┌──────┐
    312  *   └───────▶ │ base │
    313  *             └──────┘
    314  *
    315  * So, exclusive write is propagated.
    316  *
    317  * Assume, we want to select fl2 instead of fl1.
    318  * So, we set some option for write-to-selected driver and do permission update.
    319  *
    320  * With simple DFS, if permission update goes first through
    321  * write-to-selected -> fl1 -> base branch it will succeed: it firstly drop
    322  * exclusive write permissions and than apply them for another BdrvChildren.
    323  * But if permission update goes first through write-to-selected -> fl2 -> base
    324  * branch it will fail, as when we try to update fl2->base child, old not yet
    325  * updated fl1->base child will be in conflict.
    326  *
    327  * With topological-sort order we always update parents before children, so fl1
    328  * and fl2 are both updated when we update base and there is no conflict.
    329  */
    330 static void test_parallel_perm_update(void)
    331 {
    332     BlockDriverState *top = no_perm_node("top");
    333     BlockDriverState *ws =
    334             bdrv_new_open_driver(&bdrv_write_to_selected, "ws", BDRV_O_RDWR,
    335                                  &error_abort);
    336     BDRVWriteToSelectedState *s = ws->opaque;
    337     BlockDriverState *base = no_perm_node("base");
    338     BlockDriverState *fl1 = pass_through_node("fl1");
    339     BlockDriverState *fl2 = pass_through_node("fl2");
    340     BdrvChild *c_fl1, *c_fl2;
    341 
    342     /*
    343      * bdrv_attach_child() eats child bs reference, so we need two @base
    344      * references for two filters:
    345      */
    346     bdrv_ref(base);
    347 
    348     bdrv_attach_child(top, ws, "file", &child_of_bds, BDRV_CHILD_DATA,
    349                       &error_abort);
    350     c_fl1 = bdrv_attach_child(ws, fl1, "first", &child_of_bds,
    351                               BDRV_CHILD_DATA, &error_abort);
    352     c_fl2 = bdrv_attach_child(ws, fl2, "second", &child_of_bds,
    353                               BDRV_CHILD_DATA, &error_abort);
    354     bdrv_attach_child(fl1, base, "backing", &child_of_bds,
    355                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
    356                       &error_abort);
    357     bdrv_attach_child(fl2, base, "backing", &child_of_bds,
    358                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
    359                       &error_abort);
    360 
    361     /* Select fl1 as first child to be active */
    362     s->selected = c_fl1;
    363     bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
    364 
    365     assert(c_fl1->perm & BLK_PERM_WRITE);
    366     assert(!(c_fl2->perm & BLK_PERM_WRITE));
    367 
    368     /* Now, try to switch active child and update permissions */
    369     s->selected = c_fl2;
    370     bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
    371 
    372     assert(c_fl2->perm & BLK_PERM_WRITE);
    373     assert(!(c_fl1->perm & BLK_PERM_WRITE));
    374 
    375     /* Switch once more, to not care about real child order in the list */
    376     s->selected = c_fl1;
    377     bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
    378 
    379     assert(c_fl1->perm & BLK_PERM_WRITE);
    380     assert(!(c_fl2->perm & BLK_PERM_WRITE));
    381 
    382     bdrv_unref(top);
    383 }
    384 
    385 /*
    386  * It's possible that filter required permissions allows to insert it to backing
    387  * chain, like:
    388  *
    389  *  1.  [top] -> [filter] -> [base]
    390  *
    391  * but doesn't allow to add it as a branch:
    392  *
    393  *  2.  [filter] --\
    394  *                 v
    395  *      [top] -> [base]
    396  *
    397  * So, inserting such filter should do all graph modifications and only then
    398  * update permissions. If we try to go through intermediate state [2] and update
    399  * permissions on it we'll fail.
    400  *
    401  * Let's check that bdrv_append() can append such a filter.
    402  */
    403 static void test_append_greedy_filter(void)
    404 {
    405     BlockDriverState *top = exclusive_writer_node("top");
    406     BlockDriverState *base = no_perm_node("base");
    407     BlockDriverState *fl = exclusive_writer_node("fl1");
    408 
    409     bdrv_attach_child(top, base, "backing", &child_of_bds,
    410                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
    411                       &error_abort);
    412 
    413     bdrv_append(fl, base, &error_abort);
    414     bdrv_unref(fl);
    415     bdrv_unref(top);
    416 }
    417 
    418 int main(int argc, char *argv[])
    419 {
    420     bdrv_init();
    421     qemu_init_main_loop(&error_abort);
    422 
    423     g_test_init(&argc, &argv, NULL);
    424 
    425     g_test_add_func("/bdrv-graph-mod/update-perm-tree", test_update_perm_tree);
    426     g_test_add_func("/bdrv-graph-mod/should-update-child",
    427                     test_should_update_child);
    428     g_test_add_func("/bdrv-graph-mod/parallel-perm-update",
    429                     test_parallel_perm_update);
    430     g_test_add_func("/bdrv-graph-mod/parallel-exclusive-write",
    431                     test_parallel_exclusive_write);
    432     g_test_add_func("/bdrv-graph-mod/append-greedy-filter",
    433                     test_append_greedy_filter);
    434 
    435     return g_test_run();
    436 }