xref: /spdk/test/unit/lib/blob/blob_bdev.c/blob_bdev_ut.c (revision 60982c759db49b4f4579f16e3b24df0725ba4b94)
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 
7 #include "spdk_internal/cunit.h"
8 #include "common/lib/ut_multithread.c"
9 
10 static void ut_put_io_channel(struct spdk_io_channel *ch);
11 
12 #define spdk_put_io_channel(ch) ut_put_io_channel(ch);
13 #include "blob/bdev/blob_bdev.c"
14 
15 DEFINE_STUB(spdk_bdev_io_type_supported, bool, (struct spdk_bdev *bdev,
16 		enum spdk_bdev_io_type io_type), false);
17 DEFINE_STUB_V(spdk_bdev_free_io, (struct spdk_bdev_io *g_bdev_io));
18 DEFINE_STUB(spdk_bdev_queue_io_wait, int,
19 	    (struct spdk_bdev *bdev, struct spdk_io_channel *ch,
20 	     struct spdk_bdev_io_wait_entry *entry), 0);
21 DEFINE_STUB(spdk_bdev_read_blocks, int,
22 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf,
23 	     uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
24 	     void *cb_arg), 0);
25 DEFINE_STUB(spdk_bdev_write_blocks, int,
26 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, void *buf,
27 	     uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
28 	     void *cb_arg), 0);
29 DEFINE_STUB(spdk_bdev_readv_blocks, int,
30 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt,
31 	     uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
32 	     void *cb_arg), 0);
33 DEFINE_STUB(spdk_bdev_writev_blocks, int,
34 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt,
35 	     uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
36 	     void *cb_arg), 0);
37 DEFINE_STUB(spdk_bdev_readv_blocks_ext, int,
38 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt,
39 	     uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
40 	     void *cb_arg, struct spdk_bdev_ext_io_opts *opts), 0);
41 DEFINE_STUB(spdk_bdev_writev_blocks_ext, int,
42 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, struct iovec *iov, int iovcnt,
43 	     uint64_t offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
44 	     void *cb_arg, struct spdk_bdev_ext_io_opts *opts), 0);
45 DEFINE_STUB(spdk_bdev_write_zeroes_blocks, int,
46 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, uint64_t offset_blocks,
47 	     uint64_t num_blocks, spdk_bdev_io_completion_cb cb, void *cb_arg), 0);
48 DEFINE_STUB(spdk_bdev_unmap_blocks, int,
49 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, uint64_t offset_blocks,
50 	     uint64_t num_blocks, spdk_bdev_io_completion_cb cb, void *cb_arg), 0);
51 DEFINE_STUB(spdk_bdev_copy_blocks, int,
52 	    (struct spdk_bdev_desc *desc, struct spdk_io_channel *ch, uint64_t dst_offset_blocks,
53 	     uint64_t src_offset_blocks, uint64_t num_blocks, spdk_bdev_io_completion_cb cb,
54 	     void *cb_arg), 0);
55 
56 struct spdk_bdev {
57 	char name[16];
58 	uint64_t blockcnt;
59 	uint32_t blocklen;
60 	uint32_t open_cnt;
61 	enum spdk_bdev_claim_type claim_type;
62 	struct spdk_bdev_module *claim_module;
63 	struct spdk_bdev_desc *claim_desc;
64 };
65 
66 struct spdk_bdev_desc {
67 	struct spdk_bdev *bdev;
68 	bool write;
69 	enum spdk_bdev_claim_type claim_type;
70 	struct spdk_thread *thread;
71 };
72 
73 struct spdk_bdev *g_bdev;
74 
75 static struct spdk_bdev_module g_bdev_mod = {
76 	.name = "blob_bdev_ut"
77 };
78 
79 struct spdk_io_channel *
80 spdk_bdev_get_io_channel(struct spdk_bdev_desc *desc)
81 {
82 	if (desc != NULL) {
83 		return (struct spdk_io_channel *)0x1;
84 	}
85 	return NULL;
86 }
87 
88 static void
89 ut_put_io_channel(struct spdk_io_channel *ch)
90 {
91 }
92 
93 static struct spdk_bdev *
94 get_bdev(const char *bdev_name)
95 {
96 	if (g_bdev == NULL) {
97 		return NULL;
98 	}
99 
100 	if (strcmp(bdev_name, g_bdev->name) != 0) {
101 		return NULL;
102 	}
103 
104 	return g_bdev;
105 }
106 
107 int
108 spdk_bdev_open_ext(const char *bdev_name, bool write, spdk_bdev_event_cb_t event_cb,
109 		   void *event_ctx, struct spdk_bdev_desc **_desc)
110 {
111 	struct spdk_bdev_desc *desc;
112 	struct spdk_bdev *bdev = get_bdev(bdev_name);
113 
114 	if (bdev == NULL) {
115 		return -ENODEV;
116 	}
117 
118 	if (write && bdev->claim_module != NULL) {
119 		return -EPERM;
120 	}
121 
122 	desc = calloc(1, sizeof(*desc));
123 	desc->bdev = g_bdev;
124 	desc->write = write;
125 	desc->thread = spdk_get_thread();
126 	*_desc = desc;
127 	bdev->open_cnt++;
128 
129 	return 0;
130 }
131 
132 void
133 spdk_bdev_close(struct spdk_bdev_desc *desc)
134 {
135 	struct spdk_bdev *bdev = desc->bdev;
136 
137 	CU_ASSERT(desc->thread == spdk_get_thread());
138 
139 	bdev->open_cnt--;
140 	if (bdev->claim_desc == desc) {
141 		bdev->claim_desc = NULL;
142 		bdev->claim_type = SPDK_BDEV_CLAIM_NONE;
143 		bdev->claim_module = NULL;
144 	}
145 	free(desc);
146 }
147 
148 struct spdk_bdev *
149 spdk_bdev_desc_get_bdev(struct spdk_bdev_desc *desc)
150 {
151 	return desc->bdev;
152 }
153 
154 uint64_t
155 spdk_bdev_get_num_blocks(const struct spdk_bdev *bdev)
156 {
157 	return bdev->blockcnt;
158 }
159 
160 uint32_t
161 spdk_bdev_get_block_size(const struct spdk_bdev *bdev)
162 {
163 	return bdev->blocklen;
164 }
165 
166 /* This is a simple approximation: it does not support shared claims */
167 int
168 spdk_bdev_module_claim_bdev_desc(struct spdk_bdev_desc *desc, enum spdk_bdev_claim_type type,
169 				 struct spdk_bdev_claim_opts *opts,
170 				 struct spdk_bdev_module *module)
171 {
172 	struct spdk_bdev *bdev = desc->bdev;
173 
174 	if (bdev->claim_module != NULL) {
175 		return -EPERM;
176 	}
177 
178 	bdev->claim_type = type;
179 	bdev->claim_module = module;
180 	bdev->claim_desc = desc;
181 
182 	desc->claim_type = type;
183 
184 	return 0;
185 }
186 
187 static void
188 init_bdev(struct spdk_bdev *bdev, const char *name, uint64_t num_blocks)
189 {
190 	memset(bdev, 0, sizeof(*bdev));
191 	snprintf(bdev->name, sizeof(bdev->name), "%s", name);
192 	bdev->blockcnt = num_blocks;
193 }
194 
195 static void
196 create_bs_dev(void)
197 {
198 	struct spdk_bdev bdev;
199 	struct spdk_bs_dev *bs_dev = NULL;
200 	struct blob_bdev *blob_bdev;
201 	int rc;
202 
203 	init_bdev(&bdev, "bdev0", 16);
204 	g_bdev = &bdev;
205 
206 	rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev);
207 	CU_ASSERT(rc == 0);
208 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
209 	CU_ASSERT(bdev.open_cnt == 1);
210 
211 	blob_bdev = (struct blob_bdev *)bs_dev;
212 	CU_ASSERT(blob_bdev->desc != NULL);
213 	CU_ASSERT(blob_bdev->desc->write);
214 	CU_ASSERT(blob_bdev->desc->bdev == g_bdev);
215 	CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE);
216 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE);
217 
218 	bs_dev->destroy(bs_dev);
219 	CU_ASSERT(bdev.open_cnt == 0);
220 	g_bdev = NULL;
221 }
222 
223 static void
224 create_bs_dev_ro(void)
225 {
226 	struct spdk_bdev bdev;
227 	struct spdk_bs_dev *bs_dev = NULL;
228 	struct blob_bdev *blob_bdev;
229 	struct spdk_bdev_bs_dev_opts opts = { 0 };
230 	int rc;
231 
232 	/* opts with the wrong size returns -EINVAL */
233 	rc = spdk_bdev_create_bs_dev("nope", false, &opts, sizeof(opts) + 8, NULL, NULL, &bs_dev);
234 	CU_ASSERT(rc == -EINVAL);
235 
236 	/* opts with the right size is OK, but can still fail if the device doesn't exist. */
237 	opts.opts_size = sizeof(opts);
238 	rc = spdk_bdev_create_bs_dev("nope", false, &opts, sizeof(opts), NULL, NULL, &bs_dev);
239 	CU_ASSERT(rc == -ENODEV);
240 
241 	init_bdev(&bdev, "bdev0", 16);
242 	g_bdev = &bdev;
243 
244 	/* The normal way to create a read-only device */
245 	rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev);
246 	CU_ASSERT(rc == 0);
247 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
248 	CU_ASSERT(bdev.open_cnt == 1);
249 
250 	blob_bdev = (struct blob_bdev *)bs_dev;
251 	CU_ASSERT(blob_bdev->desc != NULL);
252 	CU_ASSERT(!blob_bdev->desc->write);
253 	CU_ASSERT(blob_bdev->desc->bdev == g_bdev);
254 	CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE);
255 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE);
256 
257 	bs_dev->destroy(bs_dev);
258 	CU_ASSERT(bdev.open_cnt == 0);
259 	g_bdev = NULL;
260 }
261 
262 static void
263 create_bs_dev_rw(void)
264 {
265 	struct spdk_bdev bdev;
266 	struct spdk_bs_dev *bs_dev = NULL;
267 	struct blob_bdev *blob_bdev;
268 	int rc;
269 
270 	init_bdev(&bdev, "bdev0", 16);
271 	g_bdev = &bdev;
272 
273 	/* This is equivalent to spdk_bdev_create_bs_dev_ext() */
274 	rc = spdk_bdev_create_bs_dev("bdev0", true, NULL, 0, NULL, NULL, &bs_dev);
275 	CU_ASSERT(rc == 0);
276 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
277 	CU_ASSERT(bdev.open_cnt == 1);
278 
279 	blob_bdev = (struct blob_bdev *)bs_dev;
280 	CU_ASSERT(blob_bdev->desc != NULL);
281 	CU_ASSERT(blob_bdev->desc->write);
282 	CU_ASSERT(blob_bdev->desc->bdev == g_bdev);
283 	CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE);
284 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE);
285 
286 	bs_dev->destroy(bs_dev);
287 	CU_ASSERT(bdev.open_cnt == 0);
288 	g_bdev = NULL;
289 }
290 
291 static void
292 claim_bs_dev(void)
293 {
294 	struct spdk_bdev bdev;
295 	struct spdk_bs_dev *bs_dev = NULL, *bs_dev2 = NULL;
296 	struct blob_bdev *blob_bdev;
297 	int rc;
298 
299 	init_bdev(&bdev, "bdev0", 16);
300 	g_bdev = &bdev;
301 
302 	rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev);
303 	CU_ASSERT(rc == 0);
304 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
305 
306 	blob_bdev = (struct blob_bdev *)bs_dev;
307 	CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE);
308 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE);
309 	CU_ASSERT(blob_bdev->desc->write);
310 
311 	/* Can get an exclusive write claim */
312 	rc = spdk_bs_bdev_claim(bs_dev, &g_bdev_mod);
313 	CU_ASSERT(rc == 0);
314 	CU_ASSERT(blob_bdev->desc->write);
315 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_ONE);
316 	CU_ASSERT(bdev.claim_desc == blob_bdev->desc);
317 
318 	/* Claim blocks a second writer without messing up the first one. */
319 	rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev2);
320 	CU_ASSERT(rc == -EPERM);
321 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_ONE);
322 	CU_ASSERT(bdev.claim_desc == blob_bdev->desc);
323 
324 	/* Claim blocks a second claim without messing up the first one. */
325 	rc = spdk_bs_bdev_claim(bs_dev, &g_bdev_mod);
326 	CU_ASSERT(rc == -EPERM);
327 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_ONE);
328 	CU_ASSERT(bdev.claim_desc == blob_bdev->desc);
329 
330 	bs_dev->destroy(bs_dev);
331 	CU_ASSERT(bdev.open_cnt == 0);
332 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE);
333 	CU_ASSERT(bdev.claim_module == NULL);
334 	CU_ASSERT(bdev.claim_desc == NULL);
335 	g_bdev = NULL;
336 }
337 
338 static void
339 claim_bs_dev_ro(void)
340 {
341 	struct spdk_bdev bdev;
342 	struct spdk_bs_dev *bs_dev = NULL, *bs_dev2 = NULL;
343 	struct blob_bdev *blob_bdev;
344 	int rc;
345 
346 	init_bdev(&bdev, "bdev0", 16);
347 	g_bdev = &bdev;
348 
349 	rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev);
350 	CU_ASSERT(rc == 0);
351 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
352 
353 	blob_bdev = (struct blob_bdev *)bs_dev;
354 	CU_ASSERT(blob_bdev->desc->claim_type == SPDK_BDEV_CLAIM_NONE);
355 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE);
356 	CU_ASSERT(!blob_bdev->desc->write);
357 
358 	/* Can get an shared reader claim */
359 	rc = spdk_bs_bdev_claim(bs_dev, &g_bdev_mod);
360 	CU_ASSERT(rc == 0);
361 	CU_ASSERT(!blob_bdev->desc->write);
362 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_NONE);
363 	CU_ASSERT(bdev.claim_desc == blob_bdev->desc);
364 
365 	/* Claim blocks a writer without messing up the claim. */
366 	rc = spdk_bdev_create_bs_dev_ext("bdev0", NULL, NULL, &bs_dev2);
367 	CU_ASSERT(rc == -EPERM);
368 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_READ_MANY_WRITE_NONE);
369 	CU_ASSERT(bdev.claim_desc == blob_bdev->desc);
370 
371 	/* Another reader is just fine */
372 	rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev2);
373 	CU_ASSERT(rc == 0);
374 	SPDK_CU_ASSERT_FATAL(bs_dev2 != NULL);
375 	bs_dev2->destroy(bs_dev2);
376 
377 	bs_dev->destroy(bs_dev);
378 	CU_ASSERT(bdev.open_cnt == 0);
379 	CU_ASSERT(bdev.claim_type == SPDK_BDEV_CLAIM_NONE);
380 	CU_ASSERT(bdev.claim_module == NULL);
381 	CU_ASSERT(bdev.claim_desc == NULL);
382 	g_bdev = NULL;
383 }
384 
385 /*
386  * Verify that create_channel() and destroy_channel() increment and decrement the blob_bdev->refs.
387  */
388 static void
389 deferred_destroy_refs(void)
390 {
391 	struct spdk_bdev bdev;
392 	struct spdk_io_channel *ch1, *ch2;
393 	struct spdk_bs_dev *bs_dev = NULL;
394 	struct blob_bdev *blob_bdev;
395 	int rc;
396 
397 	set_thread(0);
398 	init_bdev(&bdev, "bdev0", 16);
399 	g_bdev = &bdev;
400 
401 	/* Open a blob_bdev, verify reference count is 1. */
402 	rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev);
403 	CU_ASSERT(rc == 0);
404 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
405 	blob_bdev = (struct blob_bdev *)bs_dev;
406 	CU_ASSERT(blob_bdev->refs == 1);
407 	CU_ASSERT(blob_bdev->desc != NULL);
408 
409 	/* Verify reference count increases with channels on the same thread. */
410 	ch1 = bs_dev->create_channel(bs_dev);
411 	SPDK_CU_ASSERT_FATAL(ch1 != NULL);
412 	CU_ASSERT(blob_bdev->refs == 2);
413 	ch2 = bs_dev->create_channel(bs_dev);
414 	SPDK_CU_ASSERT_FATAL(ch2 != NULL);
415 	CU_ASSERT(blob_bdev->refs == 3);
416 	bs_dev->destroy_channel(bs_dev, ch1);
417 	CU_ASSERT(blob_bdev->refs == 2);
418 	bs_dev->destroy_channel(bs_dev, ch2);
419 	CU_ASSERT(blob_bdev->refs == 1);
420 	CU_ASSERT(blob_bdev->desc != NULL);
421 
422 	/* Verify reference count increases with channels on different threads. */
423 	ch1 = bs_dev->create_channel(bs_dev);
424 	SPDK_CU_ASSERT_FATAL(ch1 != NULL);
425 	CU_ASSERT(blob_bdev->refs == 2);
426 	set_thread(1);
427 	ch2 = bs_dev->create_channel(bs_dev);
428 	SPDK_CU_ASSERT_FATAL(ch2 != NULL);
429 	CU_ASSERT(blob_bdev->refs == 3);
430 	bs_dev->destroy_channel(bs_dev, ch1);
431 	CU_ASSERT(blob_bdev->refs == 2);
432 	bs_dev->destroy_channel(bs_dev, ch2);
433 	CU_ASSERT(blob_bdev->refs == 1);
434 	CU_ASSERT(blob_bdev->desc != NULL);
435 
436 	set_thread(0);
437 	bs_dev->destroy(bs_dev);
438 	g_bdev = NULL;
439 }
440 
441 /*
442  * When a channel is open bs_dev->destroy() should not free bs_dev until after the last channel is
443  * closed. Further, destroy() prevents the creation of new channels.
444  */
445 static void
446 deferred_destroy_channels(void)
447 {
448 	struct spdk_bdev bdev;
449 	struct spdk_io_channel *ch1, *ch2;
450 	struct spdk_bs_dev *bs_dev = NULL;
451 	struct blob_bdev *blob_bdev;
452 	int rc;
453 
454 	set_thread(0);
455 	init_bdev(&bdev, "bdev0", 16);
456 
457 	/* Open bs_dev and sanity check */
458 	g_bdev = &bdev;
459 	rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev);
460 	CU_ASSERT(rc == 0);
461 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
462 	CU_ASSERT(bdev.open_cnt == 1);
463 	blob_bdev = (struct blob_bdev *)bs_dev;
464 	CU_ASSERT(blob_bdev->refs == 1);
465 	CU_ASSERT(blob_bdev->desc != NULL);
466 
467 	/* Create a channel, destroy the bs_dev. It should not be freed yet. */
468 	ch1 = bs_dev->create_channel(bs_dev);
469 	SPDK_CU_ASSERT_FATAL(ch1 != NULL);
470 	CU_ASSERT(blob_bdev->refs == 2);
471 	bs_dev->destroy(bs_dev);
472 
473 	/* Destroy closes the bdev and prevents desc from being used for creating more channels. */
474 	CU_ASSERT(blob_bdev->desc == NULL);
475 	CU_ASSERT(bdev.open_cnt == 0);
476 	CU_ASSERT(blob_bdev->refs == 1);
477 	ch2 = bs_dev->create_channel(bs_dev);
478 	CU_ASSERT(ch2 == NULL)
479 	CU_ASSERT(blob_bdev->refs == 1);
480 	bs_dev->destroy_channel(bs_dev, ch1);
481 	g_bdev = NULL;
482 
483 	/* Now bs_dev should have been freed. Builds with asan will verify. */
484 }
485 
486 /*
487  * Verify that deferred destroy copes well with the last channel destruction being on a thread other
488  * than the thread used to obtain the bdev descriptor.
489  */
490 static void
491 deferred_destroy_threads(void)
492 {
493 	struct spdk_bdev bdev;
494 	struct spdk_io_channel *ch1, *ch2;
495 	struct spdk_bs_dev *bs_dev = NULL;
496 	struct blob_bdev *blob_bdev;
497 	int rc;
498 
499 	set_thread(0);
500 	init_bdev(&bdev, "bdev0", 16);
501 	g_bdev = &bdev;
502 
503 	/* Open bs_dev and sanity check */
504 	rc = spdk_bdev_create_bs_dev("bdev0", false, NULL, 0, NULL, NULL, &bs_dev);
505 	CU_ASSERT(rc == 0);
506 	SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
507 	CU_ASSERT(bdev.open_cnt == 1);
508 	blob_bdev = (struct blob_bdev *)bs_dev;
509 	CU_ASSERT(blob_bdev->refs == 1);
510 	CU_ASSERT(blob_bdev->desc != NULL);
511 
512 	/* Create two channels, each on their own thread. */
513 	ch1 = bs_dev->create_channel(bs_dev);
514 	SPDK_CU_ASSERT_FATAL(ch1 != NULL);
515 	CU_ASSERT(blob_bdev->refs == 2);
516 	CU_ASSERT(spdk_get_thread() == blob_bdev->desc->thread);
517 	set_thread(1);
518 	ch2 = bs_dev->create_channel(bs_dev);
519 	SPDK_CU_ASSERT_FATAL(ch2 != NULL);
520 	CU_ASSERT(blob_bdev->refs == 3);
521 
522 	/* Destroy the bs_dev on thread 0, the channel on thread 0, then the channel on thread 1. */
523 	set_thread(0);
524 	bs_dev->destroy(bs_dev);
525 	CU_ASSERT(blob_bdev->desc == NULL);
526 	CU_ASSERT(bdev.open_cnt == 0);
527 	CU_ASSERT(blob_bdev->refs == 2);
528 	bs_dev->destroy_channel(bs_dev, ch1);
529 	CU_ASSERT(blob_bdev->refs == 1);
530 	set_thread(1);
531 	bs_dev->destroy_channel(bs_dev, ch2);
532 	set_thread(0);
533 	g_bdev = NULL;
534 
535 	/* Now bs_dev should have been freed. Builds with asan will verify. */
536 }
537 
538 int
539 main(int argc, char **argv)
540 {
541 	CU_pSuite	suite;
542 	unsigned int	num_failures;
543 
544 	CU_initialize_registry();
545 
546 	suite = CU_add_suite("blob_bdev", NULL, NULL);
547 
548 	CU_ADD_TEST(suite, create_bs_dev);
549 	CU_ADD_TEST(suite, create_bs_dev_ro);
550 	CU_ADD_TEST(suite, create_bs_dev_rw);
551 	CU_ADD_TEST(suite, claim_bs_dev);
552 	CU_ADD_TEST(suite, claim_bs_dev_ro);
553 	CU_ADD_TEST(suite, deferred_destroy_refs);
554 	CU_ADD_TEST(suite, deferred_destroy_channels);
555 	CU_ADD_TEST(suite, deferred_destroy_threads);
556 
557 	allocate_threads(2);
558 	set_thread(0);
559 
560 	num_failures = spdk_ut_run_tests(argc, argv, NULL);
561 	CU_cleanup_registry();
562 
563 	free_threads();
564 
565 	return num_failures;
566 }
567