1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2024 Oxide Computer Company
14 */
15
16 /*
17 * Iterate over the namespaces and ensure that the information we get in the
18 * discovery is the same as when we take a snapshot. To ensure that nothing
19 * changes out from under us, we take a controller write lock which will ensure
20 * that no modifications can occur.
21 *
22 * In addition, we want to test that discovery filters work so we first go
23 * through and count the different levels.
24 */
25
26 #include <err.h>
27 #include <string.h>
28 #include <umem.h>
29
30 #include "libnvme_test_common.h"
31
32 static bool
ns_disc_count_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)33 ns_disc_count_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg)
34 {
35 uint32_t *valp = arg;
36 *valp = *valp + 1;
37 return (true);
38 }
39
40 static bool
ns_disc_count(nvme_ctrl_t * ctrl,nvme_ns_disc_level_t level,uint32_t exp)41 ns_disc_count(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level, uint32_t exp)
42 {
43 uint32_t count = 0;
44
45 if (!nvme_ns_discover(ctrl, level, ns_disc_count_cb, &count)) {
46 libnvme_test_ctrl_warn(ctrl, "failed to discover at level %u",
47 level);
48 return (false);
49 } else if (count != exp) {
50 warnx("TEST FAILED: ns discovery level %u found 0x%x "
51 "namespaces, but expected 0x%x", level, count, exp);
52 return (false);
53 } else {
54 (void) printf("TEST PASSED: ns discovery level %u had correct "
55 "count (0x%x)\n", level, exp);
56 return (true);
57 }
58 }
59
60 static bool
ns_disc_blkdev_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)61 ns_disc_blkdev_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg)
62 {
63 int *ret = arg;
64 nvme_ns_info_t *info;
65 const uint32_t nsid = nvme_ns_disc_nsid(disc);
66 const char *addr;
67
68 if (nvme_ns_disc_level(disc) < NVME_NS_DISC_F_BLKDEV) {
69 warnx("TEST FAILED: ns %u has level %u, but filtering on "
70 "blkdev (%u)", nsid, nvme_ns_disc_level(disc),
71 NVME_NS_DISC_F_BLKDEV);
72 *ret = EXIT_FAILURE;
73 return (true);
74 }
75
76 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) {
77 libnvme_test_ctrl_warn(ctrl, "failed to get info snapshot for "
78 "nsid %u", nsid);
79 *ret = EXIT_FAILURE;
80 return (true);
81 }
82
83 if (!nvme_ns_info_bd_addr(info, &addr)) {
84 libnvme_test_ctrl_warn(ctrl, "failed to get bd addr for nsid "
85 "%u", nsid);
86 *ret = EXIT_FAILURE;
87 } else if (addr[0] == '\0') {
88 warnx("TEST FAILED: nsid %u has invalid bd addr", nsid);
89 *ret = EXIT_FAILURE;
90 } else {
91 (void) printf("TEST PASSED: nsid %u bd addr valid\n", nsid);
92 }
93
94 nvme_ns_info_free(info);
95 return (true);
96 }
97
98 static bool
ns_disc_guids_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)99 ns_disc_guids_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg)
100 {
101 int *ret = arg;
102 nvme_ns_info_t *info;
103 const uint32_t nsid = nvme_ns_disc_nsid(disc);
104 const nvme_ns_disc_flags_t flags = nvme_ns_disc_flags(disc);
105 uint8_t id[16];
106 bool bret;
107
108 if (nvme_ns_disc_level(disc) < NVME_NS_DISC_F_ACTIVE) {
109 warnx("TEST FAILED: ns %u has level %u, but filtering on "
110 "active (%u)", nsid, nvme_ns_disc_level(disc),
111 NVME_NS_DISC_F_ACTIVE);
112 *ret = EXIT_FAILURE;
113 return (true);
114 }
115
116 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) {
117 libnvme_test_ctrl_warn(ctrl, "failed to get info snapshot for "
118 "nsid %u", nsid);
119 *ret = EXIT_FAILURE;
120 return (true);
121 }
122
123 bret = nvme_ns_info_eui64(info, id);
124 if (bret != ((flags & NVME_NS_DISC_F_EUI64_VALID) != 0)) {
125 warnx("TEST FAILED: nvme_ns_info_eui64() returned %s, but "
126 "expected %s from discovery information for nsid %u",
127 bret ? "true" : "false",
128 (flags & NVME_NS_DISC_F_EUI64_VALID) != 0 ? "true" :
129 "false", nsid);
130 *ret = EXIT_FAILURE;
131 } else {
132 (void) printf("TEST PASSED: namespace snapshot and discovery "
133 "agreed on EUI64 presence for nsid %u\n", nsid);
134 }
135
136 if (bret) {
137 const uint8_t *eui64 = nvme_ns_disc_eui64(disc);
138 const uint8_t zero[8] = { 0 };
139
140 if (memcmp(eui64, id, sizeof (zero)) != 0) {
141 warnx("TEST FAILED: EUI64 differs between "
142 "discovery and info snapshot for nsid %u", nsid);
143 *ret = EXIT_FAILURE;
144 } else {
145 (void) printf("TEST PASSED: EUI64 equal between "
146 "discovery and info snapshot for nsid %u\n", nsid);
147 }
148
149 if (memcmp(id, zero, sizeof (zero)) == 0) {
150 warnx("TEST FAILED: Found invalid zero EUI64 for nsid "
151 "%u", nsid);
152 *ret = EXIT_FAILURE;
153 } else {
154 (void) printf("TEST PASSED: EUI64 is non-zero for "
155 "nsid %u\n", nsid);
156 }
157 } else {
158 if (nvme_ns_disc_eui64(disc) != NULL) {
159 warnx("TEST FAILED: discovery EUI64 was valid, but "
160 "should be NULL for nsid %u", nsid);
161 *ret = EXIT_FAILURE;
162 } else {
163 (void) printf("TEST PASSED: discovery EUI64 correctly "
164 "returned NULL for nsid %u\n", nsid);
165 }
166
167 switch (nvme_ns_info_err(info)) {
168 case NVME_INFO_ERR_VERSION:
169 case NVME_INFO_ERR_MISSING_CAP:
170 (void) printf("TEST PASSED: nvme_ns_info_eui64() "
171 "returned a valid error for nsid %u\n", nsid);
172 break;
173 default:
174 warnx("TEST FAILED: nvme_ns_info_eui64() returned an "
175 "invalid error for nsid %u: %s (%u)", nsid,
176 nvme_ns_info_errtostr(info, nvme_ns_info_err(info)),
177 nvme_ns_info_err(info));
178 *ret = EXIT_FAILURE;
179 break;
180 }
181 }
182
183 bret = nvme_ns_info_nguid(info, id);
184 if (bret != ((flags & NVME_NS_DISC_F_NGUID_VALID) != 0)) {
185 warnx("TEST FAILED: nvme_ns_info_nguid() returned %s, but "
186 "expected %s from discovery information for nsid %u",
187 bret ? "true" : "false",
188 (flags & NVME_NS_DISC_F_NGUID_VALID) != 0 ? "true" :
189 "false", nsid);
190 *ret = EXIT_FAILURE;
191 } else {
192 (void) printf("TEST PASSED: namespace snapshot and discovery "
193 "agreed on NGUID presence for nsid %u\n", nsid);
194 }
195
196 if (bret) {
197 const uint8_t *nguid = nvme_ns_disc_nguid(disc);
198 const uint8_t zero[16] = { 0 };
199
200 if (memcmp(nguid, id, sizeof (zero)) != 0) {
201 warnx("TEST FAILED: NGUID differs between "
202 "discovery and info snapshot for nsid %u", nsid);
203 *ret = EXIT_FAILURE;
204 } else {
205 (void) printf("TEST PASSED: NGUID equal between "
206 "discovery and info snapshot for nsid %u\n", nsid);
207 }
208
209 if (memcmp(id, zero, sizeof (zero)) == 0) {
210 warnx("TEST FAILED: Found invalid zero NGUID for nsid "
211 "%u", nsid);
212 *ret = EXIT_FAILURE;
213 } else {
214 (void) printf("TEST PASSED: NGUID is non-zero for "
215 "nsid %u\n", nsid);
216 }
217 } else {
218 if (nvme_ns_disc_nguid(disc) != NULL) {
219 warnx("TEST FAILED: discovery NGUID was valid, but "
220 "should be NULL for nsid %u", nsid);
221 *ret = EXIT_FAILURE;
222 } else {
223 (void) printf("TEST PASSED: discovery NGUID correctly "
224 "returned NULL for nsid %u\n", nsid);
225 }
226
227 switch (nvme_ns_info_err(info)) {
228 case NVME_INFO_ERR_VERSION:
229 case NVME_INFO_ERR_MISSING_CAP:
230 (void) printf("TEST PASSED: nvme_ns_info_nguid() "
231 "returned a valid error for nsid %u\n", nsid);
232 break;
233 default:
234 warnx("TEST FAILED: nvme_ns_info_nguid() returned an "
235 "invalid error for nsid %u: %s (%u)", nsid,
236 nvme_ns_info_errtostr(info, nvme_ns_info_err(info)),
237 nvme_ns_info_err(info));
238 *ret = EXIT_FAILURE;
239 break;
240 }
241 }
242 nvme_ns_info_free(info);
243 return (true);
244 }
245
246 static bool
ns_disc_level_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)247 ns_disc_level_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg)
248 {
249 int *ret = arg;
250 nvme_ns_info_t *info;
251 const uint32_t nsid = nvme_ns_disc_nsid(disc);
252
253 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) {
254 libnvme_test_ctrl_warn(ctrl, "failed to get info snapshot for "
255 "nsid %u", nsid);
256 *ret = EXIT_FAILURE;
257 return (true);
258 }
259
260 if (nvme_ns_disc_level(disc) != nvme_ns_info_level(info)) {
261 warnx("TEST FAILED: discovery and ns info snapshot disagree "
262 "on discovery level: disc has %u, info has %u",
263 nvme_ns_disc_level(disc), nvme_ns_info_level(info));
264 *ret = EXIT_FAILURE;
265 } else {
266 (void) printf("TEST PASSED: discovery and ns info snapshot "
267 "agree for nsid %u\n", nsid);
268 }
269
270 nvme_ns_info_free(info);
271 return (true);
272 }
273
274 static bool
ns_disc_bad_disc_init(nvme_ctrl_t * ctrl,nvme_ns_disc_level_t level,nvme_ns_iter_t ** iterp,nvme_err_t exp_err,const char * desc)275 ns_disc_bad_disc_init(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level,
276 nvme_ns_iter_t **iterp, nvme_err_t exp_err, const char *desc)
277 {
278 if (nvme_ns_discover_init(ctrl, level, iterp)) {
279 warnx("TEST FAILED: nvme_ns_discover_init() erroneously "
280 "passed despite %s", desc);
281 nvme_ns_discover_fini(*iterp);
282 return (false);
283 } else if (nvme_ctrl_err(ctrl) != exp_err) {
284 warnx("TEST FAILED: nvme_ns_discover_init() returned "
285 "wrong error %s (0x%x), not %s (0x%x)",
286 nvme_ctrl_errtostr(ctrl, nvme_ctrl_err(ctrl)),
287 nvme_ctrl_err(ctrl), nvme_ctrl_errtostr(ctrl,
288 exp_err), exp_err);
289 return (false);
290 } else {
291 (void) printf("TEST PASSED: nvme_ns_discover_init() failed "
292 "correctly for %s\n", desc);
293 return (true);
294 }
295 }
296
297 static bool
ns_disc_bad_disc(nvme_ctrl_t * ctrl,nvme_ns_disc_level_t level,nvme_ns_disc_f func,nvme_err_t exp_err,const char * desc)298 ns_disc_bad_disc(nvme_ctrl_t *ctrl, nvme_ns_disc_level_t level,
299 nvme_ns_disc_f func, nvme_err_t exp_err, const char *desc)
300 {
301 if (nvme_ns_discover(ctrl, level, func, NULL)) {
302 warnx("TEST FAILED: nvme_ns_discover() erroneously "
303 "passed despite %s", desc);
304 return (false);
305 } else if (nvme_ctrl_err(ctrl) != exp_err) {
306 warnx("TEST FAILED: nvme_ns_discover() returned "
307 "wrong error %s (0x%x), not %s (0x%x)",
308 nvme_ctrl_errtostr(ctrl, nvme_ctrl_err(ctrl)),
309 nvme_ctrl_err(ctrl), nvme_ctrl_errtostr(ctrl,
310 exp_err), exp_err);
311 return (false);
312 } else {
313 (void) printf("TEST PASSED: nvme_ns_discover() failed "
314 "correctly for %s\n", desc);
315 return (true);
316 }
317 }
318
319 static bool
ns_disc_nop_cb(nvme_ctrl_t * ctrl,const nvme_ns_disc_t * disc,void * arg)320 ns_disc_nop_cb(nvme_ctrl_t *ctrl, const nvme_ns_disc_t *disc, void *arg)
321 {
322 return (true);
323 }
324
325 int
main(void)326 main(void)
327 {
328 int ret = EXIT_SUCCESS;
329 nvme_t *nvme;
330 nvme_ctrl_t *ctrl;
331 nvme_ctrl_info_t *info;
332 nvme_iter_t iret;
333 nvme_ns_iter_t *iter;
334 const nvme_ns_disc_t *disc;
335 uint32_t nbd = 0, nni = 0, nact = 0, nalloc = 0, nns = 0;
336
337 libnvme_test_init(&nvme, &ctrl);
338 if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE, NVME_LOCK_F_DONT_BLOCK)) {
339 libnvme_test_ctrl_fatal(ctrl, "failed to obtain write lock");
340 }
341
342 if (!nvme_ns_discover_init(ctrl, NVME_NS_DISC_F_ALL, &iter)) {
343 libnvme_test_ctrl_fatal(ctrl, "failed to initialize initial "
344 "ns discovery");
345 }
346
347 while ((iret = nvme_ns_discover_step(iter, &disc)) == NVME_ITER_VALID) {
348 switch (nvme_ns_disc_level(disc)) {
349 case NVME_NS_DISC_F_BLKDEV:
350 nbd++;
351 /* FALLTHROUGH */
352 case NVME_NS_DISC_F_NOT_IGNORED:
353 nni++;
354 /* FALLTHROUGH */
355 case NVME_NS_DISC_F_ACTIVE:
356 nact++;
357 /* FALLTHROUGH */
358 case NVME_NS_DISC_F_ALLOCATED:
359 nalloc++;
360 /* FALLTHROUGH */
361 case NVME_NS_DISC_F_ALL:
362 nns++;
363 break;
364 }
365 }
366
367 nvme_ns_discover_fini(iter);
368 if (iret != NVME_ITER_DONE) {
369 libnvme_test_ctrl_fatal(ctrl, "initial ns discovery failed");
370 }
371
372 if (!nvme_ctrl_info_snap(ctrl, &info)) {
373 libnvme_test_ctrl_fatal(ctrl, "failed to get info snapshot");
374 }
375
376 if (nns != nvme_ctrl_info_nns(info)) {
377 warnx("TEST FAILED: discovery found %u namespaces, but the "
378 "identify controller suggests there are %u", nns,
379 nvme_ctrl_info_nns(info));
380 }
381
382 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_BLKDEV, nbd)) {
383 ret = EXIT_FAILURE;
384 }
385
386 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_NOT_IGNORED, nni)) {
387 ret = EXIT_FAILURE;
388 }
389
390 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_ACTIVE, nact)) {
391 ret = EXIT_FAILURE;
392 }
393
394 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_ALLOCATED, nalloc)) {
395 ret = EXIT_FAILURE;
396 }
397
398 if (!ns_disc_count(ctrl, NVME_NS_DISC_F_ALL, nns)) {
399 ret = EXIT_FAILURE;
400 }
401
402 /*
403 * For anything that has a blkdev address, ensure that our info snapshot
404 * has a valid blkdev address.
405 */
406 if (!nvme_ns_discover(ctrl, NVME_NS_DISC_F_BLKDEV, ns_disc_blkdev_cb,
407 &ret)) {
408 libnvme_test_ctrl_warn(ctrl, "discovery failed for blkdev "
409 "test");
410 ret = EXIT_FAILURE;
411 }
412
413 /*
414 * For anything active, check if there are guids and that the
415 * information snapshot matches the same logic.
416 */
417 if (!nvme_ns_discover(ctrl, NVME_NS_DISC_F_ACTIVE, ns_disc_guids_cb,
418 &ret)) {
419 libnvme_test_ctrl_warn(ctrl, "discovery failed for guids "
420 "test");
421 ret = EXIT_FAILURE;
422 }
423
424 /*
425 * For everything, make sure the levels match.
426 */
427 if (!nvme_ns_discover(ctrl, NVME_NS_DISC_F_ALL, ns_disc_level_cb,
428 &ret)) {
429 libnvme_test_ctrl_warn(ctrl, "discovery failed for levels "
430 "test");
431 ret = EXIT_FAILURE;
432 }
433
434 nvme_ctrl_unlock(ctrl);
435
436 if (!ns_disc_bad_disc_init(ctrl, INT32_MAX, &iter, NVME_ERR_BAD_FLAG,
437 "invalid level")) {
438 ret = EXIT_FAILURE;
439 }
440
441 if (!ns_disc_bad_disc_init(ctrl, NVME_NS_DISC_F_ALL, NULL,
442 NVME_ERR_BAD_PTR, "invalid iter pointer")) {
443 ret = EXIT_FAILURE;
444 }
445
446 if (!ns_disc_bad_disc(ctrl, UINT32_MAX, ns_disc_nop_cb,
447 NVME_ERR_BAD_FLAG, "invalid level")) {
448 ret = EXIT_FAILURE;
449 }
450
451 if (!ns_disc_bad_disc(ctrl, NVME_NS_DISC_F_ALL, NULL,
452 NVME_ERR_BAD_PTR, "invalid function pointer")) {
453 ret = EXIT_FAILURE;
454 }
455
456 umem_setmtbf(1);
457 if (!ns_disc_bad_disc_init(ctrl, NVME_NS_DISC_F_ALL, &iter,
458 NVME_ERR_NO_MEM, "no memory")) {
459 ret = EXIT_FAILURE;
460 }
461
462 if (!ns_disc_bad_disc(ctrl, NVME_NS_DISC_F_ALL, ns_disc_nop_cb,
463 NVME_ERR_NO_MEM, "no memory")) {
464 ret = EXIT_FAILURE;
465 }
466 umem_setmtbf(0);
467
468 if (ret == EXIT_SUCCESS) {
469 (void) printf("All tests passed successfully\n");
470 }
471
472 nvme_ctrl_info_free(info);
473 nvme_ctrl_fini(ctrl);
474 nvme_fini(nvme);
475 return (ret);
476 }
477