Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3 : : */
4 : :
5 : : #include "spdk/stdinc.h"
6 : : #include "spdk_internal/cunit.h"
7 : : #include "spdk/string.h"
8 : : #include "spdk/init.h"
9 : :
10 : : #include "common/lib/ut_multithread.c"
11 : :
12 : : #include "bdev/bdev.c"
13 : : #include "lvol/lvol.c"
14 : : #include "bdev/malloc/bdev_malloc.c"
15 : : #include "bdev/lvol/vbdev_lvol.c"
16 : : #include "accel/accel_sw.c"
17 : : #include "bdev/part.c"
18 : : #include "blob/blobstore.h"
19 : : #include "bdev/aio/bdev_aio.h"
20 : :
21 : : #include "unit/lib/json_mock.c"
22 : :
23 : : #ifdef SPDK_CONFIG_PMDK
24 : : DEFINE_STUB(pmem_msync, int, (const void *addr, size_t len), 0);
25 : : DEFINE_STUB(pmem_memcpy_persist, void *, (void *pmemdest, const void *src, size_t len), NULL);
26 : : DEFINE_STUB(pmem_is_pmem, int, (const void *addr, size_t len), 0);
27 : : DEFINE_STUB(pmem_memset_persist, void *, (void *pmemdest, int c, size_t len), NULL);
28 : : #endif
29 : :
30 : : char g_testdir[PATH_MAX];
31 : :
32 : : static void
33 : 1 : set_testdir(const char *path)
34 : : {
35 : : char *tmp;
36 : :
37 : 1 : tmp = realpath(path, NULL);
38 [ + - - + ]: 1 : snprintf(g_testdir, sizeof(g_testdir), "%s", tmp ? dirname(tmp) : ".");
39 : 1 : free(tmp);
40 : 1 : }
41 : :
42 : : static int
43 : 3 : make_test_file(size_t size, char *path, size_t len, const char *name)
44 : : {
45 : : int fd;
46 : : int rc;
47 : :
48 : 3 : CU_ASSERT(len <= INT32_MAX);
49 [ - + ]: 3 : if (snprintf(path, len, "%s/%s", g_testdir, name) >= (int)len) {
50 : 0 : return -ENAMETOOLONG;
51 : : }
52 [ - + ]: 3 : unlink(path);
53 [ - + ]: 3 : fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
54 [ - + ]: 3 : if (fd < 0) {
55 [ # # # # ]: 0 : return -errno;
56 : : }
57 : 3 : rc = ftruncate(fd, size);
58 [ - + ]: 3 : if (rc != 0) {
59 [ # # # # ]: 0 : rc = -errno;
60 [ # # ]: 0 : unlink(path);
61 : 0 : }
62 : 3 : close(fd);
63 : 3 : return rc;
64 : 0 : }
65 : :
66 : : static void
67 : 8 : unregister_cb(void *ctx, int bdeverrno)
68 : : {
69 : 8 : int *rc = ctx;
70 : :
71 [ + - ]: 8 : if (rc != NULL) {
72 [ # # ]: 8 : *rc = bdeverrno;
73 : 0 : }
74 : 8 : }
75 : :
76 : : struct op_with_handle_data {
77 : : union {
78 : : struct spdk_lvol_store *lvs;
79 : : struct spdk_lvol *lvol;
80 : : } u;
81 : : int lvserrno;
82 : : };
83 : :
84 : : static struct op_with_handle_data *
85 : 10 : clear_owh(struct op_with_handle_data *owh)
86 : : {
87 [ - + ]: 10 : memset(owh, 0, sizeof(*owh));
88 [ # # # # ]: 10 : owh->lvserrno = 0xbad;
89 : :
90 : 10 : return owh;
91 : : }
92 : :
93 : : /* spdk_poll_threads() doesn't have visibility into uncompleted aio operations. */
94 : : static void
95 : 23 : poll_error_updated(int *error)
96 : : {
97 [ + + # # ]: 3298 : while (*error == 0xbad) {
98 : 3275 : poll_threads();
99 : : }
100 : 23 : }
101 : :
102 : : static void
103 : 4 : lvs_op_with_handle_cb(void *cb_arg, struct spdk_lvol_store *lvs, int lvserrno)
104 : : {
105 : 4 : struct op_with_handle_data *data = cb_arg;
106 : :
107 [ # # # # : 4 : data->u.lvs = lvs;
# # ]
108 [ # # # # ]: 4 : data->lvserrno = lvserrno;
109 : 4 : }
110 : :
111 : : static void
112 : 6 : lvol_op_with_handle_cb(void *cb_arg, struct spdk_lvol *lvol, int lvserrno)
113 : : {
114 : 6 : struct op_with_handle_data *data = cb_arg;
115 : :
116 [ # # # # : 6 : data->u.lvol = lvol;
# # ]
117 [ # # # # ]: 6 : data->lvserrno = lvserrno;
118 : 6 : }
119 : :
120 : : static void
121 : 82 : lvol_op_complete_cb(void *cb_arg, int lvolerrno)
122 : : {
123 : 82 : int *err = cb_arg;
124 : :
125 [ # # ]: 82 : *err = lvolerrno;
126 : 82 : }
127 : :
128 : : static void
129 : 0 : ut_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *event_ctx)
130 : : {
131 : 0 : }
132 : :
133 : : static void
134 : 171 : io_done(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
135 : : {
136 : 171 : int *err = cb_arg;
137 : :
138 : 171 : spdk_bdev_free_io(bdev_io);
139 [ - + # # : 171 : SPDK_CU_ASSERT_FATAL(success);
# # ]
140 : :
141 [ # # ]: 171 : *err = 0;
142 : 171 : }
143 : :
144 : : static void
145 : 203 : prepare_block(char *buf, size_t bufsz, const char *uuid_str, uint64_t block)
146 : : {
147 [ - + ]: 203 : memset(buf, 0, bufsz);
148 [ - + ]: 203 : snprintf(buf, bufsz, "%s %8" PRIu64, uuid_str, block);
149 : 203 : }
150 : :
151 : : static void
152 : 3 : scribble(struct spdk_bdev_desc *desc, uint64_t start, uint64_t count)
153 : 3 : {
154 [ # # # # ]: 3 : struct spdk_bdev *bdev = desc->bdev;
155 [ # # # # : 3 : const uint32_t blocklen = desc->bdev->blocklen;
# # # # ]
156 : 3 : struct spdk_io_channel *ch = spdk_bdev_get_io_channel(desc);
157 : 3 : char uuid_str[SPDK_UUID_STRING_LEN];
158 [ - + - + ]: 3 : char buf[count][blocklen];
159 : 3 : int err = 0xbad;
160 : : uint64_t i;
161 : :
162 [ + - + - : 3 : SPDK_CU_ASSERT_FATAL(count > 0 && count < INT32_MAX);
- + ]
163 [ - + # # ]: 3 : SPDK_CU_ASSERT_FATAL(ch != NULL);
164 : :
165 [ # # ]: 3 : spdk_uuid_fmt_lower(uuid_str, sizeof(uuid_str), &bdev->uuid);
166 : :
167 [ + + ]: 38 : for (i = 0; i < count; i++) {
168 [ # # # # : 35 : prepare_block(buf[i], sizeof(buf[i]), uuid_str, start + i);
# # # # ]
169 : 0 : }
170 : :
171 : 3 : spdk_bdev_write(desc, ch, buf, start * blocklen, sizeof(buf), io_done, &err);
172 : 3 : poll_threads();
173 [ - + # # ]: 3 : SPDK_CU_ASSERT_FATAL(err == 0);
174 : 3 : spdk_put_io_channel(ch);
175 : 3 : poll_threads();
176 : 3 : }
177 : :
178 : : #define verify(desc, bdev, start, count) _verify(desc, bdev, start, count, __FILE__, __LINE__)
179 : :
180 : : static bool
181 : 12 : _verify(struct spdk_bdev_desc *desc, struct spdk_bdev *bdev, uint64_t start, uint64_t count,
182 : : const char *file, int line)
183 : 12 : {
184 : 12 : struct spdk_io_channel *ch = spdk_bdev_get_io_channel(desc);
185 [ # # # # : 12 : const uint32_t blocklen = desc->bdev->blocklen;
# # # # ]
186 : 12 : char uuid_str[SPDK_UUID_STRING_LEN];
187 [ - + ]: 12 : char buf[blocklen];
188 [ - + ]: 12 : char expect[blocklen];
189 : 12 : int err = 0xbad;
190 : 12 : bool ret = true;
191 : : uint64_t i;
192 : :
193 [ + - + - : 12 : SPDK_CU_ASSERT_FATAL(count > 0 && count < INT32_MAX);
- + ]
194 [ - + # # ]: 12 : SPDK_CU_ASSERT_FATAL(ch != NULL);
195 : :
196 [ # # ]: 12 : spdk_uuid_fmt_lower(uuid_str, sizeof(uuid_str), &bdev->uuid);
197 : :
198 [ + + ]: 180 : for (i = 0; i < count; i++) {
199 : 168 : uint64_t block = start + i;
200 : :
201 : 168 : spdk_bdev_read(desc, ch, buf, block * blocklen, sizeof(buf), io_done, &err);
202 : 168 : poll_threads();
203 [ - + # # ]: 168 : SPDK_CU_ASSERT_FATAL(err == 0);
204 : 168 : prepare_block(expect, sizeof(expect), uuid_str, block);
205 [ - + - + : 168 : if (memcmp(expect, buf, blocklen) != 0) {
- + ]
206 : 0 : printf("%s:%d: ERROR: expected '%s' got '%s'\n", file, line,
207 : : expect, buf);
208 : 0 : ret = false;
209 : 0 : }
210 : 0 : }
211 : :
212 : 12 : spdk_put_io_channel(ch);
213 : 12 : poll_threads();
214 : :
215 [ # # ]: 12 : return ret;
216 : 0 : }
217 : :
218 : : static bool
219 : 12 : cluster_is_allocated(struct spdk_blob *blob, uint32_t cluster)
220 : : {
221 [ # # # # : 12 : return bs_io_unit_is_allocated(blob, cluster * blob->bs->pages_per_cluster);
# # # # ]
222 : : }
223 : :
224 : : static void
225 : 1 : esnap_clone_io(void)
226 : : {
227 : 1 : struct spdk_lvol_store *lvs = NULL;
228 : 1 : struct spdk_bdev *bs_bdev = NULL;
229 : 1 : struct spdk_bdev *esnap_bdev = NULL;
230 : 1 : struct spdk_bdev *lvol_bdev = NULL;
231 : 1 : struct spdk_bdev_desc *esnap_desc = NULL;
232 : 1 : struct spdk_bdev_desc *lvol_desc = NULL;
233 : 1 : const char bs_malloc_uuid[SPDK_UUID_STRING_LEN] = "11110049-cf29-4681-ab4b-5dd16de6cd81";
234 : 1 : const char esnap_uuid[SPDK_UUID_STRING_LEN] = "222251be-1ece-434d-8513-6944d5c93a53";
235 : 1 : struct malloc_bdev_opts malloc_opts = { 0 };
236 : 1 : const uint32_t bs_size_bytes = 10 * 1024 * 1024;
237 : 1 : const uint32_t bs_block_size = 4096;
238 : 1 : const uint32_t cluster_size = 32 * 1024;
239 [ - + ]: 1 : const uint32_t blocks_per_cluster = cluster_size / bs_block_size;
240 : 1 : const uint32_t esnap_size_bytes = 4 * cluster_size;
241 : 1 : struct op_with_handle_data owh_data = { 0 };
242 : : struct lvol_bdev *_lvol_bdev;
243 : : struct spdk_blob *blob;
244 : : int rc;
245 : :
246 : 1 : g_bdev_opts.bdev_auto_examine = false;
247 : :
248 : : /* Create device for lvstore */
249 : 1 : spdk_uuid_parse(&malloc_opts.uuid, bs_malloc_uuid);
250 : 1 : malloc_opts.name = "bs_malloc";
251 [ - + ]: 1 : malloc_opts.num_blocks = bs_size_bytes / bs_block_size;
252 [ # # ]: 1 : malloc_opts.block_size = bs_block_size;
253 : 1 : rc = create_malloc_disk(&bs_bdev, &malloc_opts);
254 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
255 : :
256 : : /* Create lvstore */
257 : 1 : rc = vbdev_lvs_create("bs_malloc", "lvs1", cluster_size, 0, 0,
258 : 1 : lvs_op_with_handle_cb, clear_owh(&owh_data));
259 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
260 : 1 : poll_error_updated(&owh_data.lvserrno);
261 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
262 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvs != NULL);
263 : 1 : lvs = owh_data.u.lvs;
264 : :
265 : : /* Create esnap device */
266 [ - + ]: 1 : memset(&malloc_opts, 0, sizeof(malloc_opts));
267 : 1 : spdk_uuid_parse(&malloc_opts.uuid, esnap_uuid);
268 : 1 : malloc_opts.name = "esnap_malloc";
269 [ - + ]: 1 : malloc_opts.num_blocks = esnap_size_bytes / bs_block_size;
270 [ # # ]: 1 : malloc_opts.block_size = bs_block_size;
271 : 1 : rc = create_malloc_disk(&esnap_bdev, &malloc_opts);
272 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
273 : :
274 : : /* Fill esnap device with pattern */
275 : 1 : rc = spdk_bdev_open_ext(esnap_uuid, true, ut_event_cb, NULL, &esnap_desc);
276 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
277 [ # # # # ]: 1 : scribble(esnap_desc, 0, esnap_bdev->blockcnt);
278 : :
279 : : /* Reopen the external snapshot read-only for verification later */
280 : 1 : spdk_bdev_close(esnap_desc);
281 : 1 : poll_threads();
282 : 1 : rc = spdk_bdev_open_ext(esnap_uuid, false, ut_event_cb, NULL, &esnap_desc);
283 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
284 : :
285 : : /* Create esnap clone */
286 : 1 : vbdev_lvol_create_bdev_clone(esnap_uuid, lvs, "clone1",
287 : 1 : lvol_op_with_handle_cb, clear_owh(&owh_data));
288 : 1 : poll_error_updated(&owh_data.lvserrno);
289 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
290 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvol != NULL);
291 : :
292 : : /* Open the esnap clone */
293 : 1 : rc = spdk_bdev_open_ext("lvs1/clone1", true, ut_event_cb, NULL, &lvol_desc);
294 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
295 [ # # # # ]: 1 : lvol_bdev = lvol_desc->bdev;
296 : 1 : _lvol_bdev = (struct lvol_bdev *)lvol_bdev;
297 [ # # # # : 1 : blob = _lvol_bdev->lvol->blob;
# # # # ]
298 [ # # # # : 1 : CU_ASSERT(blob->active.num_clusters == 4);
# # ]
299 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 0));
300 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 1));
301 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 2));
302 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 3));
303 : :
304 : : /* Be sure the esnap and the clone see the same content. */
305 [ # # # # ]: 1 : CU_ASSERT(verify(esnap_desc, esnap_bdev, 0, esnap_bdev->blockcnt));
306 [ # # # # ]: 1 : CU_ASSERT(verify(lvol_desc, esnap_bdev, 0, esnap_bdev->blockcnt));
307 : :
308 : : /* Overwrite the second block of the first cluster then verify the whole first cluster */
309 : 1 : scribble(lvol_desc, 1, 1);
310 : 1 : CU_ASSERT(cluster_is_allocated(blob, 0));
311 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 1));
312 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 2));
313 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 3));
314 : 1 : CU_ASSERT(verify(lvol_desc, esnap_bdev, 0, 1));
315 : 1 : CU_ASSERT(verify(lvol_desc, lvol_bdev, 1, 1));
316 : 1 : CU_ASSERT(verify(lvol_desc, esnap_bdev, 2, blocks_per_cluster - 2));
317 : : /* And be sure no writes made it to the external snapshot */
318 [ # # # # ]: 1 : CU_ASSERT(verify(esnap_desc, esnap_bdev, 0, esnap_bdev->blockcnt));
319 : :
320 : : /* Overwrite the two blocks that span the end of the first cluster and the start of the
321 : : * second cluster
322 : : */
323 : 1 : scribble(lvol_desc, blocks_per_cluster - 1, 2);
324 : : /* The first part of the first cluster was written previously - it should be the same. */
325 : 1 : CU_ASSERT(cluster_is_allocated(blob, 0));
326 : 1 : CU_ASSERT(cluster_is_allocated(blob, 1));
327 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 2));
328 : 1 : CU_ASSERT(!cluster_is_allocated(blob, 3));
329 : 1 : CU_ASSERT(verify(lvol_desc, esnap_bdev, 0, 1));
330 : 1 : CU_ASSERT(verify(lvol_desc, lvol_bdev, 1, 1));
331 : 1 : CU_ASSERT(verify(lvol_desc, esnap_bdev, 2, blocks_per_cluster - 2 - 1));
332 : : /* Check the newly written area spanning the first two clusters. */
333 : 1 : CU_ASSERT(verify(lvol_desc, lvol_bdev, blocks_per_cluster - 1, 2));
334 : : /* The rest should not have changed. */
335 [ # # # # ]: 1 : CU_ASSERT(verify(lvol_desc, esnap_bdev, blocks_per_cluster + 1,
336 : : esnap_bdev->blockcnt - blocks_per_cluster - 1));
337 : : /* And be sure no writes made it to the external snapshot */
338 [ # # # # ]: 1 : CU_ASSERT(verify(esnap_desc, esnap_bdev, 0, esnap_bdev->blockcnt));
339 : :
340 : : /* Clean up */
341 : 1 : bdev_close(lvol_bdev, lvol_desc);
342 : 1 : bdev_close(esnap_bdev, esnap_desc);
343 : 1 : delete_malloc_disk("esnap_malloc", NULL, 0);
344 : : /* This triggers spdk_lvs_unload() */
345 : 1 : delete_malloc_disk("bs_malloc", NULL, 0);
346 : 1 : poll_threads();
347 : 1 : }
348 : :
349 : : static void
350 : 2 : esnap_wait_for_examine(void *ctx)
351 : : {
352 : 2 : int *flag = ctx;
353 : :
354 [ # # ]: 2 : *flag = 0;
355 : 2 : }
356 : :
357 : : static void
358 : 1 : esnap_hotplug(void)
359 : : {
360 : 1 : const char *uuid_esnap = "22218fb6-6743-483d-88b1-de643dc7c0bc";
361 : 1 : struct malloc_bdev_opts malloc_opts = { 0 };
362 : 1 : const uint32_t bs_size_bytes = 10 * 1024 * 1024;
363 : 1 : const uint32_t bs_block_size = 4096;
364 : 1 : const uint32_t cluster_size = 32 * 1024;
365 : 1 : const uint32_t esnap_size_bytes = 2 * cluster_size;
366 : 1 : struct op_with_handle_data owh_data = { 0 };
367 : 1 : struct spdk_bdev *malloc_bdev = NULL, *bdev;
368 : : struct spdk_lvol_store *lvs;
369 : : struct spdk_lvol *lvol;
370 : 1 : struct spdk_uuid uuid = { 0 };
371 : 1 : char aiopath[PATH_MAX];
372 : 1 : int rc, rc2;
373 : :
374 : 1 : g_bdev_opts.bdev_auto_examine = true;
375 : :
376 : : /* Create aio device to hold the lvstore. */
377 : 1 : rc = make_test_file(bs_size_bytes, aiopath, sizeof(aiopath), "esnap_hotplug.aio");
378 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
379 : 1 : rc = create_aio_bdev("aio1", aiopath, bs_block_size, false, false, &uuid);
380 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
381 : 1 : poll_threads();
382 : :
383 : 1 : rc = vbdev_lvs_create("aio1", "lvs1", cluster_size, 0, 0,
384 : 1 : lvs_op_with_handle_cb, clear_owh(&owh_data));
385 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
386 : 1 : poll_error_updated(&owh_data.lvserrno);
387 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
388 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvs != NULL);
389 : 1 : lvs = owh_data.u.lvs;
390 : :
391 : : /* Create esnap device */
392 : 1 : spdk_uuid_parse(&malloc_opts.uuid, uuid_esnap);
393 : 1 : malloc_opts.name = "esnap_malloc";
394 [ - + ]: 1 : malloc_opts.num_blocks = esnap_size_bytes / bs_block_size;
395 [ # # ]: 1 : malloc_opts.block_size = bs_block_size;
396 : 1 : rc = create_malloc_disk(&malloc_bdev, &malloc_opts);
397 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
398 : :
399 : : /* Create esnap clone */
400 : 1 : vbdev_lvol_create_bdev_clone(uuid_esnap, lvs, "clone1",
401 : 1 : lvol_op_with_handle_cb, clear_owh(&owh_data));
402 : 1 : poll_error_updated(&owh_data.lvserrno);
403 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
404 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvol != NULL);
405 : :
406 : : /* Verify that lvol bdev exists */
407 : 1 : bdev = spdk_bdev_get_by_name("lvs1/clone1");
408 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(bdev != NULL);
409 : :
410 : : /* Unload the lvstore and verify the bdev is gone. */
411 : 1 : rc = rc2 = 0xbad;
412 : 1 : bdev_aio_delete("aio1", unregister_cb, &rc);
413 : 1 : CU_ASSERT(spdk_bdev_get_by_name(uuid_esnap) != NULL)
414 [ # # # # ]: 1 : delete_malloc_disk(malloc_bdev->name, unregister_cb, &rc2);
415 : 1 : malloc_bdev = NULL;
416 : 1 : poll_error_updated(&rc);
417 : 1 : poll_error_updated(&rc2);
418 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
419 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc2 == 0);
420 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(spdk_bdev_get_by_name("lvs1/clone1") == NULL);
421 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(spdk_bdev_get_by_name(uuid_esnap) == NULL);
422 : :
423 : : /* Trigger the reload of the lvstore */
424 : 1 : rc = create_aio_bdev("aio1", aiopath, bs_block_size, false, false, &uuid);
425 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
426 : 1 : rc = 0xbad;
427 : 1 : spdk_bdev_wait_for_examine(esnap_wait_for_examine, &rc);
428 : 1 : poll_error_updated(&rc);
429 : :
430 : : /* Verify the lvol is loaded without creating a bdev. */
431 : 1 : lvol = spdk_lvol_get_by_names("lvs1", "clone1");
432 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/clone1") == NULL)
433 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(lvol != NULL);
434 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(lvol->degraded_set != NULL);
# # # # ]
435 : :
436 : : /* Create the esnap device and verify that the bdev is created. */
437 : 1 : rc = create_malloc_disk(&malloc_bdev, &malloc_opts);
438 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
439 : 1 : poll_threads();
440 : 1 : CU_ASSERT(malloc_bdev != NULL);
441 [ # # # # ]: 1 : CU_ASSERT(lvol->degraded_set == NULL);
442 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/clone1") != NULL);
443 : :
444 : : /* Clean up */
445 : 1 : rc = rc2 = 0xbad;
446 : 1 : bdev_aio_delete("aio1", unregister_cb, &rc);
447 : 1 : poll_error_updated(&rc);
448 : 1 : CU_ASSERT(rc == 0);
449 [ + - ]: 1 : if (malloc_bdev != NULL) {
450 [ # # # # ]: 1 : delete_malloc_disk(malloc_bdev->name, unregister_cb, &rc2);
451 : 1 : poll_threads();
452 : 1 : CU_ASSERT(rc2 == 0);
453 : 0 : }
454 [ - + ]: 1 : rc = unlink(aiopath);
455 : 1 : CU_ASSERT(rc == 0);
456 : 1 : }
457 : :
458 : : static void
459 : 1 : esnap_remove_degraded(void)
460 : : {
461 : 1 : const char *uuid_esnap = "33358eb9-3dcf-4275-b089-0becc126fc3d";
462 : 1 : struct malloc_bdev_opts malloc_opts = { 0 };
463 : 1 : const uint32_t bs_size_bytes = 10 * 1024 * 1024;
464 : 1 : const uint32_t bs_block_size = 4096;
465 : 1 : const uint32_t cluster_size = 32 * 1024;
466 : 1 : const uint32_t esnap_size_bytes = 2 * cluster_size;
467 : 1 : struct op_with_handle_data owh_data = { 0 };
468 : : struct spdk_lvol_store *lvs;
469 : 1 : struct spdk_bdev *malloc_bdev = NULL;
470 : 1 : struct spdk_uuid uuid = { 0 };
471 : : struct spdk_lvol *vol1, *vol2, *vol3;
472 : 1 : char aiopath[PATH_MAX];
473 : 1 : int rc, rc2;
474 : :
475 : 1 : g_bdev_opts.bdev_auto_examine = true;
476 : :
477 : : /* Create aio device to hold the lvstore. */
478 : 1 : rc = make_test_file(bs_size_bytes, aiopath, sizeof(aiopath), "remove_degraded.aio");
479 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
480 : 1 : rc = create_aio_bdev("aio1", aiopath, bs_block_size, false, false, &uuid);
481 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
482 : 1 : poll_threads();
483 : :
484 : 1 : rc = vbdev_lvs_create("aio1", "lvs1", cluster_size, 0, 0,
485 : 1 : lvs_op_with_handle_cb, clear_owh(&owh_data));
486 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
487 : 1 : poll_error_updated(&owh_data.lvserrno);
488 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
489 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvs != NULL);
490 : 1 : lvs = owh_data.u.lvs;
491 : :
492 : : /* Create esnap device */
493 : 1 : spdk_uuid_parse(&malloc_opts.uuid, uuid_esnap);
494 : 1 : malloc_opts.name = "esnap";
495 [ - + ]: 1 : malloc_opts.num_blocks = esnap_size_bytes / bs_block_size;
496 [ # # ]: 1 : malloc_opts.block_size = bs_block_size;
497 : 1 : rc = create_malloc_disk(&malloc_bdev, &malloc_opts);
498 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
499 : :
500 : : /* Create a snapshot of vol1.
501 : : * State:
502 : : * esnap <-- vol1
503 : : */
504 : 1 : vbdev_lvol_create_bdev_clone(uuid_esnap, lvs, "vol1",
505 : 1 : lvol_op_with_handle_cb, clear_owh(&owh_data));
506 : 1 : poll_error_updated(&owh_data.lvserrno);
507 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
508 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvol != NULL);
509 : 1 : vol1 = owh_data.u.lvol;
510 : :
511 : : /* Create a snapshot of vol1.
512 : : * State:
513 : : * esnap <-- vol2 <-- vol1
514 : : */
515 : 1 : vbdev_lvol_create_snapshot(vol1, "vol2", lvol_op_with_handle_cb, clear_owh(&owh_data));
516 : 1 : poll_error_updated(&owh_data.lvserrno);
517 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
518 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvol != NULL);
519 : 1 : vol2 = owh_data.u.lvol;
520 : :
521 : : /* Create a clone of vol2.
522 : : * State:
523 : : * esnap <-- vol2 <-- vol1
524 : : * `---- vol3
525 : : */
526 : 1 : vbdev_lvol_create_clone(vol2, "vol3", lvol_op_with_handle_cb, clear_owh(&owh_data));
527 : 1 : poll_error_updated(&owh_data.lvserrno);
528 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
529 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvol != NULL);
530 : 1 : vol3 = owh_data.u.lvol;
531 : :
532 : : /* Unload the lvstore and delete esnap */
533 : 1 : rc = rc2 = 0xbad;
534 : 1 : bdev_aio_delete("aio1", unregister_cb, &rc);
535 : 1 : CU_ASSERT(spdk_bdev_get_by_name(uuid_esnap) != NULL)
536 [ # # # # ]: 1 : delete_malloc_disk(malloc_bdev->name, unregister_cb, &rc2);
537 : 1 : malloc_bdev = NULL;
538 : 1 : poll_error_updated(&rc);
539 : 1 : poll_error_updated(&rc2);
540 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
541 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc2 == 0);
542 : :
543 : : /* Trigger the reload of the lvstore.
544 : : * State:
545 : : * (missing) <-- vol2 <-- vol1
546 : : * `---- vol3
547 : : */
548 : 1 : rc = create_aio_bdev("aio1", aiopath, bs_block_size, false, false, &uuid);
549 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
550 : 1 : rc = 0xbad;
551 : 1 : spdk_bdev_wait_for_examine(esnap_wait_for_examine, &rc);
552 : 1 : poll_error_updated(&rc);
553 : :
554 : : /* Verify vol1 is as described in diagram above */
555 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol1") == NULL);
556 : 1 : vol1 = spdk_lvol_get_by_names("lvs1", "vol1");
557 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol1 != NULL);
558 [ # # # # ]: 1 : lvs = vol1->lvol_store;
559 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_clone(vol1->blob));
560 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_esnap_clone(vol1->blob));
561 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_snapshot(vol1->blob));
562 [ # # # # ]: 1 : CU_ASSERT(vol1->degraded_set == NULL);
563 : :
564 : : /* Verify vol2 is as described in diagram above */
565 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol2") == NULL);
566 : 1 : vol2 = spdk_lvol_get_by_names("lvs1", "vol2");
567 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol2 != NULL);
568 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_clone(vol2->blob));
569 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_esnap_clone(vol2->blob));
570 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_snapshot(vol2->blob));
571 [ # # # # : 1 : CU_ASSERT(RB_MIN(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree) == vol2->degraded_set);
# # ]
572 [ # # # # : 1 : CU_ASSERT(TAILQ_FIRST(&vol2->degraded_set->lvols) == vol2);
# # # # #
# ]
573 : :
574 : : /* Verify vol3 is as described in diagram above */
575 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol3") == NULL);
576 : 1 : vol3 = spdk_lvol_get_by_names("lvs1", "vol3");
577 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol3 != NULL);
578 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_clone(vol3->blob));
579 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_esnap_clone(vol3->blob));
580 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_snapshot(vol3->blob));
581 [ # # # # ]: 1 : CU_ASSERT(vol3->degraded_set == NULL);
582 : :
583 : : /* Try to delete vol2. Should fail because it has multiple clones. */
584 : 1 : rc = 0xbad;
585 : 1 : vbdev_lvol_destroy(vol2, lvol_op_complete_cb, &rc);
586 : 1 : poll_error_updated(&rc);
587 : 1 : CU_ASSERT(rc == -EPERM);
588 : :
589 : : /* Delete vol1
590 : : * New state:
591 : : * (missing) <-- vol2 <-- vol3
592 : : */
593 : 1 : rc = 0xbad;
594 : 1 : vbdev_lvol_destroy(vol1, lvol_op_complete_cb, &rc);
595 : 1 : poll_error_updated(&rc);
596 : 1 : CU_ASSERT(rc == 0);
597 : :
598 : : /* Verify vol1 is gone */
599 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol1") == NULL);
600 : 1 : vol1 = spdk_lvol_get_by_names("lvs1", "vol1");
601 : 1 : CU_ASSERT(vol1 == NULL);
602 : :
603 : : /* Verify vol2 is as described in diagram above */
604 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol2") == NULL);
605 : 1 : vol2 = spdk_lvol_get_by_names("lvs1", "vol2");
606 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol2 != NULL);
607 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_clone(vol2->blob));
608 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_esnap_clone(vol2->blob));
609 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_snapshot(vol2->blob));
610 [ # # # # : 1 : CU_ASSERT(RB_MIN(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree) == vol2->degraded_set);
# # ]
611 [ # # # # : 1 : CU_ASSERT(TAILQ_FIRST(&vol2->degraded_set->lvols) == vol2);
# # # # #
# ]
612 : :
613 : : /* Verify vol3 is as described in diagram above */
614 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol3") == NULL);
615 : 1 : vol3 = spdk_lvol_get_by_names("lvs1", "vol3");
616 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol3 != NULL);
617 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_clone(vol3->blob));
618 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_esnap_clone(vol3->blob));
619 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_snapshot(vol3->blob));
620 [ # # # # ]: 1 : CU_ASSERT(vol3->degraded_set == NULL);
621 : :
622 : : /* Delete vol2
623 : : * New state:
624 : : * (missing) <-- vol3
625 : : */
626 : 1 : rc = 0xbad;
627 : 1 : vbdev_lvol_destroy(vol2, lvol_op_complete_cb, &rc);
628 : 1 : poll_error_updated(&rc);
629 : 1 : CU_ASSERT(rc == 0);
630 : :
631 : : /* Verify vol2 is gone */
632 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol2") == NULL);
633 : 1 : vol2 = spdk_lvol_get_by_names("lvs1", "vol2");
634 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol2 == NULL);
635 : :
636 : : /* Verify vol3 is as described in diagram above */
637 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol3") == NULL);
638 : 1 : vol3 = spdk_lvol_get_by_names("lvs1", "vol3");
639 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol3 != NULL);
640 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_clone(vol3->blob));
641 [ # # # # ]: 1 : CU_ASSERT(spdk_blob_is_esnap_clone(vol3->blob));
642 [ # # # # ]: 1 : CU_ASSERT(!spdk_blob_is_snapshot(vol3->blob));
643 [ # # # # : 1 : CU_ASSERT(RB_MIN(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree) == vol3->degraded_set);
# # ]
644 [ # # # # : 1 : CU_ASSERT(TAILQ_FIRST(&vol3->degraded_set->lvols) == vol3);
# # # # #
# ]
645 : :
646 : : /* Delete vol3
647 : : * New state:
648 : : * (nothing)
649 : : */
650 : 1 : rc = 0xbad;
651 : 1 : vbdev_lvol_destroy(vol3, lvol_op_complete_cb, &rc);
652 : 1 : poll_error_updated(&rc);
653 : 1 : CU_ASSERT(rc == 0);
654 : :
655 : : /* Verify vol3 is gone */
656 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/vol3") == NULL);
657 : 1 : vol3 = spdk_lvol_get_by_names("lvs1", "vol3");
658 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(vol3 == NULL);
659 : :
660 : : /* Nothing depends on the missing bdev, so it is no longer missing. */
661 [ # # # # : 1 : CU_ASSERT(RB_EMPTY(&lvs->degraded_lvol_sets_tree));
# # ]
662 : :
663 : : /* Clean up */
664 : 1 : rc = rc2 = 0xbad;
665 : 1 : bdev_aio_delete("aio1", unregister_cb, &rc);
666 : 1 : poll_error_updated(&rc);
667 : 1 : CU_ASSERT(rc == 0);
668 [ - + ]: 1 : if (malloc_bdev != NULL) {
669 [ # # # # ]: 0 : delete_malloc_disk(malloc_bdev->name, unregister_cb, &rc2);
670 : 0 : poll_threads();
671 : 0 : CU_ASSERT(rc2 == 0);
672 : 0 : }
673 [ - + ]: 1 : rc = unlink(aiopath);
674 : 1 : CU_ASSERT(rc == 0);
675 : 1 : }
676 : :
677 : : static void
678 : 1 : late_delete(void)
679 : : {
680 : 1 : char aiopath[PATH_MAX];
681 : 1 : struct spdk_lvol_store *lvs = NULL;
682 : 1 : const uint32_t bs_size_bytes = 10 * 1024 * 1024;
683 : 1 : const uint32_t bs_block_size = 4096;
684 : 1 : const uint32_t cluster_size = 32 * 1024;
685 : 1 : struct op_with_handle_data owh_data;
686 : : struct lvol_bdev *lvol_bdev;
687 : : struct spdk_lvol *lvol;
688 : 1 : struct spdk_uuid uuid = { 0 };
689 : 1 : int rc, rc2;
690 : : int count;
691 : :
692 : : /* Create device for lvstore. This cannot be a malloc bdev because we need to sneak a
693 : : * deletion in while blob_persist() is in progress.
694 : : */
695 : 1 : rc = make_test_file(bs_size_bytes, aiopath, sizeof(aiopath), "late_delete.aio");
696 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
697 : 1 : rc = create_aio_bdev("aio1", aiopath, bs_block_size, false, false, &uuid);
698 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
699 : 1 : poll_threads();
700 : :
701 : : /* Create lvstore */
702 : 1 : rc = vbdev_lvs_create("aio1", "lvs1", cluster_size, 0, 0,
703 : 1 : lvs_op_with_handle_cb, clear_owh(&owh_data));
704 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(rc == 0);
705 : 1 : poll_error_updated(&owh_data.lvserrno);
706 [ - + # # : 1 : SPDK_CU_ASSERT_FATAL(owh_data.lvserrno == 0);
# # ]
707 [ - + # # ]: 1 : SPDK_CU_ASSERT_FATAL(owh_data.u.lvs != NULL);
708 : 1 : lvs = owh_data.u.lvs;
709 : :
710 : : /* Create an lvol */
711 : 1 : vbdev_lvol_create(lvs, "lvol", 1, true, LVOL_CLEAR_WITH_DEFAULT,
712 : 1 : lvol_op_with_handle_cb, clear_owh(&owh_data));
713 : 1 : poll_error_updated(&owh_data.lvserrno);
714 [ # # ]: 1 : CU_ASSERT(owh_data.lvserrno == 0);
715 : 1 : CU_ASSERT(owh_data.u.lvol != NULL);
716 : :
717 : : /* Verify the lvol can be found both ways */
718 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/lvol") != NULL);
719 : 1 : CU_ASSERT(spdk_lvol_get_by_names("lvs1", "lvol") != NULL);
720 : :
721 : : /*
722 : : * Once the lvolstore deletion starts, it will briefly be possible to look up lvol bdevs and
723 : : * degraded lvols in that lvolstore but any attempt to delete them will fail with -ENODEV.
724 : : */
725 : 1 : rc = 0xbad;
726 : 1 : count = 0;
727 : 1 : vbdev_lvs_destruct(lvs, lvol_op_complete_cb, &rc);
728 : 0 : do {
729 : 169 : lvol_bdev = (struct lvol_bdev *)spdk_bdev_get_by_name("lvs1/lvol");
730 [ + + ]: 169 : if (lvol_bdev != NULL) {
731 : 2 : rc2 = 0xbad;
732 [ # # # # ]: 2 : vbdev_lvol_destroy(lvol_bdev->lvol, lvol_op_complete_cb, &rc2);
733 : 2 : CU_ASSERT(rc2 == -ENODEV);
734 : 0 : }
735 : 169 : lvol = spdk_lvol_get_by_names("lvs1", "lvol");
736 [ + + ]: 169 : if (lvol != NULL) {
737 : : /* If we are here, we are likely reproducing #2998 */
738 : 75 : rc2 = 0xbad;
739 : 75 : vbdev_lvol_destroy(lvol, lvol_op_complete_cb, &rc2);
740 : 75 : CU_ASSERT(rc2 == -ENODEV);
741 [ # # ]: 75 : count++;
742 : 0 : }
743 : :
744 [ # # # # : 169 : spdk_thread_poll(g_ut_threads[0].thread, 0, 0);
# # ]
745 [ + + ]: 169 : } while (rc == 0xbad);
746 : :
747 : : /* Ensure that the test exercised the race */
748 : 1 : CU_ASSERT(count != 0);
749 : :
750 : : /* The lvol is now gone */
751 : 1 : CU_ASSERT(spdk_bdev_get_by_name("lvs1/lvol") == NULL);
752 : 1 : CU_ASSERT(spdk_lvol_get_by_names("lvs1", "lvol") == NULL);
753 : :
754 : : /* Clean up */
755 : 1 : rc = 0xbad;
756 : 1 : bdev_aio_delete("aio1", unregister_cb, &rc);
757 : 1 : poll_error_updated(&rc);
758 : 1 : CU_ASSERT(rc == 0);
759 [ - + ]: 1 : rc = unlink(aiopath);
760 : 1 : CU_ASSERT(rc == 0);
761 : 1 : }
762 : :
763 : : static void
764 : 1 : bdev_init_cb(void *arg, int rc)
765 : : {
766 [ - + # # ]: 1 : assert(rc == 0);
767 : 1 : }
768 : :
769 : : static void
770 : 1 : subsystem_init_cb(int rc, void *ctx)
771 : : {
772 [ - + # # ]: 1 : assert(rc == 0);
773 : 1 : }
774 : :
775 : : static void
776 : 3 : bdev_fini_cb(void *arg)
777 : : {
778 : 3 : }
779 : :
780 : : int
781 : 1 : main(int argc, char **argv)
782 : : {
783 : 1 : CU_pSuite suite = NULL;
784 : : unsigned int num_failures;
785 : : int rc;
786 : :
787 [ # # # # ]: 1 : set_testdir(argv[0]);
788 : :
789 : 1 : CU_initialize_registry();
790 : :
791 : 1 : suite = CU_add_suite("esnap_io", NULL, NULL);
792 : :
793 : 1 : CU_ADD_TEST(suite, esnap_clone_io);
794 : 1 : CU_ADD_TEST(suite, esnap_hotplug);
795 : 1 : CU_ADD_TEST(suite, esnap_remove_degraded);
796 : 1 : CU_ADD_TEST(suite, late_delete);
797 : :
798 : 1 : allocate_threads(2);
799 : 1 : set_thread(0);
800 : :
801 : : /*
802 : : * This is a non-standard way of initializing libraries. It works for this test but
803 : : * shouldn't be used as an example elsewhere, except for maybe other tests.
804 : : */
805 : 1 : spdk_subsystem_init(subsystem_init_cb, NULL);
806 : 1 : rc = spdk_iobuf_initialize();
807 [ - + ]: 1 : if (rc != 0) {
808 : 0 : SPDK_ERRLOG("Failed to initialize iobuf\n");
809 [ # # ]: 0 : abort();
810 : : }
811 : 1 : rc = spdk_accel_initialize();
812 [ - + ]: 1 : if (rc != 0) {
813 : 0 : SPDK_ERRLOG("Failed to initialize accel\n");
814 [ # # ]: 0 : abort();
815 : : }
816 : 1 : spdk_bdev_initialize(bdev_init_cb, NULL);
817 : :
818 : 1 : num_failures = spdk_ut_run_tests(argc, argv, NULL);
819 : 1 : CU_cleanup_registry();
820 : :
821 : 1 : spdk_bdev_finish(bdev_fini_cb, NULL);
822 : 1 : spdk_accel_finish(bdev_fini_cb, NULL);
823 : 1 : spdk_iobuf_finish(bdev_fini_cb, NULL);
824 : :
825 : 1 : free_threads();
826 : :
827 : 1 : return num_failures;
828 : : }
|