Commit | Line | Data |
---|---|---|
247ef2aa KZ |
1 | /* |
2 | * libiscsi engine | |
3 | * | |
4 | * this engine read/write iscsi lun with libiscsi. | |
5 | */ | |
6 | ||
7 | ||
8 | #include "../fio.h" | |
9 | #include "../optgroup.h" | |
10 | ||
11 | #include <stdlib.h> | |
12 | #include <iscsi/iscsi.h> | |
13 | #include <iscsi/scsi-lowlevel.h> | |
14 | #include <poll.h> | |
15 | ||
16 | struct iscsi_lun; | |
17 | struct iscsi_info; | |
18 | ||
19 | struct iscsi_task { | |
20 | struct scsi_task *scsi_task; | |
21 | struct iscsi_lun *iscsi_lun; | |
22 | struct io_u *io_u; | |
23 | }; | |
24 | ||
25 | struct iscsi_lun { | |
26 | struct iscsi_info *iscsi_info; | |
27 | struct iscsi_context *iscsi; | |
28 | struct iscsi_url *url; | |
29 | int block_size; | |
30 | uint64_t num_blocks; | |
31 | }; | |
32 | ||
33 | struct iscsi_info { | |
34 | struct iscsi_lun **luns; | |
35 | int nr_luns; | |
36 | struct pollfd *pfds; | |
37 | struct iscsi_task **complete_events; | |
38 | int nr_events; | |
39 | }; | |
40 | ||
41 | struct iscsi_options { | |
42 | void *pad; | |
43 | char *initiator; | |
44 | }; | |
45 | ||
46 | static struct fio_option options[] = { | |
47 | { | |
48 | .name = "initiator", | |
49 | .lname = "initiator", | |
50 | .type = FIO_OPT_STR_STORE, | |
51 | .off1 = offsetof(struct iscsi_options, initiator), | |
52 | .def = "iqn.2019-04.org.fio:fio", | |
53 | .help = "initiator name", | |
54 | .category = FIO_OPT_C_ENGINE, | |
55 | .group = FIO_OPT_G_ISCSI, | |
56 | }, | |
57 | ||
58 | { | |
59 | .name = NULL, | |
60 | }, | |
61 | }; | |
62 | ||
63 | static int fio_iscsi_setup_lun(struct iscsi_info *iscsi_info, | |
64 | char *initiator, struct fio_file *f, int i) | |
65 | { | |
66 | struct iscsi_lun *iscsi_lun = NULL; | |
67 | struct scsi_task *task = NULL; | |
68 | struct scsi_readcapacity16 *rc16 = NULL; | |
69 | int ret = 0; | |
70 | ||
223decdd | 71 | iscsi_lun = calloc(1, sizeof(struct iscsi_lun)); |
247ef2aa KZ |
72 | |
73 | iscsi_lun->iscsi_info = iscsi_info; | |
74 | ||
75 | iscsi_lun->url = iscsi_parse_full_url(NULL, f->file_name); | |
76 | if (iscsi_lun->url == NULL) { | |
77 | log_err("iscsi: failed to parse url: %s\n", f->file_name); | |
78 | ret = EINVAL; | |
79 | goto out; | |
80 | } | |
81 | ||
82 | iscsi_lun->iscsi = iscsi_create_context(initiator); | |
83 | if (iscsi_lun->iscsi == NULL) { | |
84 | log_err("iscsi: failed to create iscsi context.\n"); | |
85 | ret = 1; | |
86 | goto out; | |
87 | } | |
88 | ||
89 | if (iscsi_set_targetname(iscsi_lun->iscsi, iscsi_lun->url->target)) { | |
90 | log_err("iscsi: failed to set target name.\n"); | |
91 | ret = EINVAL; | |
92 | goto out; | |
93 | } | |
94 | ||
95 | if (iscsi_set_session_type(iscsi_lun->iscsi, ISCSI_SESSION_NORMAL) != 0) { | |
96 | log_err("iscsi: failed to set session type.\n"); | |
97 | ret = EINVAL; | |
98 | goto out; | |
99 | } | |
100 | ||
101 | if (iscsi_set_header_digest(iscsi_lun->iscsi, | |
102 | ISCSI_HEADER_DIGEST_NONE_CRC32C) != 0) { | |
103 | log_err("iscsi: failed to set header digest.\n"); | |
104 | ret = EINVAL; | |
105 | goto out; | |
106 | } | |
107 | ||
108 | if (iscsi_full_connect_sync(iscsi_lun->iscsi, | |
109 | iscsi_lun->url->portal, | |
110 | iscsi_lun->url->lun)) { | |
d6670be4 | 111 | log_err("iscsi: failed to connect to LUN : %s\n", |
247ef2aa KZ |
112 | iscsi_get_error(iscsi_lun->iscsi)); |
113 | ret = EINVAL; | |
114 | goto out; | |
115 | } | |
116 | ||
117 | task = iscsi_readcapacity16_sync(iscsi_lun->iscsi, iscsi_lun->url->lun); | |
118 | if (task == NULL || task->status != SCSI_STATUS_GOOD) { | |
b57f4fd4 KZ |
119 | log_err("iscsi: failed to send readcapacity command: %s\n", |
120 | iscsi_get_error(iscsi_lun->iscsi)); | |
247ef2aa KZ |
121 | ret = EINVAL; |
122 | goto out; | |
123 | } | |
124 | ||
125 | rc16 = scsi_datain_unmarshall(task); | |
126 | if (rc16 == NULL) { | |
127 | log_err("iscsi: failed to unmarshal readcapacity16 data.\n"); | |
128 | ret = EINVAL; | |
129 | goto out; | |
130 | } | |
131 | ||
132 | iscsi_lun->block_size = rc16->block_length; | |
133 | iscsi_lun->num_blocks = rc16->returned_lba + 1; | |
134 | ||
135 | scsi_free_scsi_task(task); | |
136 | task = NULL; | |
137 | ||
138 | f->real_file_size = iscsi_lun->num_blocks * iscsi_lun->block_size; | |
139 | f->engine_data = iscsi_lun; | |
140 | ||
141 | iscsi_info->luns[i] = iscsi_lun; | |
142 | iscsi_info->pfds[i].fd = iscsi_get_fd(iscsi_lun->iscsi); | |
143 | ||
144 | out: | |
145 | if (task) { | |
146 | scsi_free_scsi_task(task); | |
147 | } | |
148 | ||
149 | if (ret && iscsi_lun) { | |
150 | if (iscsi_lun->iscsi != NULL) { | |
151 | if (iscsi_is_logged_in(iscsi_lun->iscsi)) { | |
152 | iscsi_logout_sync(iscsi_lun->iscsi); | |
153 | } | |
154 | iscsi_destroy_context(iscsi_lun->iscsi); | |
155 | } | |
156 | free(iscsi_lun); | |
157 | } | |
158 | ||
159 | return ret; | |
160 | } | |
161 | ||
162 | static int fio_iscsi_setup(struct thread_data *td) | |
163 | { | |
164 | struct iscsi_options *options = td->eo; | |
165 | struct iscsi_info *iscsi_info = NULL; | |
166 | int ret = 0; | |
167 | struct fio_file *f; | |
168 | int i; | |
169 | ||
170 | iscsi_info = malloc(sizeof(struct iscsi_info)); | |
171 | iscsi_info->nr_luns = td->o.nr_files; | |
172 | iscsi_info->luns = calloc(iscsi_info->nr_luns, sizeof(struct iscsi_lun*)); | |
173 | iscsi_info->pfds = calloc(iscsi_info->nr_luns, sizeof(struct pollfd)); | |
174 | ||
175 | iscsi_info->nr_events = 0; | |
176 | iscsi_info->complete_events = calloc(td->o.iodepth, sizeof(struct iscsi_task*)); | |
177 | ||
178 | td->io_ops_data = iscsi_info; | |
179 | ||
180 | for_each_file(td, f, i) { | |
181 | ret = fio_iscsi_setup_lun(iscsi_info, options->initiator, f, i); | |
182 | if (ret < 0) break; | |
183 | } | |
184 | ||
185 | return ret; | |
186 | } | |
187 | ||
188 | static int fio_iscsi_init(struct thread_data *td) { | |
189 | return 0; | |
190 | } | |
191 | ||
192 | static void fio_iscsi_cleanup_lun(struct iscsi_lun *iscsi_lun) { | |
193 | if (iscsi_lun->iscsi != NULL) { | |
194 | if (iscsi_is_logged_in(iscsi_lun->iscsi)) { | |
195 | iscsi_logout_sync(iscsi_lun->iscsi); | |
196 | } | |
197 | iscsi_destroy_context(iscsi_lun->iscsi); | |
198 | } | |
199 | free(iscsi_lun); | |
200 | } | |
201 | ||
202 | static void fio_iscsi_cleanup(struct thread_data *td) | |
203 | { | |
204 | struct iscsi_info *iscsi_info = td->io_ops_data; | |
205 | ||
206 | for (int i = 0; i < iscsi_info->nr_luns; i++) { | |
207 | if (iscsi_info->luns[i]) { | |
208 | fio_iscsi_cleanup_lun(iscsi_info->luns[i]); | |
209 | iscsi_info->luns[i] = NULL; | |
210 | } | |
211 | } | |
212 | ||
213 | free(iscsi_info->luns); | |
214 | free(iscsi_info->pfds); | |
215 | free(iscsi_info->complete_events); | |
216 | free(iscsi_info); | |
217 | } | |
218 | ||
219 | static int fio_iscsi_prep(struct thread_data *td, struct io_u *io_u) | |
220 | { | |
221 | return 0; | |
222 | } | |
223 | ||
224 | static int fio_iscsi_open_file(struct thread_data *td, struct fio_file *f) | |
225 | { | |
226 | return 0; | |
227 | } | |
228 | ||
229 | static int fio_iscsi_close_file(struct thread_data *td, struct fio_file *f) | |
230 | { | |
231 | return 0; | |
232 | } | |
233 | ||
234 | static void iscsi_cb(struct iscsi_context *iscsi, int status, | |
235 | void *command_data, void *private_data) | |
236 | { | |
237 | struct iscsi_task *iscsi_task = (struct iscsi_task*)private_data; | |
238 | struct iscsi_lun *iscsi_lun = iscsi_task->iscsi_lun; | |
239 | struct iscsi_info *iscsi_info = iscsi_lun->iscsi_info; | |
240 | struct io_u *io_u = iscsi_task->io_u; | |
241 | ||
242 | if (status == SCSI_STATUS_GOOD) { | |
243 | io_u->error = 0; | |
244 | } else { | |
245 | log_err("iscsi: request failed with error %s.\n", | |
246 | iscsi_get_error(iscsi_lun->iscsi)); | |
247 | ||
248 | io_u->error = 1; | |
249 | io_u->resid = io_u->xfer_buflen; | |
250 | } | |
251 | ||
252 | iscsi_info->complete_events[iscsi_info->nr_events] = iscsi_task; | |
253 | iscsi_info->nr_events++; | |
254 | } | |
255 | ||
256 | static enum fio_q_status fio_iscsi_queue(struct thread_data *td, | |
257 | struct io_u *io_u) | |
258 | { | |
259 | struct iscsi_lun *iscsi_lun = io_u->file->engine_data; | |
260 | struct scsi_task *scsi_task = NULL; | |
261 | struct iscsi_task *iscsi_task = malloc(sizeof(struct iscsi_task)); | |
262 | int ret = -1; | |
263 | ||
264 | if (io_u->ddir == DDIR_READ || io_u->ddir == DDIR_WRITE) { | |
265 | if (io_u->offset % iscsi_lun->block_size != 0) { | |
266 | log_err("iscsi: offset is not align to block size.\n"); | |
267 | ret = -1; | |
268 | goto out; | |
269 | } | |
270 | ||
271 | if (io_u->xfer_buflen % iscsi_lun->block_size != 0) { | |
272 | log_err("iscsi: buflen is not align to block size.\n"); | |
273 | ret = -1; | |
274 | goto out; | |
275 | } | |
276 | } | |
277 | ||
278 | if (io_u->ddir == DDIR_READ) { | |
279 | scsi_task = scsi_cdb_read16(io_u->offset / iscsi_lun->block_size, | |
280 | io_u->xfer_buflen, | |
281 | iscsi_lun->block_size, | |
282 | 0, 0, 0, 0, 0); | |
283 | ret = scsi_task_add_data_in_buffer(scsi_task, io_u->xfer_buflen, | |
284 | io_u->xfer_buf); | |
285 | if (ret < 0) { | |
286 | log_err("iscsi: failed to add data in buffer.\n"); | |
287 | goto out; | |
288 | } | |
289 | } else if (io_u->ddir == DDIR_WRITE) { | |
290 | scsi_task = scsi_cdb_write16(io_u->offset / iscsi_lun->block_size, | |
291 | io_u->xfer_buflen, | |
292 | iscsi_lun->block_size, | |
293 | 0, 0, 0, 0, 0); | |
294 | ret = scsi_task_add_data_out_buffer(scsi_task, io_u->xfer_buflen, | |
295 | io_u->xfer_buf); | |
296 | if (ret < 0) { | |
297 | log_err("iscsi: failed to add data out buffer.\n"); | |
298 | goto out; | |
299 | } | |
300 | } else if (ddir_sync(io_u->ddir)) { | |
301 | scsi_task = scsi_cdb_synchronizecache16( | |
302 | 0, iscsi_lun->num_blocks * iscsi_lun->block_size, 0, 0); | |
303 | } else { | |
304 | log_err("iscsi: invalid I/O operation: %d\n", io_u->ddir); | |
305 | ret = EINVAL; | |
306 | goto out; | |
307 | } | |
308 | ||
309 | iscsi_task->scsi_task = scsi_task; | |
310 | iscsi_task->iscsi_lun = iscsi_lun; | |
311 | iscsi_task->io_u = io_u; | |
312 | ||
313 | ret = iscsi_scsi_command_async(iscsi_lun->iscsi, iscsi_lun->url->lun, | |
314 | scsi_task, iscsi_cb, NULL, iscsi_task); | |
315 | if (ret < 0) { | |
316 | log_err("iscsi: failed to send scsi command.\n"); | |
317 | goto out; | |
318 | } | |
319 | ||
320 | return FIO_Q_QUEUED; | |
321 | ||
322 | out: | |
323 | if (iscsi_task) { | |
324 | free(iscsi_task); | |
325 | } | |
326 | ||
327 | if (scsi_task) { | |
328 | scsi_free_scsi_task(scsi_task); | |
329 | } | |
330 | ||
331 | if (ret) { | |
332 | io_u->error = ret; | |
333 | } | |
334 | return FIO_Q_COMPLETED; | |
335 | } | |
336 | ||
337 | static int fio_iscsi_getevents(struct thread_data *td, unsigned int min, | |
338 | unsigned int max, const struct timespec *t) | |
339 | { | |
340 | struct iscsi_info *iscsi_info = td->io_ops_data; | |
341 | int ret = 0; | |
342 | ||
343 | iscsi_info->nr_events = 0; | |
344 | ||
345 | while (iscsi_info->nr_events < min) { | |
346 | for (int i = 0; i < iscsi_info->nr_luns; i++) { | |
347 | int events = iscsi_which_events(iscsi_info->luns[i]->iscsi); | |
348 | iscsi_info->pfds[i].events = events; | |
349 | } | |
350 | ||
351 | ret = poll(iscsi_info->pfds, iscsi_info->nr_luns, -1); | |
352 | if (ret < 0) { | |
576d4cf9 KZ |
353 | if (errno == EINTR || errno == EAGAIN) { |
354 | continue; | |
355 | } | |
247ef2aa KZ |
356 | log_err("iscsi: failed to poll events: %s.\n", |
357 | strerror(errno)); | |
358 | break; | |
359 | } | |
360 | ||
361 | for (int i = 0; i < iscsi_info->nr_luns; i++) { | |
362 | ret = iscsi_service(iscsi_info->luns[i]->iscsi, | |
363 | iscsi_info->pfds[i].revents); | |
364 | assert(ret >= 0); | |
365 | } | |
366 | } | |
367 | ||
368 | return ret < 0 ? ret : iscsi_info->nr_events; | |
369 | } | |
370 | ||
371 | static struct io_u *fio_iscsi_event(struct thread_data *td, int event) | |
372 | { | |
373 | struct iscsi_info *iscsi_info = (struct iscsi_info*)td->io_ops_data; | |
374 | struct iscsi_task *iscsi_task = iscsi_info->complete_events[event]; | |
375 | struct io_u *io_u = iscsi_task->io_u; | |
376 | ||
377 | iscsi_info->complete_events[event] = NULL; | |
378 | ||
379 | scsi_free_scsi_task(iscsi_task->scsi_task); | |
380 | free(iscsi_task); | |
381 | ||
382 | return io_u; | |
383 | } | |
384 | ||
5a8a6a03 | 385 | FIO_STATIC struct ioengine_ops ioengine = { |
247ef2aa KZ |
386 | .name = "libiscsi", |
387 | .version = FIO_IOOPS_VERSION, | |
388 | .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NODISKUTIL, | |
389 | .setup = fio_iscsi_setup, | |
390 | .init = fio_iscsi_init, | |
391 | .prep = fio_iscsi_prep, | |
392 | .queue = fio_iscsi_queue, | |
393 | .getevents = fio_iscsi_getevents, | |
394 | .event = fio_iscsi_event, | |
395 | .cleanup = fio_iscsi_cleanup, | |
396 | .open_file = fio_iscsi_open_file, | |
397 | .close_file = fio_iscsi_close_file, | |
398 | .option_struct_size = sizeof(struct iscsi_options), | |
399 | .options = options, | |
400 | }; | |
401 | ||
402 | static void fio_init fio_iscsi_register(void) | |
403 | { | |
5a8a6a03 | 404 | register_ioengine(&ioengine); |
247ef2aa KZ |
405 | } |
406 | ||
407 | static void fio_exit fio_iscsi_unregister(void) | |
408 | { | |
5a8a6a03 | 409 | unregister_ioengine(&ioengine); |
247ef2aa | 410 | } |