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 2025 Oxide Computer Company
14 */
15
16 /*
17 * This test goes through the lifecycle of a namespace and verifies that when we
18 * try to take transitions that it does not support, we can generate the
19 * expected errors.
20 *
21 * This test expects to start from the device-empty profile.
22 */
23
24 #include <err.h>
25 #include <stdlib.h>
26 #include <sys/sysmacros.h>
27
28 #include "libnvme_test_common.h"
29
30 /*
31 * We expect that we should always be issued NSID 1.
32 */
33 #define NS_LC_NSID 1
34
35 /*
36 * This is a list of actions we can take in a given state against a namespace.
37 * There is no namespace create in this list as there is no way to specify a
38 * namespace to take action against.
39 */
40 typedef enum {
41 NS_ACT_NS_DELETE,
42 NS_ACT_CTRL_ATTACH,
43 NS_ACT_CTRL_DETACH,
44 NS_ACT_BD_ATTACH,
45 NS_ACT_BD_DETACH
46 } ns_act_t;
47
48 #define NS_LC_NACTS (NS_ACT_BD_DETACH + 1)
49
50 nvme_err_t nvme_unalloc_errs[NS_LC_NACTS] = {
51 [NS_ACT_NS_DELETE] = NVME_ERR_NS_UNALLOC,
52 [NS_ACT_CTRL_ATTACH] = NVME_ERR_NS_UNALLOC,
53 [NS_ACT_CTRL_DETACH] = NVME_ERR_NS_UNALLOC,
54 [NS_ACT_BD_ATTACH] = NVME_ERR_NS_UNALLOC,
55 [NS_ACT_BD_DETACH] = NVME_ERR_NS_UNALLOC
56 };
57
58 nvme_err_t nvme_alloc_errs[NS_LC_NACTS] = {
59 [NS_ACT_NS_DELETE] = NVME_ERR_OK,
60 [NS_ACT_CTRL_ATTACH] = NVME_ERR_OK,
61 [NS_ACT_CTRL_DETACH] = NVME_ERR_NS_CTRL_NOT_ATTACHED,
62 [NS_ACT_BD_ATTACH] = NVME_ERR_NS_CTRL_NOT_ATTACHED,
63 [NS_ACT_BD_DETACH] = NVME_ERR_NS_CTRL_NOT_ATTACHED
64 };
65
66 nvme_err_t nvme_attach_errs[NS_LC_NACTS] = {
67 [NS_ACT_NS_DELETE] = NVME_ERR_NS_CTRL_ATTACHED,
68 [NS_ACT_CTRL_ATTACH] = NVME_ERR_NS_CTRL_ATTACHED,
69 [NS_ACT_CTRL_DETACH] = NVME_ERR_OK,
70 [NS_ACT_BD_ATTACH] = NVME_ERR_OK,
71 [NS_ACT_BD_DETACH] = NVME_ERR_NS_CTRL_ATTACHED
72 };
73
74 nvme_err_t nvme_blkdev_errs[NS_LC_NACTS] = {
75 [NS_ACT_NS_DELETE] = NVME_ERR_NS_BLKDEV_ATTACH,
76 [NS_ACT_CTRL_ATTACH] = NVME_ERR_NS_BLKDEV_ATTACH,
77 [NS_ACT_CTRL_DETACH] = NVME_ERR_NS_BLKDEV_ATTACH,
78 [NS_ACT_BD_ATTACH] = NVME_ERR_NS_BLKDEV_ATTACH,
79 [NS_ACT_BD_DETACH] = NVME_ERR_OK
80 };
81
82 static bool
ns_life_err_comp(nvme_ctrl_t * ctrl,nvme_err_t exp,nvme_err_t act,const char * desc,const char * state)83 ns_life_err_comp(nvme_ctrl_t *ctrl, nvme_err_t exp, nvme_err_t act,
84 const char *desc, const char *state)
85 {
86 if (act != exp) {
87 warnx("TEST FAILED: %s in state %s returned %s (0x%x), but "
88 "expected %s (0x%x)", desc, state,
89 nvme_ctrl_errtostr(ctrl, act), act,
90 nvme_ctrl_errtostr(ctrl, exp), exp);
91 } else {
92 (void) printf("TEST PASSED: %s in state %s correctly returned "
93 "%s\n", desc, state, nvme_ctrl_errtostr(ctrl, act));
94 }
95
96 return (act == exp);
97 }
98
99 static bool
ns_life_ns_delete(nvme_ctrl_t * ctrl,uint32_t nsid,nvme_err_t exp,const char * state)100 ns_life_ns_delete(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_err_t exp,
101 const char *state)
102 {
103 nvme_err_t act;
104
105 if (!libnvme_test_ns_delete(ctrl, nsid, &act)) {
106 return (false);
107 }
108
109 return (ns_life_err_comp(ctrl, exp, act, "namespace delete", state));
110 }
111
112 static bool
ns_life_ctrl_attach(nvme_ctrl_t * ctrl,uint32_t nsid,nvme_err_t exp,const char * state)113 ns_life_ctrl_attach(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_err_t exp,
114 const char *state)
115 {
116 nvme_err_t act;
117
118 if (!libnvme_test_ctrl_attach(ctrl, nsid, NVME_NS_ATTACH_CTRL_ATTACH,
119 &act)) {
120 return (false);
121 }
122
123 return (ns_life_err_comp(ctrl, exp, act, "controller attach", state));
124 }
125
126 static bool
ns_life_ctrl_detach(nvme_ctrl_t * ctrl,uint32_t nsid,nvme_err_t exp,const char * state)127 ns_life_ctrl_detach(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_err_t exp,
128 const char *state)
129 {
130 nvme_err_t act;
131
132 if (!libnvme_test_ctrl_attach(ctrl, nsid, NVME_NS_ATTACH_CTRL_DETACH,
133 &act)) {
134 return (false);
135 }
136
137 return (ns_life_err_comp(ctrl, exp, act, "controller detach", state));
138 }
139
140 static bool
ns_life_blkdev_attach(nvme_ctrl_t * ctrl,uint32_t nsid,nvme_err_t exp,const char * state)141 ns_life_blkdev_attach(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_err_t exp,
142 const char *state)
143 {
144 nvme_err_t act;
145
146 if (!libnvme_test_ns_blkdev(ctrl, nsid, true, &act)) {
147 return (false);
148 }
149
150 return (ns_life_err_comp(ctrl, exp, act, "blkdev attach", state));
151 }
152
153 static bool
ns_life_blkdev_detach(nvme_ctrl_t * ctrl,uint32_t nsid,nvme_err_t exp,const char * state)154 ns_life_blkdev_detach(nvme_ctrl_t *ctrl, uint32_t nsid, nvme_err_t exp,
155 const char *state)
156 {
157 nvme_err_t act;
158
159 if (!libnvme_test_ns_blkdev(ctrl, nsid, false, &act)) {
160 return (false);
161 }
162
163 return (ns_life_err_comp(ctrl, exp, act, "blkdev detach", state));
164 }
165
166 typedef bool (*ns_life_f)(nvme_ctrl_t *, uint32_t, nvme_err_t,
167 const char *);
168
169 static const ns_life_f ns_lf_funcs[NS_LC_NACTS] = {
170 [NS_ACT_NS_DELETE] = ns_life_ns_delete,
171 [NS_ACT_CTRL_ATTACH] = ns_life_ctrl_attach,
172 [NS_ACT_CTRL_DETACH] = ns_life_ctrl_detach,
173 [NS_ACT_BD_ATTACH] = ns_life_blkdev_attach,
174 [NS_ACT_BD_DETACH] = ns_life_blkdev_detach
175 };
176
177 typedef struct ns_life_test {
178 nvme_ns_disc_level_t nlt_disc;
179 const char *nlt_desc;
180 nvme_err_t *nlt_errs;
181 size_t nlt_nerrs;
182 } ns_life_test_t;
183
184 static const ns_life_test_t ns_life_tests[] = { {
185 .nlt_disc = NVME_NS_DISC_F_ALL,
186 .nlt_desc = "unallocated",
187 .nlt_errs = nvme_unalloc_errs,
188 .nlt_nerrs = ARRAY_SIZE(nvme_unalloc_errs)
189 }, {
190 .nlt_disc = NVME_NS_DISC_F_ALLOCATED,
191 .nlt_desc = "allocated",
192 .nlt_errs = nvme_alloc_errs,
193 .nlt_nerrs = ARRAY_SIZE(nvme_alloc_errs)
194 }, {
195 .nlt_disc = NVME_NS_DISC_F_NOT_IGNORED,
196 .nlt_desc = "active",
197 .nlt_errs = nvme_attach_errs,
198 .nlt_nerrs = ARRAY_SIZE(nvme_attach_errs)
199 }, {
200 .nlt_disc = NVME_NS_DISC_F_BLKDEV,
201 .nlt_desc = "blkdev",
202 .nlt_errs = nvme_blkdev_errs,
203 .nlt_nerrs = ARRAY_SIZE(nvme_blkdev_errs)
204 } };
205
206 static bool
ns_life_run_one(nvme_ctrl_t * ctrl,uint32_t lbaf,const ns_life_test_t * test)207 ns_life_run_one(nvme_ctrl_t *ctrl, uint32_t lbaf, const ns_life_test_t *test)
208 {
209 bool ret = true;
210
211 for (size_t i = 0; i < test->nlt_nerrs; i++) {
212 if (!libnvme_test_setup_ns(ctrl, test->nlt_disc, NS_LC_NSID,
213 lbaf)) {
214 warnx("TEST FAILED: failed to transition ns to %s",
215 test->nlt_desc);
216 ret = false;
217 }
218
219 if (!ns_lf_funcs[i](ctrl, NS_LC_NSID, test->nlt_errs[i],
220 test->nlt_desc)) {
221 ret = false;
222 }
223 }
224
225 return (ret);
226 }
227
228 int
main(void)229 main(void)
230 {
231 int ret = EXIT_SUCCESS;
232 nvme_t *nvme;
233 nvme_ctrl_t *ctrl;
234 nvme_ctrl_info_t *info;
235 uint32_t lbaf;
236
237 libnvme_test_init(&nvme, &ctrl);
238 if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE, NVME_LOCK_F_DONT_BLOCK)) {
239 libnvme_test_ctrl_fatal(ctrl, "failed to obtain write lock");
240 }
241
242 if (!nvme_ctrl_info_snap(ctrl, &info)) {
243 libnvme_test_ctrl_fatal(ctrl, "failed to get info snapshot");
244 }
245
246 if (!libnvme_test_lbaf(info, NVME_TEST_LBA_SIZE, &lbaf)) {
247 errx(EXIT_FAILURE, "failed to find 4K LBA format, cannot "
248 "continue");
249 }
250
251 for (size_t i = 0; i < ARRAY_SIZE(ns_life_tests); i++) {
252 if (!ns_life_run_one(ctrl, lbaf, &ns_life_tests[i]))
253 ret = EXIT_FAILURE;
254 }
255
256 nvme_ctrl_info_free(info);
257 nvme_ctrl_unlock(ctrl);
258 nvme_ctrl_fini(ctrl);
259 nvme_fini(nvme);
260
261 if (ret == EXIT_SUCCESS) {
262 (void) printf("All tests passed successfully\n");
263 }
264
265 return (ret);
266 }
267