]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #ifndef CEPH_LIBRBD_IO_OBJECT_REQUEST_H | |
5 | #define CEPH_LIBRBD_IO_OBJECT_REQUEST_H | |
6 | ||
7 | #include "include/int_types.h" | |
7c673cae FG |
8 | #include "include/buffer.h" |
9 | #include "include/rados/librados.hpp" | |
31f18b77 FG |
10 | #include "common/snap_types.h" |
11 | #include "common/zipkin_trace.h" | |
7c673cae | 12 | #include "librbd/ObjectMap.h" |
31f18b77 | 13 | #include <map> |
7c673cae FG |
14 | |
15 | class Context; | |
16 | ||
17 | namespace librbd { | |
18 | ||
19 | struct ImageCtx; | |
20 | ||
21 | namespace io { | |
22 | ||
23 | struct AioCompletion; | |
24 | class CopyupRequest; | |
25 | class ObjectRemoveRequest; | |
26 | class ObjectTruncateRequest; | |
27 | class ObjectWriteRequest; | |
28 | class ObjectZeroRequest; | |
29 | ||
30 | struct ObjectRequestHandle { | |
31 | virtual ~ObjectRequestHandle() { | |
32 | } | |
33 | ||
34 | virtual void complete(int r) = 0; | |
35 | virtual void send() = 0; | |
36 | }; | |
37 | ||
38 | /** | |
39 | * This class represents an I/O operation to a single RBD data object. | |
40 | * Its subclasses encapsulate logic for dealing with special cases | |
41 | * for I/O due to layering. | |
42 | */ | |
43 | template <typename ImageCtxT = ImageCtx> | |
44 | class ObjectRequest : public ObjectRequestHandle { | |
45 | public: | |
46 | typedef std::vector<std::pair<uint64_t, uint64_t> > Extents; | |
47 | ||
48 | static ObjectRequest* create_remove(ImageCtxT *ictx, | |
49 | const std::string &oid, | |
50 | uint64_t object_no, | |
51 | const ::SnapContext &snapc, | |
31f18b77 | 52 | const ZTracer::Trace &parent_trace, |
7c673cae FG |
53 | Context *completion); |
54 | static ObjectRequest* create_truncate(ImageCtxT *ictx, | |
55 | const std::string &oid, | |
56 | uint64_t object_no, | |
57 | uint64_t object_off, | |
58 | const ::SnapContext &snapc, | |
31f18b77 | 59 | const ZTracer::Trace &parent_trace, |
7c673cae FG |
60 | Context *completion); |
61 | static ObjectRequest* create_write(ImageCtxT *ictx, const std::string &oid, | |
62 | uint64_t object_no, | |
63 | uint64_t object_off, | |
64 | const ceph::bufferlist &data, | |
31f18b77 FG |
65 | const ::SnapContext &snapc, int op_flags, |
66 | const ZTracer::Trace &parent_trace, | |
67 | Context *completion); | |
7c673cae FG |
68 | static ObjectRequest* create_zero(ImageCtxT *ictx, const std::string &oid, |
69 | uint64_t object_no, uint64_t object_off, | |
70 | uint64_t object_len, | |
71 | const ::SnapContext &snapc, | |
31f18b77 | 72 | const ZTracer::Trace &parent_trace, |
7c673cae FG |
73 | Context *completion); |
74 | static ObjectRequest* create_writesame(ImageCtxT *ictx, | |
75 | const std::string &oid, | |
76 | uint64_t object_no, | |
77 | uint64_t object_off, | |
78 | uint64_t object_len, | |
79 | const ceph::bufferlist &data, | |
80 | const ::SnapContext &snapc, | |
31f18b77 FG |
81 | int op_flags, |
82 | const ZTracer::Trace &parent_trace, | |
83 | Context *completion); | |
c07f9fc5 FG |
84 | static ObjectRequest* create_compare_and_write(ImageCtxT *ictx, |
85 | const std::string &oid, | |
86 | uint64_t object_no, | |
87 | uint64_t object_off, | |
88 | const ceph::bufferlist &cmp_data, | |
89 | const ceph::bufferlist &write_data, | |
90 | const ::SnapContext &snapc, | |
91 | uint64_t *mismatch_offset, int op_flags, | |
92 | const ZTracer::Trace &parent_trace, | |
93 | Context *completion); | |
7c673cae FG |
94 | |
95 | ObjectRequest(ImageCtx *ictx, const std::string &oid, | |
96 | uint64_t objectno, uint64_t off, uint64_t len, | |
31f18b77 FG |
97 | librados::snap_t snap_id, bool hide_enoent, |
98 | const char *trace_name, const ZTracer::Trace &parent_trace, | |
99 | Context *completion); | |
100 | ~ObjectRequest() override { | |
101 | m_trace.event("finish"); | |
102 | } | |
7c673cae | 103 | |
31f18b77 FG |
104 | virtual void add_copyup_ops(librados::ObjectWriteOperation *wr, |
105 | bool set_hints) { | |
106 | }; | |
7c673cae | 107 | |
c07f9fc5 | 108 | virtual void complete(int r); |
7c673cae FG |
109 | |
110 | virtual bool should_complete(int r) = 0; | |
111 | void send() override = 0; | |
112 | ||
113 | bool has_parent() const { | |
114 | return m_has_parent; | |
115 | } | |
116 | ||
117 | virtual bool is_op_payload_empty() const { | |
118 | return false; | |
119 | } | |
120 | ||
121 | virtual const char *get_op_type() const = 0; | |
122 | virtual bool pre_object_map_update(uint8_t *new_state) = 0; | |
123 | ||
124 | protected: | |
125 | bool compute_parent_extents(); | |
126 | ||
127 | ImageCtx *m_ictx; | |
128 | std::string m_oid; | |
129 | uint64_t m_object_no, m_object_off, m_object_len; | |
130 | librados::snap_t m_snap_id; | |
131 | Context *m_completion; | |
132 | Extents m_parent_extents; | |
133 | bool m_hide_enoent; | |
31f18b77 | 134 | ZTracer::Trace m_trace; |
7c673cae FG |
135 | |
136 | private: | |
137 | bool m_has_parent = false; | |
138 | }; | |
139 | ||
140 | template <typename ImageCtxT = ImageCtx> | |
141 | class ObjectReadRequest : public ObjectRequest<ImageCtxT> { | |
142 | public: | |
143 | typedef std::vector<std::pair<uint64_t, uint64_t> > Extents; | |
144 | typedef std::map<uint64_t, uint64_t> ExtentMap; | |
145 | ||
146 | static ObjectReadRequest* create(ImageCtxT *ictx, const std::string &oid, | |
147 | uint64_t objectno, uint64_t offset, | |
148 | uint64_t len, Extents &buffer_extents, | |
149 | librados::snap_t snap_id, bool sparse, | |
31f18b77 FG |
150 | int op_flags, |
151 | const ZTracer::Trace &parent_trace, | |
152 | Context *completion) { | |
7c673cae | 153 | return new ObjectReadRequest(ictx, oid, objectno, offset, len, |
31f18b77 FG |
154 | buffer_extents, snap_id, sparse, op_flags, |
155 | parent_trace, completion); | |
7c673cae FG |
156 | } |
157 | ||
158 | ObjectReadRequest(ImageCtxT *ictx, const std::string &oid, | |
159 | uint64_t objectno, uint64_t offset, uint64_t len, | |
160 | Extents& buffer_extents, librados::snap_t snap_id, | |
31f18b77 FG |
161 | bool sparse, int op_flags, |
162 | const ZTracer::Trace &parent_trace, Context *completion); | |
7c673cae FG |
163 | |
164 | bool should_complete(int r) override; | |
165 | void send() override; | |
166 | void guard_read(); | |
167 | ||
168 | inline uint64_t get_offset() const { | |
169 | return this->m_object_off; | |
170 | } | |
171 | inline uint64_t get_length() const { | |
172 | return this->m_object_len; | |
173 | } | |
174 | ceph::bufferlist &data() { | |
175 | return m_read_data; | |
176 | } | |
177 | const Extents &get_buffer_extents() const { | |
178 | return m_buffer_extents; | |
179 | } | |
180 | ExtentMap &get_extent_map() { | |
181 | return m_ext_map; | |
182 | } | |
183 | ||
184 | const char *get_op_type() const override { | |
185 | return "read"; | |
186 | } | |
187 | ||
188 | bool pre_object_map_update(uint8_t *new_state) override { | |
189 | return false; | |
190 | } | |
191 | ||
192 | private: | |
193 | Extents m_buffer_extents; | |
194 | bool m_tried_parent; | |
195 | bool m_sparse; | |
196 | int m_op_flags; | |
197 | ceph::bufferlist m_read_data; | |
198 | ExtentMap m_ext_map; | |
199 | ||
200 | /** | |
201 | * Reads go through the following state machine to deal with | |
202 | * layering: | |
203 | * | |
204 | * need copyup | |
205 | * LIBRBD_AIO_READ_GUARD ---------------> LIBRBD_AIO_READ_COPYUP | |
206 | * | | | |
207 | * v | | |
208 | * done <------------------------------------/ | |
209 | * ^ | |
210 | * | | |
211 | * LIBRBD_AIO_READ_FLAT | |
212 | * | |
213 | * Reads start in LIBRBD_AIO_READ_GUARD or _FLAT, depending on | |
214 | * whether there is a parent or not. | |
215 | */ | |
216 | enum read_state_d { | |
217 | LIBRBD_AIO_READ_GUARD, | |
218 | LIBRBD_AIO_READ_COPYUP, | |
219 | LIBRBD_AIO_READ_FLAT | |
220 | }; | |
221 | ||
222 | read_state_d m_state; | |
223 | ||
224 | void send_copyup(); | |
225 | ||
226 | void read_from_parent(Extents&& image_extents); | |
227 | }; | |
228 | ||
229 | class AbstractObjectWriteRequest : public ObjectRequest<> { | |
230 | public: | |
231 | AbstractObjectWriteRequest(ImageCtx *ictx, const std::string &oid, | |
232 | uint64_t object_no, uint64_t object_off, | |
233 | uint64_t len, const ::SnapContext &snapc, | |
31f18b77 FG |
234 | bool hide_enoent, const char *trace_name, |
235 | const ZTracer::Trace &parent_trace, | |
236 | Context *completion); | |
7c673cae | 237 | |
31f18b77 FG |
238 | void add_copyup_ops(librados::ObjectWriteOperation *wr, |
239 | bool set_hints) override | |
7c673cae | 240 | { |
31f18b77 | 241 | add_write_ops(wr, set_hints); |
7c673cae FG |
242 | } |
243 | ||
244 | bool should_complete(int r) override; | |
245 | void send() override; | |
246 | ||
247 | /** | |
248 | * Writes go through the following state machine to deal with | |
249 | * layering and the object map: | |
250 | * | |
251 | * <start> | |
252 | * | | |
253 | * |\ | |
254 | * | \ -or- | |
255 | * | ---------------------------------> LIBRBD_AIO_WRITE_PRE | |
256 | * | . | | |
257 | * | . | | |
258 | * | . v | |
259 | * | . . . . > LIBRBD_AIO_WRITE_FLAT. . . | |
260 | * | | . | |
261 | * | | . | |
262 | * | | . | |
263 | * v need copyup (copyup performs pre) | . | |
264 | * LIBRBD_AIO_WRITE_GUARD -----------> LIBRBD_AIO_WRITE_COPYUP | . | |
265 | * . | | . | . | |
266 | * . | | . | . | |
267 | * . | /-----/ . | . | |
268 | * . | | . | . | |
269 | * . \-------------------\ | /-------------------/ . | |
270 | * . | | | . . | |
271 | * . v v v . . | |
272 | * . LIBRBD_AIO_WRITE_POST . . | |
273 | * . | . . | |
274 | * . | . . . . . . . . . | |
275 | * . | . . | |
276 | * . v v . | |
277 | * . . . . . . . . . . . . . . > <finish> < . . . . . . . . . . . . . . | |
278 | * | |
279 | * The _PRE/_POST states are skipped if the object map is disabled. | |
280 | * The write starts in _WRITE_GUARD or _FLAT depending on whether or not | |
281 | * there is a parent overlap. | |
282 | */ | |
283 | protected: | |
284 | enum write_state_d { | |
285 | LIBRBD_AIO_WRITE_GUARD, | |
286 | LIBRBD_AIO_WRITE_COPYUP, | |
287 | LIBRBD_AIO_WRITE_FLAT, | |
288 | LIBRBD_AIO_WRITE_PRE, | |
289 | LIBRBD_AIO_WRITE_POST, | |
290 | LIBRBD_AIO_WRITE_ERROR | |
291 | }; | |
292 | ||
293 | write_state_d m_state; | |
294 | librados::ObjectWriteOperation m_write; | |
295 | uint64_t m_snap_seq; | |
296 | std::vector<librados::snap_t> m_snaps; | |
297 | bool m_object_exist; | |
298 | bool m_guard = true; | |
299 | ||
31f18b77 FG |
300 | virtual void add_write_ops(librados::ObjectWriteOperation *wr, |
301 | bool set_hints) = 0; | |
7c673cae FG |
302 | virtual void guard_write(); |
303 | virtual bool post_object_map_update() { | |
304 | return false; | |
305 | } | |
306 | virtual void send_write(); | |
307 | virtual void send_write_op(); | |
308 | virtual void handle_write_guard(); | |
309 | ||
310 | void send_pre_object_map_update(); | |
311 | ||
312 | private: | |
313 | bool send_post_object_map_update(); | |
314 | void send_copyup(); | |
315 | }; | |
316 | ||
317 | class ObjectWriteRequest : public AbstractObjectWriteRequest { | |
318 | public: | |
319 | ObjectWriteRequest(ImageCtx *ictx, const std::string &oid, uint64_t object_no, | |
320 | uint64_t object_off, const ceph::bufferlist &data, | |
31f18b77 FG |
321 | const ::SnapContext &snapc, int op_flags, |
322 | const ZTracer::Trace &parent_trace, Context *completion) | |
7c673cae | 323 | : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, |
31f18b77 FG |
324 | data.length(), snapc, false, "write", |
325 | parent_trace, completion), | |
7c673cae FG |
326 | m_write_data(data), m_op_flags(op_flags) { |
327 | } | |
328 | ||
329 | bool is_op_payload_empty() const override { | |
330 | return (m_write_data.length() == 0); | |
331 | } | |
332 | ||
333 | const char *get_op_type() const override { | |
334 | return "write"; | |
335 | } | |
336 | ||
337 | bool pre_object_map_update(uint8_t *new_state) override { | |
338 | *new_state = OBJECT_EXISTS; | |
339 | return true; | |
340 | } | |
341 | ||
342 | protected: | |
31f18b77 FG |
343 | void add_write_ops(librados::ObjectWriteOperation *wr, |
344 | bool set_hints) override; | |
7c673cae FG |
345 | |
346 | void send_write() override; | |
347 | ||
348 | private: | |
349 | ceph::bufferlist m_write_data; | |
350 | int m_op_flags; | |
351 | }; | |
352 | ||
353 | class ObjectRemoveRequest : public AbstractObjectWriteRequest { | |
354 | public: | |
355 | ObjectRemoveRequest(ImageCtx *ictx, const std::string &oid, | |
356 | uint64_t object_no, const ::SnapContext &snapc, | |
31f18b77 FG |
357 | const ZTracer::Trace &parent_trace, Context *completion) |
358 | : AbstractObjectWriteRequest(ictx, oid, object_no, 0, 0, snapc, true, | |
359 | "remote", parent_trace, completion), | |
7c673cae FG |
360 | m_object_state(OBJECT_NONEXISTENT) { |
361 | } | |
362 | ||
363 | const char* get_op_type() const override { | |
364 | if (has_parent()) { | |
365 | return "remove (trunc)"; | |
366 | } | |
367 | return "remove"; | |
368 | } | |
369 | ||
370 | bool pre_object_map_update(uint8_t *new_state) override { | |
371 | if (has_parent()) { | |
372 | m_object_state = OBJECT_EXISTS; | |
373 | } else { | |
374 | m_object_state = OBJECT_PENDING; | |
375 | } | |
376 | *new_state = m_object_state; | |
377 | return true; | |
378 | } | |
379 | ||
380 | bool post_object_map_update() override { | |
381 | if (m_object_state == OBJECT_EXISTS) { | |
382 | return false; | |
383 | } | |
384 | return true; | |
385 | } | |
386 | ||
387 | void guard_write() override; | |
388 | void send_write() override; | |
389 | ||
390 | protected: | |
31f18b77 FG |
391 | void add_write_ops(librados::ObjectWriteOperation *wr, |
392 | bool set_hints) override { | |
7c673cae FG |
393 | if (has_parent()) { |
394 | wr->truncate(0); | |
395 | } else { | |
396 | wr->remove(); | |
397 | } | |
398 | } | |
399 | ||
400 | private: | |
401 | uint8_t m_object_state; | |
402 | }; | |
403 | ||
404 | class ObjectTrimRequest : public AbstractObjectWriteRequest { | |
405 | public: | |
406 | // we'd need to only conditionally specify if a post object map | |
407 | // update is needed. pre update is decided as usual (by checking | |
408 | // the state of the object in the map). | |
409 | ObjectTrimRequest(ImageCtx *ictx, const std::string &oid, uint64_t object_no, | |
31f18b77 FG |
410 | const ::SnapContext &snapc, bool post_object_map_update, |
411 | Context *completion) | |
412 | : AbstractObjectWriteRequest(ictx, oid, object_no, 0, 0, snapc, true, | |
413 | "trim", {}, completion), | |
7c673cae FG |
414 | m_post_object_map_update(post_object_map_update) { |
415 | } | |
416 | ||
417 | const char* get_op_type() const override { | |
418 | return "remove (trim)"; | |
419 | } | |
420 | ||
421 | bool pre_object_map_update(uint8_t *new_state) override { | |
422 | *new_state = OBJECT_PENDING; | |
423 | return true; | |
424 | } | |
425 | ||
426 | bool post_object_map_update() override { | |
427 | return m_post_object_map_update; | |
428 | } | |
429 | ||
430 | protected: | |
31f18b77 FG |
431 | void add_write_ops(librados::ObjectWriteOperation *wr, |
432 | bool set_hints) override { | |
7c673cae FG |
433 | wr->remove(); |
434 | } | |
435 | ||
436 | private: | |
437 | bool m_post_object_map_update; | |
438 | }; | |
439 | ||
440 | class ObjectTruncateRequest : public AbstractObjectWriteRequest { | |
441 | public: | |
442 | ObjectTruncateRequest(ImageCtx *ictx, const std::string &oid, | |
443 | uint64_t object_no, uint64_t object_off, | |
31f18b77 FG |
444 | const ::SnapContext &snapc, |
445 | const ZTracer::Trace &parent_trace, Context *completion) | |
7c673cae | 446 | : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, 0, snapc, |
31f18b77 | 447 | true, "truncate", parent_trace, completion) { |
7c673cae FG |
448 | } |
449 | ||
450 | const char* get_op_type() const override { | |
451 | return "truncate"; | |
452 | } | |
453 | ||
454 | bool pre_object_map_update(uint8_t *new_state) override { | |
455 | if (!m_object_exist && !has_parent()) | |
456 | *new_state = OBJECT_NONEXISTENT; | |
457 | else | |
458 | *new_state = OBJECT_EXISTS; | |
459 | return true; | |
460 | } | |
461 | ||
462 | void send_write() override; | |
463 | ||
464 | protected: | |
31f18b77 FG |
465 | void add_write_ops(librados::ObjectWriteOperation *wr, |
466 | bool set_hints) override { | |
7c673cae FG |
467 | wr->truncate(m_object_off); |
468 | } | |
469 | }; | |
470 | ||
471 | class ObjectZeroRequest : public AbstractObjectWriteRequest { | |
472 | public: | |
473 | ObjectZeroRequest(ImageCtx *ictx, const std::string &oid, uint64_t object_no, | |
474 | uint64_t object_off, uint64_t object_len, | |
31f18b77 FG |
475 | const ::SnapContext &snapc, |
476 | const ZTracer::Trace &parent_trace, Context *completion) | |
7c673cae | 477 | : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, object_len, |
31f18b77 FG |
478 | snapc, true, "zero", parent_trace, |
479 | completion) { | |
7c673cae FG |
480 | } |
481 | ||
482 | const char* get_op_type() const override { | |
483 | return "zero"; | |
484 | } | |
485 | ||
486 | bool pre_object_map_update(uint8_t *new_state) override { | |
487 | *new_state = OBJECT_EXISTS; | |
488 | return true; | |
489 | } | |
490 | ||
31f18b77 FG |
491 | void send_write() override; |
492 | ||
7c673cae | 493 | protected: |
31f18b77 FG |
494 | void add_write_ops(librados::ObjectWriteOperation *wr, |
495 | bool set_hints) override { | |
7c673cae FG |
496 | wr->zero(m_object_off, m_object_len); |
497 | } | |
498 | }; | |
499 | ||
500 | class ObjectWriteSameRequest : public AbstractObjectWriteRequest { | |
501 | public: | |
31f18b77 FG |
502 | ObjectWriteSameRequest(ImageCtx *ictx, const std::string &oid, |
503 | uint64_t object_no, uint64_t object_off, | |
504 | uint64_t object_len, const ceph::bufferlist &data, | |
505 | const ::SnapContext &snapc, int op_flags, | |
506 | const ZTracer::Trace &parent_trace, | |
507 | Context *completion) | |
7c673cae | 508 | : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, |
31f18b77 FG |
509 | object_len, snapc, false, "writesame", |
510 | parent_trace, completion), | |
7c673cae FG |
511 | m_write_data(data), m_op_flags(op_flags) { |
512 | } | |
513 | ||
514 | const char *get_op_type() const override { | |
515 | return "writesame"; | |
516 | } | |
517 | ||
518 | bool pre_object_map_update(uint8_t *new_state) override { | |
519 | *new_state = OBJECT_EXISTS; | |
520 | return true; | |
521 | } | |
522 | ||
523 | protected: | |
31f18b77 FG |
524 | void add_write_ops(librados::ObjectWriteOperation *wr, |
525 | bool set_hints) override; | |
7c673cae FG |
526 | |
527 | void send_write() override; | |
528 | ||
529 | private: | |
530 | ceph::bufferlist m_write_data; | |
531 | int m_op_flags; | |
532 | }; | |
533 | ||
c07f9fc5 FG |
534 | class ObjectCompareAndWriteRequest : public AbstractObjectWriteRequest { |
535 | public: | |
536 | typedef std::vector<std::pair<uint64_t, uint64_t> > Extents; | |
537 | ||
538 | ObjectCompareAndWriteRequest(ImageCtx *ictx, const std::string &oid, | |
539 | uint64_t object_no, uint64_t object_off, | |
540 | const ceph::bufferlist &cmp_bl, | |
541 | const ceph::bufferlist &write_bl, | |
542 | const ::SnapContext &snapc, | |
543 | uint64_t *mismatch_offset, int op_flags, | |
544 | const ZTracer::Trace &parent_trace, | |
545 | Context *completion) | |
546 | : AbstractObjectWriteRequest(ictx, oid, object_no, object_off, | |
547 | cmp_bl.length(), snapc, false, "compare_and_write", | |
548 | parent_trace, completion), | |
549 | m_cmp_bl(cmp_bl), m_write_bl(write_bl), | |
550 | m_mismatch_offset(mismatch_offset), m_op_flags(op_flags) { | |
551 | } | |
552 | ||
553 | const char *get_op_type() const override { | |
554 | return "compare_and_write"; | |
555 | } | |
556 | ||
557 | bool pre_object_map_update(uint8_t *new_state) override { | |
558 | *new_state = OBJECT_EXISTS; | |
559 | return true; | |
560 | } | |
561 | ||
562 | void complete(int r) override; | |
563 | protected: | |
564 | void add_write_ops(librados::ObjectWriteOperation *wr, | |
565 | bool set_hints) override; | |
566 | ||
567 | void send_write() override; | |
568 | ||
569 | private: | |
570 | ceph::bufferlist m_cmp_bl; | |
571 | ceph::bufferlist m_write_bl; | |
572 | uint64_t *m_mismatch_offset; | |
573 | int m_op_flags; | |
574 | }; | |
575 | ||
7c673cae FG |
576 | } // namespace io |
577 | } // namespace librbd | |
578 | ||
579 | extern template class librbd::io::ObjectRequest<librbd::ImageCtx>; | |
580 | extern template class librbd::io::ObjectReadRequest<librbd::ImageCtx>; | |
581 | ||
582 | #endif // CEPH_LIBRBD_IO_OBJECT_REQUEST_H |