]> git.proxmox.com Git - ceph.git/blob - ceph/src/librbd/operation/DisableFeaturesRequest.cc
bump version to 18.2.2-pve1
[ceph.git] / ceph / src / librbd / operation / DisableFeaturesRequest.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "librbd/operation/DisableFeaturesRequest.h"
5 #include "common/dout.h"
6 #include "common/errno.h"
7 #include "cls/rbd/cls_rbd_client.h"
8 #include "librbd/ExclusiveLock.h"
9 #include "librbd/ImageCtx.h"
10 #include "librbd/ImageState.h"
11 #include "librbd/Journal.h"
12 #include "librbd/Utils.h"
13 #include "librbd/image/SetFlagsRequest.h"
14 #include "librbd/io/ImageDispatcherInterface.h"
15 #include "librbd/journal/RemoveRequest.h"
16 #include "librbd/journal/TypeTraits.h"
17 #include "librbd/mirror/DisableRequest.h"
18 #include "librbd/object_map/RemoveRequest.h"
19
20 #define dout_subsys ceph_subsys_rbd
21 #undef dout_prefix
22 #define dout_prefix *_dout << "librbd::DisableFeaturesRequest: "
23
24 namespace librbd {
25 namespace operation {
26
27 using util::create_async_context_callback;
28 using util::create_context_callback;
29 using util::create_rados_callback;
30
31 template <typename I>
32 DisableFeaturesRequest<I>::DisableFeaturesRequest(I &image_ctx,
33 Context *on_finish,
34 uint64_t journal_op_tid,
35 uint64_t features,
36 bool force)
37 : Request<I>(image_ctx, on_finish, journal_op_tid), m_features(features),
38 m_force(force) {
39 }
40
41 template <typename I>
42 void DisableFeaturesRequest<I>::send_op() {
43 I &image_ctx = this->m_image_ctx;
44 CephContext *cct = image_ctx.cct;
45 ceph_assert(ceph_mutex_is_locked(image_ctx.owner_lock));
46
47 ldout(cct, 20) << this << " " << __func__ << ": features=" << m_features
48 << dendl;
49
50 send_prepare_lock();
51 }
52
53 template <typename I>
54 bool DisableFeaturesRequest<I>::should_complete(int r) {
55 I &image_ctx = this->m_image_ctx;
56 CephContext *cct = image_ctx.cct;
57 ldout(cct, 20) << this << " " << __func__ << " r=" << r << dendl;
58
59 if (r < 0) {
60 lderr(cct) << "encountered error: " << cpp_strerror(r) << dendl;
61 }
62 return true;
63 }
64
65 template <typename I>
66 void DisableFeaturesRequest<I>::send_prepare_lock() {
67 I &image_ctx = this->m_image_ctx;
68 CephContext *cct = image_ctx.cct;
69 ldout(cct, 20) << this << " " << __func__ << dendl;
70
71 image_ctx.state->prepare_lock(create_async_context_callback(
72 image_ctx, create_context_callback<
73 DisableFeaturesRequest<I>,
74 &DisableFeaturesRequest<I>::handle_prepare_lock>(this)));
75 }
76
77 template <typename I>
78 Context *DisableFeaturesRequest<I>::handle_prepare_lock(int *result) {
79 I &image_ctx = this->m_image_ctx;
80 CephContext *cct = image_ctx.cct;
81 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
82
83 if (*result < 0) {
84 lderr(cct) << "failed to lock image: " << cpp_strerror(*result) << dendl;
85 return this->create_context_finisher(*result);
86 }
87
88 send_block_writes();
89 return nullptr;
90 }
91
92 template <typename I>
93 void DisableFeaturesRequest<I>::send_block_writes() {
94 I &image_ctx = this->m_image_ctx;
95 CephContext *cct = image_ctx.cct;
96 ldout(cct, 20) << this << " " << __func__ << dendl;
97
98 std::unique_lock locker{image_ctx.owner_lock};
99 image_ctx.io_image_dispatcher->block_writes(create_context_callback<
100 DisableFeaturesRequest<I>,
101 &DisableFeaturesRequest<I>::handle_block_writes>(this));
102 }
103
104 template <typename I>
105 Context *DisableFeaturesRequest<I>::handle_block_writes(int *result) {
106 I &image_ctx = this->m_image_ctx;
107 CephContext *cct = image_ctx.cct;
108 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
109
110 if (*result < 0) {
111 lderr(cct) << "failed to block writes: " << cpp_strerror(*result) << dendl;
112 return handle_finish(*result);
113 }
114 m_writes_blocked = true;
115
116 {
117 std::unique_lock locker{image_ctx.owner_lock};
118 // avoid accepting new requests from peers while we manipulate
119 // the image features
120 if (image_ctx.exclusive_lock != nullptr &&
121 (image_ctx.journal == nullptr ||
122 !image_ctx.journal->is_journal_replaying())) {
123 image_ctx.exclusive_lock->block_requests(0);
124 m_requests_blocked = true;
125 }
126 }
127
128 return send_acquire_exclusive_lock(result);
129 }
130
131 template <typename I>
132 Context *DisableFeaturesRequest<I>::send_acquire_exclusive_lock(int *result) {
133 I &image_ctx = this->m_image_ctx;
134 CephContext *cct = image_ctx.cct;
135 ldout(cct, 20) << this << " " << __func__ << dendl;
136
137 {
138 std::unique_lock locker{image_ctx.owner_lock};
139 // if disabling features w/ exclusive lock supported, we need to
140 // acquire the lock to temporarily block IO against the image
141 if (image_ctx.exclusive_lock != nullptr &&
142 !image_ctx.exclusive_lock->is_lock_owner()) {
143 m_acquired_lock = true;
144
145 Context *ctx = create_context_callback<
146 DisableFeaturesRequest<I>,
147 &DisableFeaturesRequest<I>::handle_acquire_exclusive_lock>(
148 this, image_ctx.exclusive_lock);
149 image_ctx.exclusive_lock->acquire_lock(ctx);
150 return nullptr;
151 }
152 }
153
154 return handle_acquire_exclusive_lock(result);
155 }
156
157 template <typename I>
158 Context *DisableFeaturesRequest<I>::handle_acquire_exclusive_lock(int *result) {
159 I &image_ctx = this->m_image_ctx;
160 CephContext *cct = image_ctx.cct;
161 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
162
163 image_ctx.owner_lock.lock_shared();
164 if (*result < 0) {
165 lderr(cct) << "failed to lock image: " << cpp_strerror(*result) << dendl;
166 image_ctx.owner_lock.unlock_shared();
167 return handle_finish(*result);
168 } else if (image_ctx.exclusive_lock != nullptr &&
169 !image_ctx.exclusive_lock->is_lock_owner()) {
170 lderr(cct) << "failed to acquire exclusive lock" << dendl;
171 *result = image_ctx.exclusive_lock->get_unlocked_op_error();
172 image_ctx.owner_lock.unlock_shared();
173 return handle_finish(*result);
174 }
175
176 do {
177 m_features &= image_ctx.features;
178
179 // interlock object-map and fast-diff together
180 if (((m_features & RBD_FEATURE_OBJECT_MAP) != 0) ||
181 ((m_features & RBD_FEATURE_FAST_DIFF) != 0)) {
182 m_features |= (RBD_FEATURE_OBJECT_MAP | RBD_FEATURE_FAST_DIFF);
183 }
184
185 m_new_features = image_ctx.features & ~m_features;
186 m_features_mask = m_features;
187
188 if ((m_features & RBD_FEATURE_EXCLUSIVE_LOCK) != 0) {
189 if ((m_new_features & RBD_FEATURE_OBJECT_MAP) != 0 ||
190 (m_new_features & RBD_FEATURE_JOURNALING) != 0) {
191 lderr(cct) << "cannot disable exclusive-lock. object-map "
192 "or journaling must be disabled before "
193 "disabling exclusive-lock." << dendl;
194 *result = -EINVAL;
195 break;
196 }
197 m_features_mask |= (RBD_FEATURE_OBJECT_MAP |
198 RBD_FEATURE_FAST_DIFF |
199 RBD_FEATURE_JOURNALING);
200 }
201 if ((m_features & RBD_FEATURE_FAST_DIFF) != 0) {
202 m_disable_flags |= RBD_FLAG_FAST_DIFF_INVALID;
203 }
204 if ((m_features & RBD_FEATURE_OBJECT_MAP) != 0) {
205 m_disable_flags |= RBD_FLAG_OBJECT_MAP_INVALID;
206 }
207 } while (false);
208 image_ctx.owner_lock.unlock_shared();
209
210 if (*result < 0) {
211 return handle_finish(*result);
212 }
213
214 send_get_mirror_mode();
215 return nullptr;
216 }
217
218 template <typename I>
219 void DisableFeaturesRequest<I>::send_get_mirror_mode() {
220 I &image_ctx = this->m_image_ctx;
221 CephContext *cct = image_ctx.cct;
222
223 if ((m_features & RBD_FEATURE_JOURNALING) == 0) {
224 send_append_op_event();
225 return;
226 }
227
228 ldout(cct, 20) << this << " " << __func__ << dendl;
229
230 librados::ObjectReadOperation op;
231 cls_client::mirror_mode_get_start(&op);
232
233 using klass = DisableFeaturesRequest<I>;
234 librados::AioCompletion *comp =
235 create_rados_callback<klass, &klass::handle_get_mirror_mode>(this);
236 m_out_bl.clear();
237 int r = image_ctx.md_ctx.aio_operate(RBD_MIRRORING, comp, &op, &m_out_bl);
238 ceph_assert(r == 0);
239 comp->release();
240 }
241
242 template <typename I>
243 Context *DisableFeaturesRequest<I>::handle_get_mirror_mode(int *result) {
244 I &image_ctx = this->m_image_ctx;
245 CephContext *cct = image_ctx.cct;
246 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
247
248 if (*result == 0) {
249 auto it = m_out_bl.cbegin();
250 *result = cls_client::mirror_mode_get_finish(&it, &m_mirror_mode);
251 }
252
253 if (*result < 0 && *result != -ENOENT) {
254 lderr(cct) << "failed to retrieve pool mirror mode: "
255 << cpp_strerror(*result) << dendl;
256 return handle_finish(*result);
257 }
258
259 ldout(cct, 20) << this << " " << __func__ << ": m_mirror_mode="
260 << m_mirror_mode << dendl;
261
262 send_get_mirror_image();
263 return nullptr;
264 }
265
266 template <typename I>
267 void DisableFeaturesRequest<I>::send_get_mirror_image() {
268 I &image_ctx = this->m_image_ctx;
269 CephContext *cct = image_ctx.cct;
270
271 if (m_mirror_mode != cls::rbd::MIRROR_MODE_IMAGE) {
272 send_disable_mirror_image();
273 return;
274 }
275
276 ldout(cct, 20) << this << " " << __func__ << dendl;
277
278 librados::ObjectReadOperation op;
279 cls_client::mirror_image_get_start(&op, image_ctx.id);
280
281 using klass = DisableFeaturesRequest<I>;
282 librados::AioCompletion *comp =
283 create_rados_callback<klass, &klass::handle_get_mirror_image>(this);
284 m_out_bl.clear();
285 int r = image_ctx.md_ctx.aio_operate(RBD_MIRRORING, comp, &op, &m_out_bl);
286 ceph_assert(r == 0);
287 comp->release();
288 }
289
290 template <typename I>
291 Context *DisableFeaturesRequest<I>::handle_get_mirror_image(int *result) {
292 I &image_ctx = this->m_image_ctx;
293 CephContext *cct = image_ctx.cct;
294 ldout(cct, 20) << this << " " << __func__ << dendl;
295
296 cls::rbd::MirrorImage mirror_image;
297
298 if (*result == 0) {
299 auto it = m_out_bl.cbegin();
300 *result = cls_client::mirror_image_get_finish(&it, &mirror_image);
301 }
302
303 if (*result < 0 && *result != -ENOENT) {
304 lderr(cct) << "failed to retrieve pool mirror image: "
305 << cpp_strerror(*result) << dendl;
306 return handle_finish(*result);
307 }
308
309 if (mirror_image.state == cls::rbd::MIRROR_IMAGE_STATE_ENABLED &&
310 mirror_image.mode == cls::rbd::MIRROR_IMAGE_MODE_JOURNAL && !m_force) {
311 lderr(cct) << "cannot disable journaling: journal-based mirroring "
312 << "enabled and mirror pool mode set to image"
313 << dendl;
314 *result = -EINVAL;
315 return handle_finish(*result);
316 }
317
318 if (mirror_image.mode != cls::rbd::MIRROR_IMAGE_MODE_JOURNAL) {
319 send_close_journal();
320 } else {
321 send_disable_mirror_image();
322 }
323 return nullptr;
324 }
325
326 template <typename I>
327 void DisableFeaturesRequest<I>::send_disable_mirror_image() {
328 I &image_ctx = this->m_image_ctx;
329 CephContext *cct = image_ctx.cct;
330
331 ldout(cct, 20) << this << " " << __func__ << dendl;
332
333 Context *ctx = create_context_callback<
334 DisableFeaturesRequest<I>,
335 &DisableFeaturesRequest<I>::handle_disable_mirror_image>(this);
336
337 mirror::DisableRequest<I> *req =
338 mirror::DisableRequest<I>::create(&image_ctx, m_force, true, ctx);
339 req->send();
340 }
341
342 template <typename I>
343 Context *DisableFeaturesRequest<I>::handle_disable_mirror_image(int *result) {
344 I &image_ctx = this->m_image_ctx;
345 CephContext *cct = image_ctx.cct;
346 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
347
348 if (*result < 0) {
349 lderr(cct) << "failed to disable image mirroring: " << cpp_strerror(*result)
350 << dendl;
351 // not fatal
352 }
353
354 send_close_journal();
355 return nullptr;
356 }
357
358 template <typename I>
359 void DisableFeaturesRequest<I>::send_close_journal() {
360 I &image_ctx = this->m_image_ctx;
361 CephContext *cct = image_ctx.cct;
362
363 {
364 std::unique_lock locker{image_ctx.owner_lock};
365 if (image_ctx.journal != nullptr) {
366 ldout(cct, 20) << this << " " << __func__ << dendl;
367
368 std::swap(m_journal, image_ctx.journal);
369 Context *ctx = create_context_callback<
370 DisableFeaturesRequest<I>,
371 &DisableFeaturesRequest<I>::handle_close_journal>(this);
372
373 m_journal->close(ctx);
374 return;
375 }
376 }
377
378 send_remove_journal();
379 }
380
381 template <typename I>
382 Context *DisableFeaturesRequest<I>::handle_close_journal(int *result) {
383 I &image_ctx = this->m_image_ctx;
384 CephContext *cct = image_ctx.cct;
385 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
386
387 if (*result < 0) {
388 lderr(cct) << "failed to close image journal: " << cpp_strerror(*result)
389 << dendl;
390 }
391
392 ceph_assert(m_journal != nullptr);
393 m_journal->put();
394 m_journal = nullptr;
395
396 send_remove_journal();
397 return nullptr;
398 }
399
400 template <typename I>
401 void DisableFeaturesRequest<I>::send_remove_journal() {
402 I &image_ctx = this->m_image_ctx;
403 CephContext *cct = image_ctx.cct;
404 ldout(cct, 20) << this << " " << __func__ << dendl;
405
406 Context *ctx = create_context_callback<
407 DisableFeaturesRequest<I>,
408 &DisableFeaturesRequest<I>::handle_remove_journal>(this);
409
410 typename journal::TypeTraits<I>::ContextWQ* context_wq;
411 Journal<I>::get_work_queue(cct, &context_wq);
412
413 journal::RemoveRequest<I> *req = journal::RemoveRequest<I>::create(
414 image_ctx.md_ctx, image_ctx.id, librbd::Journal<>::IMAGE_CLIENT_ID,
415 context_wq, ctx);
416
417 req->send();
418 }
419
420 template <typename I>
421 Context *DisableFeaturesRequest<I>::handle_remove_journal(int *result) {
422 I &image_ctx = this->m_image_ctx;
423 CephContext *cct = image_ctx.cct;
424 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
425
426 if (*result < 0) {
427 lderr(cct) << "failed to remove image journal: " << cpp_strerror(*result)
428 << dendl;
429 return handle_finish(*result);
430 }
431
432 send_append_op_event();
433 return nullptr;
434 }
435
436 template <typename I>
437 void DisableFeaturesRequest<I>::send_append_op_event() {
438 I &image_ctx = this->m_image_ctx;
439 CephContext *cct = image_ctx.cct;
440
441 if (!this->template append_op_event<
442 DisableFeaturesRequest<I>,
443 &DisableFeaturesRequest<I>::handle_append_op_event>(this)) {
444 send_remove_object_map();
445 }
446
447 ldout(cct, 20) << this << " " << __func__ << dendl;
448 }
449
450 template <typename I>
451 Context *DisableFeaturesRequest<I>::handle_append_op_event(int *result) {
452 I &image_ctx = this->m_image_ctx;
453 CephContext *cct = image_ctx.cct;
454 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
455
456 if (*result < 0) {
457 lderr(cct) << "failed to commit journal entry: " << cpp_strerror(*result)
458 << dendl;
459 return handle_finish(*result);
460 }
461
462 send_remove_object_map();
463 return nullptr;
464 }
465
466 template <typename I>
467 void DisableFeaturesRequest<I>::send_remove_object_map() {
468 I &image_ctx = this->m_image_ctx;
469 CephContext *cct = image_ctx.cct;
470 ldout(cct, 20) << this << " " << __func__ << dendl;
471
472 if ((m_features & RBD_FEATURE_OBJECT_MAP) == 0) {
473 send_set_features();
474 return;
475 }
476
477 Context *ctx = create_context_callback<
478 DisableFeaturesRequest<I>,
479 &DisableFeaturesRequest<I>::handle_remove_object_map>(this);
480
481 object_map::RemoveRequest<I> *req =
482 object_map::RemoveRequest<I>::create(&image_ctx, ctx);
483 req->send();
484 }
485
486 template <typename I>
487 Context *DisableFeaturesRequest<I>::handle_remove_object_map(int *result) {
488 I &image_ctx = this->m_image_ctx;
489 CephContext *cct = image_ctx.cct;
490 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
491
492 if (*result < 0 && *result != -ENOENT) {
493 lderr(cct) << "failed to remove object map: " << cpp_strerror(*result) << dendl;
494 return handle_finish(*result);
495 }
496
497 send_set_features();
498 return nullptr;
499 }
500
501 template <typename I>
502 void DisableFeaturesRequest<I>::send_set_features() {
503 I &image_ctx = this->m_image_ctx;
504 CephContext *cct = image_ctx.cct;
505 ldout(cct, 20) << this << " " << __func__ << ": new_features="
506 << m_new_features << ", features_mask=" << m_features_mask
507 << dendl;
508
509 librados::ObjectWriteOperation op;
510 librbd::cls_client::set_features(&op, m_new_features, m_features_mask);
511
512 using klass = DisableFeaturesRequest<I>;
513 librados::AioCompletion *comp =
514 create_rados_callback<klass, &klass::handle_set_features>(this);
515 int r = image_ctx.md_ctx.aio_operate(image_ctx.header_oid, comp, &op);
516 ceph_assert(r == 0);
517 comp->release();
518 }
519
520 template <typename I>
521 Context *DisableFeaturesRequest<I>::handle_set_features(int *result) {
522 I &image_ctx = this->m_image_ctx;
523 CephContext *cct = image_ctx.cct;
524 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
525
526 if (*result == -EINVAL && (m_features_mask & RBD_FEATURE_JOURNALING) != 0) {
527 // NOTE: infernalis OSDs will not accept a mask with new features, so
528 // re-attempt with a reduced mask.
529 ldout(cct, 5) << this << " " << __func__
530 << ": re-attempt with a reduced mask" << dendl;
531 m_features_mask &= ~RBD_FEATURE_JOURNALING;
532 send_set_features();
533 }
534
535 if (*result < 0) {
536 lderr(cct) << "failed to update features: " << cpp_strerror(*result)
537 << dendl;
538 return handle_finish(*result);
539 }
540
541 send_update_flags();
542 return nullptr;
543 }
544
545 template <typename I>
546 void DisableFeaturesRequest<I>::send_update_flags() {
547 I &image_ctx = this->m_image_ctx;
548 CephContext *cct = image_ctx.cct;
549
550 if (m_disable_flags == 0) {
551 send_notify_update();
552 return;
553 }
554
555 ldout(cct, 20) << this << " " << __func__ << ": disable_flags="
556 << m_disable_flags << dendl;
557
558 Context *ctx = create_context_callback<
559 DisableFeaturesRequest<I>,
560 &DisableFeaturesRequest<I>::handle_update_flags>(this);
561
562 image::SetFlagsRequest<I> *req =
563 image::SetFlagsRequest<I>::create(&image_ctx, 0, m_disable_flags, ctx);
564 req->send();
565 }
566
567 template <typename I>
568 Context *DisableFeaturesRequest<I>::handle_update_flags(int *result) {
569 I &image_ctx = this->m_image_ctx;
570 CephContext *cct = image_ctx.cct;
571 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
572
573 if (*result < 0) {
574 lderr(cct) << "failed to update image flags: " << cpp_strerror(*result)
575 << dendl;
576 return handle_finish(*result);
577 }
578
579 send_notify_update();
580 return nullptr;
581 }
582
583 template <typename I>
584 void DisableFeaturesRequest<I>::send_notify_update() {
585 I &image_ctx = this->m_image_ctx;
586 CephContext *cct = image_ctx.cct;
587 ldout(cct, 20) << this << " " << __func__ << dendl;
588
589 Context *ctx = create_context_callback<
590 DisableFeaturesRequest<I>,
591 &DisableFeaturesRequest<I>::handle_notify_update>(this);
592
593 image_ctx.notify_update(ctx);
594 }
595
596 template <typename I>
597 Context *DisableFeaturesRequest<I>::handle_notify_update(int *result) {
598 I &image_ctx = this->m_image_ctx;
599 CephContext *cct = image_ctx.cct;
600 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
601
602 if (image_ctx.exclusive_lock == nullptr || !m_acquired_lock) {
603 return handle_finish(*result);
604 }
605
606 send_release_exclusive_lock();
607 return nullptr;
608 }
609
610 template <typename I>
611 void DisableFeaturesRequest<I>::send_release_exclusive_lock() {
612 I &image_ctx = this->m_image_ctx;
613 CephContext *cct = image_ctx.cct;
614 ldout(cct, 20) << this << " " << __func__ << dendl;
615
616 Context *ctx = create_context_callback<
617 DisableFeaturesRequest<I>,
618 &DisableFeaturesRequest<I>::handle_release_exclusive_lock>(
619 this, image_ctx.exclusive_lock);
620
621 image_ctx.exclusive_lock->release_lock(ctx);
622 }
623
624 template <typename I>
625 Context *DisableFeaturesRequest<I>::handle_release_exclusive_lock(int *result) {
626 I &image_ctx = this->m_image_ctx;
627 CephContext *cct = image_ctx.cct;
628 ldout(cct, 20) << this << " " << __func__ << ": r=" << *result << dendl;
629
630 return handle_finish(*result);
631 }
632
633 template <typename I>
634 Context *DisableFeaturesRequest<I>::handle_finish(int r) {
635 I &image_ctx = this->m_image_ctx;
636 CephContext *cct = image_ctx.cct;
637 ldout(cct, 20) << this << " " << __func__ << ": r=" << r << dendl;
638
639 {
640 std::unique_lock locker{image_ctx.owner_lock};
641 if (image_ctx.exclusive_lock != nullptr && m_requests_blocked) {
642 image_ctx.exclusive_lock->unblock_requests();
643 }
644
645 image_ctx.io_image_dispatcher->unblock_writes();
646 }
647 image_ctx.state->handle_prepare_lock_complete();
648
649 return this->create_context_finisher(r);
650 }
651
652 } // namespace operation
653 } // namespace librbd
654
655 template class librbd::operation::DisableFeaturesRequest<librbd::ImageCtx>;