From: Jason Dillaman Date: Tue, 30 Jan 2018 01:09:46 +0000 (-0500) Subject: librbd: clone request now handles clone v2 X-Git-Tag: v13.0.2~327^2~13 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=fe2fe0c46b1e6299630d7fdd5c4dd99b51d56126;p=ceph.git librbd: clone request now handles clone v2 Signed-off-by: Jason Dillaman --- diff --git a/PendingReleaseNotes b/PendingReleaseNotes index 6ce397d470b6..173b4245bb4f 100644 --- a/PendingReleaseNotes +++ b/PendingReleaseNotes @@ -31,6 +31,13 @@ * The rbd CLI's "showmapped" JSON and XML output has changed. + * RBD now optionally supports simplified image clone semantics where + non-protected snapshots can be cloned; and snapshots with linked clones + can be removed and the space automatically reclaimed once all remaining + linked clones are detached. This feature is enabled by default if + the OSD "require-min-compat-client" flag is set to mimic or later; or can be + overridden via the "rbd_default_clone_format" configuration option. + * The sample ``crush-location-hook`` script has been removed. Its output is equivalent to the built-in default behavior, so it has been replaced with an example in the CRUSH documentation. diff --git a/src/librbd/ImageCtx.cc b/src/librbd/ImageCtx.cc index ca4de1b3c147..52568ee7908b 100644 --- a/src/librbd/ImageCtx.cc +++ b/src/librbd/ImageCtx.cc @@ -649,6 +649,19 @@ struct C_InvalidateCache : public Context { return ((features & in_features) == in_features); } + bool ImageCtx::test_op_features(uint64_t in_op_features) const + { + RWLock::RLocker snap_locker(snap_lock); + return test_op_features(in_op_features, snap_lock); + } + + bool ImageCtx::test_op_features(uint64_t in_op_features, + const RWLock &in_snap_lock) const + { + assert(snap_lock.is_locked()); + return ((op_features & in_op_features) == in_op_features); + } + int ImageCtx::get_flags(librados::snap_t _snap_id, uint64_t *_flags) const { assert(snap_lock.is_locked()); diff --git a/src/librbd/ImageCtx.h b/src/librbd/ImageCtx.h index d6978c23dba4..5c7470f3048a 100644 --- a/src/librbd/ImageCtx.h +++ b/src/librbd/ImageCtx.h @@ -278,6 +278,9 @@ namespace librbd { bool test_features(uint64_t test_features) const; bool test_features(uint64_t test_features, const RWLock &in_snap_lock) const; + bool test_op_features(uint64_t op_features) const; + bool test_op_features(uint64_t op_features, + const RWLock &in_snap_lock) const; int get_flags(librados::snap_t in_snap_id, uint64_t *flags) const; int test_flags(uint64_t test_flags, bool *flags_set) const; int test_flags(uint64_t test_flags, const RWLock &in_snap_lock, diff --git a/src/librbd/image/CloneRequest.cc b/src/librbd/image/CloneRequest.cc index f0f850e1e796..fa4b62e4714f 100644 --- a/src/librbd/image/CloneRequest.cc +++ b/src/librbd/image/CloneRequest.cc @@ -83,15 +83,36 @@ void CloneRequest::validate_options() { m_use_p_features = false; } - send_validate_parent(); + std::string default_clone_format = m_cct->_conf->get_val( + "rbd_default_clone_format"); + if (default_clone_format == "1") { + m_clone_format = 1; + } else if (default_clone_format == "auto") { + librados::Rados rados(m_ioctx); + int8_t min_compat_client; + int8_t require_min_compat_client; + int r = rados.get_min_compatible_client(&min_compat_client, + &require_min_compat_client); + if (r < 0) { + complete(r); + return; + } + if (std::max(min_compat_client, require_min_compat_client) < + CEPH_RELEASE_MIMIC) { + m_clone_format = 1; + } + } + + validate_parent(); } template -void CloneRequest::send_validate_parent() { +void CloneRequest::validate_parent() { ldout(m_cct, 20) << this << " " << __func__ << dendl; if (m_p_imctx->operations_disabled) { - lderr(m_cct) << "image operations disabled due to unsupported op features" << dendl; + lderr(m_cct) << "image operations disabled due to unsupported op features" + << dendl; complete(-EROFS); return; } @@ -128,7 +149,7 @@ void CloneRequest::send_validate_parent() { return; } - if (!snap_protected) { + if (m_clone_format == 1 && !snap_protected) { lderr(m_cct) << "parent snapshot must be protected" << dendl; complete(-EINVAL); return; @@ -142,12 +163,14 @@ void CloneRequest::send_validate_child() { ldout(m_cct, 20) << this << " " << __func__ << dendl; using klass = CloneRequest; - librados::AioCompletion *comp = create_rados_callback(this); + librados::AioCompletion *comp = create_rados_callback< + klass, &klass::handle_validate_child>(this); librados::ObjectReadOperation op; op.stat(NULL, NULL, NULL); - int r = m_ioctx.aio_operate(util::old_header_name(m_name), comp, &op, &m_out_bl); + int r = m_ioctx.aio_operate(util::old_header_name(m_name), comp, &op, + &m_out_bl); assert(r == 0); comp->release(); } @@ -242,8 +265,7 @@ void CloneRequest::send_set_parent() { using klass = CloneRequest; librados::AioCompletion *comp = create_rados_callback(this); - int r = m_imctx->md_ctx.aio_operate(m_imctx->header_oid, - comp, &op); + int r = m_imctx->md_ctx.aio_operate(m_imctx->header_oid, comp, &op); assert(r == 0); comp->release(); } @@ -259,11 +281,75 @@ void CloneRequest::handle_set_parent(int r) { return; } - send_add_child(); + send_v2_set_op_feature(); +} + +template +void CloneRequest::send_v2_set_op_feature() { + if (m_clone_format == 1) { + send_v1_add_child(); + return; + } + + ldout(m_cct, 20) << this << " " << __func__ << dendl; + + librados::ObjectWriteOperation op; + cls_client::op_features_set(&op, RBD_OPERATION_FEATURE_CLONE_CHILD, + RBD_OPERATION_FEATURE_CLONE_CHILD); + + auto aio_comp = create_rados_callback< + CloneRequest, &CloneRequest::handle_v2_set_op_feature>(this); + int r = m_ioctx.aio_operate(m_imctx->header_oid, aio_comp, &op); + assert(r == 0); + aio_comp->release(); +} + +template +void CloneRequest::handle_v2_set_op_feature(int r) { + ldout(m_cct, 20) << this << " " << __func__ << " r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to enable clone v2: " << cpp_strerror(r) << dendl; + m_r_saved = r; + send_close(); + return; + } + + send_v2_child_attach(); +} + +template +void CloneRequest::send_v2_child_attach() { + ldout(m_cct, 20) << this << " " << __func__ << dendl; + + librados::ObjectWriteOperation op; + cls_client::child_attach(&op, m_p_imctx->snap_id, + {m_imctx->md_ctx.get_id(), m_imctx->id}); + + auto aio_comp = create_rados_callback< + CloneRequest, &CloneRequest::handle_v2_child_attach>(this); + int r = m_p_imctx->md_ctx.aio_operate(m_p_imctx->header_oid, aio_comp, &op); + assert(r == 0); + aio_comp->release(); +} + +template +void CloneRequest::handle_v2_child_attach(int r) { + ldout(m_cct, 20) << this << " " << __func__ << " r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to attach child image: " << cpp_strerror(r) + << dendl; + m_r_saved = r; + send_close(); + return; + } + + send_metadata_list(); } template -void CloneRequest::send_add_child() { +void CloneRequest::send_v1_add_child() { ldout(m_cct, 20) << this << " " << __func__ << dendl; librados::ObjectWriteOperation op; @@ -271,14 +357,14 @@ void CloneRequest::send_add_child() { using klass = CloneRequest; librados::AioCompletion *comp = - create_rados_callback(this); + create_rados_callback(this); int r = m_ioctx.aio_operate(RBD_CHILDREN, comp, &op); assert(r == 0); comp->release(); } template -void CloneRequest::handle_add_child(int r) { +void CloneRequest::handle_v1_add_child(int r) { ldout(m_cct, 20) << this << " " << __func__ << " r=" << r << dendl; if (r < 0) { @@ -288,22 +374,22 @@ void CloneRequest::handle_add_child(int r) { return; } - send_refresh(); + send_v1_refresh(); } template -void CloneRequest::send_refresh() { +void CloneRequest::send_v1_refresh() { ldout(m_cct, 20) << this << " " << __func__ << dendl; using klass = CloneRequest; RefreshRequest *req = RefreshRequest::create( *m_imctx, false, false, - create_context_callback(this)); + create_context_callback(this)); req->send(); } template -void CloneRequest::handle_refresh(int r) { +void CloneRequest::handle_v1_refresh(int r) { ldout(m_cct, 20) << this << " " << __func__ << " r=" << r << dendl; bool snap_protected = false; diff --git a/src/librbd/image/CloneRequest.h b/src/librbd/image/CloneRequest.h index 75b294d90d16..7c3d3ea44702 100644 --- a/src/librbd/image/CloneRequest.h +++ b/src/librbd/image/CloneRequest.h @@ -40,46 +40,51 @@ private: /** * @verbatim * - * - * | - * v - * VALIDATE PARENT - * | - * v - * (error: bottom up) VALIDATE CHILD - * _______<_______ | - * | | v - * | | CREATE IMAGE - * | | | - * | | v (parent_md exists) - * | | OPEN IMAGE. . . . . > . . . . - * v | / | . - * | REMOVE IMAGE<--------/ v . - * | | SET PARENT IN HEADER . - * | CLOSE IMAGE / | . - * | ^-------<------/ v . - * | |\ UPDATE DIR_CHILDREN. . < . . . - * | | \ / | - * | | *<-----------/ v - * | | REFRESH - * | | | - * | | v (meta is empty) - * | |\ GET META IN PARENT . . . . . . . - * | | \ / | . - * v | *<-----------/ v (journaling disabled) . - * | | SET META IN CHILD . . . . . . . v - * | | / | . - * | -------<-------/ v (no need to enable mirror) . - * | | GET MIRROR MODE . . . . . . . v - * | | / | . - * | -------<-------/ v . - * | | ENABLE MIRROR MODE v - * | | / | . - * | -------<-------/ v . - * | CLOSE IMAGE . . . . .< . . . - * | | - * | v - * |_____________>__________________ + * + * | + * v + * VALIDATE CHILD + * | + * v + * CREATE CHILD + * | ^ + * v | + * OPEN CHILD * * * * * * * * * * > REMOVE CHILD + * | ^ + * v | + * SET PARENT * * * * * * * * * * > CLOSE IMAGE + * | ^ + * |\--------\ * + * | | * + * | v (clone v2 disabled) * + * | V1 ADD CHILD * * * * * * ^ + * | | * + * | v * + * | V1 VALIDATE PROTECTED * * ^ + * | | * + * v | * + * V2 SET CLONE * * * * * * * * * * * ^ + * | | * + * v | * + * V2 ATTACH CHILD * * * * * * * * * * + * | | * + * v v * + * GET PARENT META * * * * * * * * * ^ + * | * + * v (skip if not needed) * + * SET CHILD META * * * * * * * * * * ^ + * | * + * v (skip if not needed) * + * GET MIRROR MODE * * * * * * * * * ^ + * | * + * v (skip if not needed) * + * SET MIRROR ENABLED * * * * * * * * * + * | + * v + * CLOSE IMAGE + * | + * v + * * * @endverbatim */ @@ -99,6 +104,7 @@ private: Context *m_on_finish; CephContext *m_cct; + uint32_t m_clone_format = 2; bool m_use_p_features; uint64_t m_p_features; uint64_t m_features; @@ -109,8 +115,7 @@ private: int m_r_saved = 0; void validate_options(); - - void send_validate_parent(); + void validate_parent(); void send_validate_child(); void handle_validate_child(int r); @@ -124,11 +129,17 @@ private: void send_set_parent(); void handle_set_parent(int r); - void send_add_child(); - void handle_add_child(int r); + void send_v2_set_op_feature(); + void handle_v2_set_op_feature(int r); + + void send_v2_child_attach(); + void handle_v2_child_attach(int r); + + void send_v1_add_child(); + void handle_v1_add_child(int r); - void send_refresh(); - void handle_refresh(int r); + void send_v1_refresh(); + void handle_v1_refresh(int r); void send_metadata_list(); void handle_metadata_list(int r); diff --git a/src/test/librbd/image/test_mock_CloneRequest.cc b/src/test/librbd/image/test_mock_CloneRequest.cc index 8a0f543dff33..fc223088f6db 100644 --- a/src/test/librbd/image/test_mock_CloneRequest.cc +++ b/src/test/librbd/image/test_mock_CloneRequest.cc @@ -168,6 +168,8 @@ public: void SetUp() override { TestMockFixture::SetUp(); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "2")); + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); ASSERT_EQ(0, image_ctx->operations->snap_create( cls::rbd::UserSnapshotNamespace{}, "snap")); @@ -182,6 +184,16 @@ public: } } + void expect_get_min_compat_client(int8_t min_compat_client, int r) { + auto mock_rados_client = get_mock_io_ctx(m_ioctx).get_mock_rados_client(); + EXPECT_CALL(*mock_rados_client, get_min_compatible_client(_, _)) + .WillOnce(Invoke([min_compat_client, r](int8_t* min, int8_t* required_min) { + *min = min_compat_client; + *required_min = min_compat_client; + return r; + })); + } + void expect_get_image_size(MockTestImageCtx &mock_image_ctx, uint64_t snap_id, uint64_t size) { EXPECT_CALL(mock_image_ctx, get_image_size(snap_id)) @@ -220,6 +232,29 @@ public: })); } + void expect_op_features_set(librados::IoCtx& io_ctx, + const std::string& clone_id, int r) { + bufferlist bl; + encode(static_cast(RBD_OPERATION_FEATURE_CLONE_CHILD), bl); + encode(static_cast(RBD_OPERATION_FEATURE_CLONE_CHILD), bl); + + EXPECT_CALL(get_mock_io_ctx(io_ctx), + exec(util::header_name(clone_id), _, StrEq("rbd"), + StrEq("op_features_set"), ContentsEqual(bl), _, _)) + .WillOnce(Return(r)); + } + + void expect_child_attach(MockImageCtx &mock_image_ctx, int r) { + bufferlist bl; + encode(mock_image_ctx.snap_id, bl); + encode(cls::rbd::ChildImageSpec{m_ioctx.get_id(), mock_image_ctx.id}, bl); + + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(mock_image_ctx.header_oid, _, StrEq("rbd"), + StrEq("child_attach"), ContentsEqual(bl), _, _)) + .WillOnce(Return(r)); + } + void expect_add_child(librados::IoCtx& io_ctx, int r) { EXPECT_CALL(get_mock_io_ctx(io_ctx), exec(RBD_CHILDREN, _, StrEq("rbd"), StrEq("add_child"), _, _, _)) @@ -306,13 +341,104 @@ public: librbd::ImageCtx *image_ctx; }; -TEST_F(TestMockImageCloneRequest, Success) { +TEST_F(TestMockImageCloneRequest, SuccessV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "1")); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + expect_set_parent(mock_image_ctx, 0); + expect_add_child(m_ioctx, 0); + + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_refresh_request, 0); + expect_is_snap_protected(mock_image_ctx, true, 0); + + expect_metadata_list(mock_image_ctx, {{"key", {}}}, 0); + expect_metadata_set(m_ioctx, mock_image_ctx, {{"key", {}}}, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + if (is_feature_enabled(RBD_FEATURE_JOURNALING)) { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, 0); + + expect_mirror_enable(mock_mirror_enable_request, 0); + } else { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); + } + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(&mock_image_ctx, m_ioctx, "clone name", + "clone id", clone_opts, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, SuccessV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + expect_op_work_queue(mock_image_ctx); + + InSequence seq; + expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); + expect_is_snap_protected(mock_image_ctx, true, 0); + + MockCreateRequest mock_create_request; + expect_create(mock_create_request, 0); + + expect_open(mock_image_ctx, 0); + expect_set_parent(mock_image_ctx, 0); + + expect_op_features_set(m_ioctx, mock_image_ctx.id, 0); + expect_child_attach(mock_image_ctx, 0); + + expect_metadata_list(mock_image_ctx, {{"key", {}}}, 0); + expect_metadata_set(m_ioctx, mock_image_ctx, {{"key", {}}}, 0); + + MockMirrorEnableRequest mock_mirror_enable_request; + if (is_feature_enabled(RBD_FEATURE_JOURNALING)) { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, true); + expect_mirror_mode_get(mock_image_ctx, cls::rbd::MIRROR_MODE_POOL, 0); + + expect_mirror_enable(mock_mirror_enable_request, 0); + } else { + expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); + } + + expect_close(mock_image_ctx, 0); + + C_SaferCond ctx; + ImageOptions clone_opts; + auto req = new MockCloneRequest(&mock_image_ctx, m_ioctx, "clone name", + "clone id", clone_opts, "", "", + image_ctx->op_work_queue, &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageCloneRequest, SuccessAuto) { REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "auto")); MockTestImageCtx mock_image_ctx(*image_ctx); expect_op_work_queue(mock_image_ctx); InSequence seq; + expect_get_min_compat_client(1, 0); expect_get_image_size(mock_image_ctx, mock_image_ctx.snaps.front(), 123); expect_is_snap_protected(mock_image_ctx, true, 0); @@ -431,6 +557,7 @@ TEST_F(TestMockImageCloneRequest, SetParentError) { TEST_F(TestMockImageCloneRequest, AddChildError) { REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "1")); MockTestImageCtx mock_image_ctx(*image_ctx); expect_op_work_queue(mock_image_ctx); @@ -461,6 +588,7 @@ TEST_F(TestMockImageCloneRequest, AddChildError) { TEST_F(TestMockImageCloneRequest, RefreshError) { REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "1")); MockTestImageCtx mock_image_ctx(*image_ctx); expect_op_work_queue(mock_image_ctx); @@ -495,6 +623,7 @@ TEST_F(TestMockImageCloneRequest, RefreshError) { TEST_F(TestMockImageCloneRequest, MetadataListError) { REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "1")); MockTestImageCtx mock_image_ctx(*image_ctx); expect_op_work_queue(mock_image_ctx); @@ -545,11 +674,9 @@ TEST_F(TestMockImageCloneRequest, MetadataSetError) { expect_open(mock_image_ctx, 0); expect_set_parent(mock_image_ctx, 0); - expect_add_child(m_ioctx, 0); - MockRefreshRequest mock_refresh_request; - expect_refresh(mock_refresh_request, 0); - expect_is_snap_protected(mock_image_ctx, true, 0); + expect_op_features_set(m_ioctx, mock_image_ctx.id, 0); + expect_child_attach(mock_image_ctx, 0); expect_metadata_list(mock_image_ctx, {{"key", {}}}, 0); expect_metadata_set(m_ioctx, mock_image_ctx, {{"key", {}}}, -EINVAL); @@ -570,6 +697,7 @@ TEST_F(TestMockImageCloneRequest, MetadataSetError) { TEST_F(TestMockImageCloneRequest, GetMirrorModeError) { REQUIRE_FEATURE(RBD_FEATURE_LAYERING | RBD_FEATURE_JOURNALING); + ASSERT_EQ(0, _rados.conf_set("rbd_default_clone_format", "1")); MockTestImageCtx mock_image_ctx(*image_ctx); expect_op_work_queue(mock_image_ctx); @@ -623,11 +751,9 @@ TEST_F(TestMockImageCloneRequest, MirrorEnableError) { expect_open(mock_image_ctx, 0); expect_set_parent(mock_image_ctx, 0); - expect_add_child(m_ioctx, 0); - MockRefreshRequest mock_refresh_request; - expect_refresh(mock_refresh_request, 0); - expect_is_snap_protected(mock_image_ctx, true, 0); + expect_op_features_set(m_ioctx, mock_image_ctx.id, 0); + expect_child_attach(mock_image_ctx, 0); expect_metadata_list(mock_image_ctx, {}, 0); @@ -666,11 +792,9 @@ TEST_F(TestMockImageCloneRequest, CloseError) { expect_open(mock_image_ctx, 0); expect_set_parent(mock_image_ctx, 0); - expect_add_child(m_ioctx, 0); - MockRefreshRequest mock_refresh_request; - expect_refresh(mock_refresh_request, 0); - expect_is_snap_protected(mock_image_ctx, true, 0); + expect_op_features_set(m_ioctx, mock_image_ctx.id, 0); + expect_child_attach(mock_image_ctx, 0); expect_metadata_list(mock_image_ctx, {}, 0); expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false); diff --git a/src/test/librbd/image/test_mock_RefreshRequest.cc b/src/test/librbd/image/test_mock_RefreshRequest.cc index eb8ffa5591f0..5ec7a8f54c1b 100644 --- a/src/test/librbd/image/test_mock_RefreshRequest.cc +++ b/src/test/librbd/image/test_mock_RefreshRequest.cc @@ -597,10 +597,11 @@ TEST_F(TestMockImageRefreshRequest, SuccessChild) { expect_test_features(mock_image_ctx); InSequence seq; - expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_mutable_metadata(mock_image_ctx, ictx2->features, 0); expect_get_metadata(mock_image_ctx, 0); expect_apply_metadata(mock_image_ctx, 0); expect_get_flags(mock_image_ctx, 0); + expect_get_op_features(mock_image_ctx, RBD_OPERATION_FEATURE_CLONE_CHILD, 0); expect_get_group(mock_image_ctx, 0); expect_refresh_parent_is_required(*mock_refresh_parent_request, true); expect_refresh_parent_send(mock_image_ctx, *mock_refresh_parent_request, 0); @@ -649,10 +650,11 @@ TEST_F(TestMockImageRefreshRequest, SuccessChildDontOpenParent) { expect_test_features(mock_image_ctx); InSequence seq; - expect_get_mutable_metadata(mock_image_ctx, ictx->features, 0); + expect_get_mutable_metadata(mock_image_ctx, ictx2->features, 0); expect_get_metadata(mock_image_ctx, 0); expect_apply_metadata(mock_image_ctx, 0); expect_get_flags(mock_image_ctx, 0); + expect_get_op_features(mock_image_ctx, RBD_OPERATION_FEATURE_CLONE_CHILD, 0); expect_get_group(mock_image_ctx, 0); if (ictx->test_features(RBD_FEATURE_EXCLUSIVE_LOCK)) { expect_init_exclusive_lock(mock_image_ctx, mock_exclusive_lock, 0);