xref: /qemu/tests/unit/test-bdrv-graph-mod.c (revision b21e2380)
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     .bdrv_child_perm = bdrv_default_perms,
30 };
31 
32 static 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 
42 static BlockDriver bdrv_no_perm = {
43     .format_name = "no-perm",
44     .supports_backing = true,
45     .bdrv_child_perm = no_perm_default_perms,
46 };
47 
48 static 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 
58 static BlockDriver bdrv_exclusive_writer = {
59     .format_name = "exclusive-writer",
60     .bdrv_child_perm = exclusive_write_perms,
61 };
62 
63 static 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 
68 static 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 
74 static 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  */
124 static 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  */
190 static 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  */
218 static 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 
244 static 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 
259 static 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  */
306 static 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  */
376 static 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 
390 int 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