test-bdrv-graph-mod.c (14438B)
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}