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