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