xref: /qemu/tests/unit/test-bdrv-graph-mod.c (revision 83c2201fc47bd0dfa656bde7202bd0e2539d54a0)
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 "system/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 
no_perm_default_perms(BlockDriverState * bs,BdrvChild * c,BdrvChildRole role,BlockReopenQueue * reopen_queue,uint64_t perm,uint64_t shared,uint64_t * nperm,uint64_t * nshared)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 
exclusive_write_perms(BlockDriverState * bs,BdrvChild * c,BdrvChildRole role,BlockReopenQueue * reopen_queue,uint64_t perm,uint64_t shared,uint64_t * nperm,uint64_t * nshared)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 
no_perm_node(const char * name)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 
pass_through_node(const char * name)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 
exclusive_writer_node(const char * name)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  *  | passthrough 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  *                | passthrough 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  */
test_update_perm_tree(void)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_drain_all_begin();
141     bdrv_graph_wrlock();
142     bdrv_attach_child(filter, bs, "child", &child_of_bds,
143                       BDRV_CHILD_DATA, &error_abort);
144     bdrv_graph_wrunlock();
145     bdrv_drain_all_end();
146 
147     ret = bdrv_append(filter, bs, NULL);
148     g_assert_cmpint(ret, <, 0);
149 
150     bdrv_unref(filter);
151     blk_unref(root);
152 }
153 
154 /*
155  * test_should_update_child
156  *
157  * Test that bdrv_replace_node, and concretely should_update_child
158  * do the right thing, i.e. not creating loops on the graph.
159  *
160  * The test does the following:
161  * 1. initial graph:
162  *
163  *   +------+          +--------+
164  *   | root |          | filter |
165  *   +------+          +--------+
166  *      |                  |
167  *  root|            target|
168  *      v                  v
169  *   +------+          +--------+
170  *   | node |<---------| target |
171  *   +------+  backing +--------+
172  *
173  * 2. Append @filter above @node. If should_update_child works correctly,
174  * it understands, that backing child of @target should not be updated,
175  * as it will create a loop on node graph. Resulting picture should
176  * be the left one, not the right:
177  *
178  *     +------+                            +------+
179  *     | root |                            | root |
180  *     +------+                            +------+
181  *        |                                   |
182  *    root|                               root|
183  *        v                                   v
184  *    +--------+   target                 +--------+   target
185  *    | filter |--------------+           | filter |--------------+
186  *    +--------+              |           +--------+              |
187  *        |                   |               |  ^                v
188  * backing|                   |        backing|  |           +--------+
189  *        v                   v               |  +-----------| target |
190  *     +------+          +--------+           v      backing +--------+
191  *     | node |<---------| target |        +------+
192  *     +------+  backing +--------+        | node |
193  *                                         +------+
194  *
195  *    (good picture)                       (bad picture)
196  *
197  */
test_should_update_child(void)198 static void test_should_update_child(void)
199 {
200     BlockBackend *root = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
201     BlockDriverState *bs = no_perm_node("node");
202     BlockDriverState *filter = no_perm_node("filter");
203     BlockDriverState *target = no_perm_node("target");
204 
205     blk_insert_bs(root, bs, &error_abort);
206 
207     bdrv_set_backing_hd(target, bs, &error_abort);
208 
209     bdrv_drain_all_begin();
210     bdrv_graph_wrlock();
211     g_assert(target->backing->bs == bs);
212     bdrv_attach_child(filter, target, "target", &child_of_bds,
213                       BDRV_CHILD_DATA, &error_abort);
214     bdrv_graph_wrunlock();
215     bdrv_drain_all_end();
216     bdrv_append(filter, bs, &error_abort);
217 
218     bdrv_graph_rdlock_main_loop();
219     g_assert(target->backing->bs == bs);
220     bdrv_graph_rdunlock_main_loop();
221 
222     bdrv_unref(filter);
223     bdrv_unref(bs);
224     blk_unref(root);
225 }
226 
227 /*
228  * test_parallel_exclusive_write
229  *
230  * Check that when we replace node, old permissions of the node being removed
231  * doesn't break the replacement.
232  */
test_parallel_exclusive_write(void)233 static void test_parallel_exclusive_write(void)
234 {
235     BlockDriverState *top = exclusive_writer_node("top");
236     BlockDriverState *base = no_perm_node("base");
237     BlockDriverState *fl1 = pass_through_node("fl1");
238     BlockDriverState *fl2 = pass_through_node("fl2");
239 
240     bdrv_drained_begin(fl1);
241     bdrv_drained_begin(fl2);
242 
243     /*
244      * bdrv_attach_child() eats child bs reference, so we need two @base
245      * references for two filters. We also need an additional @fl1 reference so
246      * that it still exists when we want to undrain it.
247      */
248     bdrv_ref(base);
249     bdrv_ref(fl1);
250 
251     bdrv_drain_all_begin();
252     bdrv_graph_wrlock();
253     bdrv_attach_child(top, fl1, "backing", &child_of_bds,
254                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
255                       &error_abort);
256     bdrv_attach_child(fl1, base, "backing", &child_of_bds,
257                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
258                       &error_abort);
259     bdrv_attach_child(fl2, base, "backing", &child_of_bds,
260                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
261                       &error_abort);
262 
263     bdrv_replace_node(fl1, fl2, &error_abort);
264     bdrv_graph_wrunlock();
265     bdrv_drain_all_end();
266 
267     bdrv_drained_end(fl2);
268     bdrv_drained_end(fl1);
269 
270     bdrv_unref(fl1);
271     bdrv_unref(fl2);
272     bdrv_unref(top);
273 }
274 
275 /*
276  * write-to-selected node may have several DATA children, one of them may be
277  * "selected". Exclusive write permission is taken on selected child.
278  *
279  * We don't realize write handler itself, as we need only to test how permission
280  * update works.
281  */
282 typedef struct BDRVWriteToSelectedState {
283     BdrvChild *selected;
284 } BDRVWriteToSelectedState;
285 
write_to_selected_perms(BlockDriverState * bs,BdrvChild * c,BdrvChildRole role,BlockReopenQueue * reopen_queue,uint64_t perm,uint64_t shared,uint64_t * nperm,uint64_t * nshared)286 static void write_to_selected_perms(BlockDriverState *bs, BdrvChild *c,
287                                     BdrvChildRole role,
288                                     BlockReopenQueue *reopen_queue,
289                                     uint64_t perm, uint64_t shared,
290                                     uint64_t *nperm, uint64_t *nshared)
291 {
292     BDRVWriteToSelectedState *s = bs->opaque;
293 
294     if (s->selected && c == s->selected) {
295         *nperm = BLK_PERM_WRITE;
296         *nshared = BLK_PERM_ALL & ~BLK_PERM_WRITE;
297     } else {
298         *nperm = 0;
299         *nshared = BLK_PERM_ALL;
300     }
301 }
302 
303 static BlockDriver bdrv_write_to_selected = {
304     .format_name = "write-to-selected",
305     .instance_size = sizeof(BDRVWriteToSelectedState),
306     .bdrv_child_perm = write_to_selected_perms,
307 };
308 
309 
310 /*
311  * The following test shows that topological-sort order is required for
312  * permission update, simple DFS is not enough.
313  *
314  * Consider the block driver (write-to-selected) which has two children: one is
315  * selected so we have exclusive write access to it and for the other one we
316  * don't need any specific permissions.
317  *
318  * And, these two children has a common base child, like this:
319  *   (additional "top" on top is used in test just because the only public
320  *    function to update permission should get a specific child to update.
321  *    Making bdrv_refresh_perms() public just for this test isn't worth it)
322  *
323  * ┌─────┐     ┌───────────────────┐     ┌─────┐
324  * │ fl2 │ ◀── │ write-to-selected │ ◀── │ top │
325  * └─────┘     └───────────────────┘     └─────┘
326  *   │           │
327  *   │           │ w
328  *   │           ▼
329  *   │         ┌──────┐
330  *   │         │ fl1  │
331  *   │         └──────┘
332  *   │           │
333  *   │           │ w
334  *   │           ▼
335  *   │         ┌──────┐
336  *   └───────▶ │ base │
337  *             └──────┘
338  *
339  * So, exclusive write is propagated.
340  *
341  * Assume, we want to select fl2 instead of fl1.
342  * So, we set some option for write-to-selected driver and do permission update.
343  *
344  * With simple DFS, if permission update goes first through
345  * write-to-selected -> fl1 -> base branch it will succeed: it firstly drop
346  * exclusive write permissions and than apply them for another BdrvChildren.
347  * But if permission update goes first through write-to-selected -> fl2 -> base
348  * branch it will fail, as when we try to update fl2->base child, old not yet
349  * updated fl1->base child will be in conflict.
350  *
351  * With topological-sort order we always update parents before children, so fl1
352  * and fl2 are both updated when we update base and there is no conflict.
353  */
test_parallel_perm_update(void)354 static void test_parallel_perm_update(void)
355 {
356     BlockDriverState *top = no_perm_node("top");
357     BlockDriverState *ws =
358             bdrv_new_open_driver(&bdrv_write_to_selected, "ws", BDRV_O_RDWR,
359                                  &error_abort);
360     BDRVWriteToSelectedState *s = ws->opaque;
361     BlockDriverState *base = no_perm_node("base");
362     BlockDriverState *fl1 = pass_through_node("fl1");
363     BlockDriverState *fl2 = pass_through_node("fl2");
364     BdrvChild *c_fl1, *c_fl2;
365 
366     /*
367      * bdrv_attach_child() eats child bs reference, so we need two @base
368      * references for two filters:
369      */
370     bdrv_ref(base);
371 
372     bdrv_drain_all_begin();
373     bdrv_graph_wrlock();
374     bdrv_attach_child(top, ws, "file", &child_of_bds, BDRV_CHILD_DATA,
375                       &error_abort);
376     c_fl1 = bdrv_attach_child(ws, fl1, "first", &child_of_bds,
377                               BDRV_CHILD_DATA, &error_abort);
378     c_fl2 = bdrv_attach_child(ws, fl2, "second", &child_of_bds,
379                               BDRV_CHILD_DATA, &error_abort);
380     bdrv_attach_child(fl1, base, "backing", &child_of_bds,
381                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
382                       &error_abort);
383     bdrv_attach_child(fl2, base, "backing", &child_of_bds,
384                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
385                       &error_abort);
386     bdrv_graph_wrunlock();
387     bdrv_drain_all_end();
388 
389     /* Select fl1 as first child to be active */
390     s->selected = c_fl1;
391 
392     bdrv_graph_rdlock_main_loop();
393 
394     bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
395 
396     assert(c_fl1->perm & BLK_PERM_WRITE);
397     assert(!(c_fl2->perm & BLK_PERM_WRITE));
398 
399     /* Now, try to switch active child and update permissions */
400     s->selected = c_fl2;
401     bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
402 
403     assert(c_fl2->perm & BLK_PERM_WRITE);
404     assert(!(c_fl1->perm & BLK_PERM_WRITE));
405 
406     /* Switch once more, to not care about real child order in the list */
407     s->selected = c_fl1;
408     bdrv_child_refresh_perms(top, top->children.lh_first, &error_abort);
409 
410     assert(c_fl1->perm & BLK_PERM_WRITE);
411     assert(!(c_fl2->perm & BLK_PERM_WRITE));
412 
413     bdrv_graph_rdunlock_main_loop();
414     bdrv_unref(top);
415 }
416 
417 /*
418  * It's possible that filter required permissions allows to insert it to backing
419  * chain, like:
420  *
421  *  1.  [top] -> [filter] -> [base]
422  *
423  * but doesn't allow to add it as a branch:
424  *
425  *  2.  [filter] --\
426  *                 v
427  *      [top] -> [base]
428  *
429  * So, inserting such filter should do all graph modifications and only then
430  * update permissions. If we try to go through intermediate state [2] and update
431  * permissions on it we'll fail.
432  *
433  * Let's check that bdrv_append() can append such a filter.
434  */
test_append_greedy_filter(void)435 static void test_append_greedy_filter(void)
436 {
437     BlockDriverState *top = exclusive_writer_node("top");
438     BlockDriverState *base = no_perm_node("base");
439     BlockDriverState *fl = exclusive_writer_node("fl1");
440 
441     bdrv_drain_all_begin();
442     bdrv_graph_wrlock();
443     bdrv_attach_child(top, base, "backing", &child_of_bds,
444                       BDRV_CHILD_FILTERED | BDRV_CHILD_PRIMARY,
445                       &error_abort);
446     bdrv_graph_wrunlock();
447     bdrv_drain_all_end();
448 
449     bdrv_append(fl, base, &error_abort);
450     bdrv_unref(fl);
451     bdrv_unref(top);
452 }
453 
main(int argc,char * argv[])454 int main(int argc, char *argv[])
455 {
456     bdrv_init();
457     qemu_init_main_loop(&error_abort);
458 
459     g_test_init(&argc, &argv, NULL);
460 
461     g_test_add_func("/bdrv-graph-mod/update-perm-tree", test_update_perm_tree);
462     g_test_add_func("/bdrv-graph-mod/should-update-child",
463                     test_should_update_child);
464     g_test_add_func("/bdrv-graph-mod/parallel-perm-update",
465                     test_parallel_perm_update);
466     g_test_add_func("/bdrv-graph-mod/parallel-exclusive-write",
467                     test_parallel_exclusive_write);
468     g_test_add_func("/bdrv-graph-mod/append-greedy-filter",
469                     test_append_greedy_filter);
470 
471     return g_test_run();
472 }
473