xref: /qemu/tests/unit/test-bdrv-graph-mod.c (revision 29b62a10)
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 }
437