m_use_p_features = false;
}
- send_validate_parent();
+ std::string default_clone_format = m_cct->_conf->get_val<std::string>(
+ "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 <typename I>
-void CloneRequest<I>::send_validate_parent() {
+void CloneRequest<I>::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;
}
return;
}
- if (!snap_protected) {
+ if (m_clone_format == 1 && !snap_protected) {
lderr(m_cct) << "parent snapshot must be protected" << dendl;
complete(-EINVAL);
return;
ldout(m_cct, 20) << this << " " << __func__ << dendl;
using klass = CloneRequest<I>;
- librados::AioCompletion *comp = create_rados_callback<klass, &klass::handle_validate_child>(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();
}
using klass = CloneRequest<I>;
librados::AioCompletion *comp =
create_rados_callback<klass, &klass::handle_set_parent>(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();
}
return;
}
- send_add_child();
+ send_v2_set_op_feature();
+}
+
+template <typename I>
+void CloneRequest<I>::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<I>, &CloneRequest<I>::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 <typename I>
+void CloneRequest<I>::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 <typename I>
+void CloneRequest<I>::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<I>, &CloneRequest<I>::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 <typename I>
+void CloneRequest<I>::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 <typename I>
-void CloneRequest<I>::send_add_child() {
+void CloneRequest<I>::send_v1_add_child() {
ldout(m_cct, 20) << this << " " << __func__ << dendl;
librados::ObjectWriteOperation op;
using klass = CloneRequest<I>;
librados::AioCompletion *comp =
- create_rados_callback<klass, &klass::handle_add_child>(this);
+ create_rados_callback<klass, &klass::handle_v1_add_child>(this);
int r = m_ioctx.aio_operate(RBD_CHILDREN, comp, &op);
assert(r == 0);
comp->release();
}
template <typename I>
-void CloneRequest<I>::handle_add_child(int r) {
+void CloneRequest<I>::handle_v1_add_child(int r) {
ldout(m_cct, 20) << this << " " << __func__ << " r=" << r << dendl;
if (r < 0) {
return;
}
- send_refresh();
+ send_v1_refresh();
}
template <typename I>
-void CloneRequest<I>::send_refresh() {
+void CloneRequest<I>::send_v1_refresh() {
ldout(m_cct, 20) << this << " " << __func__ << dendl;
using klass = CloneRequest<I>;
RefreshRequest<I> *req = RefreshRequest<I>::create(
*m_imctx, false, false,
- create_context_callback<klass, &klass::handle_refresh>(this));
+ create_context_callback<klass, &klass::handle_v1_refresh>(this));
req->send();
}
template <typename I>
-void CloneRequest<I>::handle_refresh(int r) {
+void CloneRequest<I>::handle_v1_refresh(int r) {
ldout(m_cct, 20) << this << " " << __func__ << " r=" << r << dendl;
bool snap_protected = false;
/**
* @verbatim
*
- * <start>
- * |
- * 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
- * |_____________>__________________<finish>
+ * <start>
+ * |
+ * v
+ * VALIDATE CHILD
+ * |
+ * v
+ * CREATE CHILD <finish>
+ * | ^
+ * 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
+ * <finish>
*
* @endverbatim
*/
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;
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);
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);
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"));
}
}
+ 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))
}));
}
+ void expect_op_features_set(librados::IoCtx& io_ctx,
+ const std::string& clone_id, int r) {
+ bufferlist bl;
+ encode(static_cast<uint64_t>(RBD_OPERATION_FEATURE_CLONE_CHILD), bl);
+ encode(static_cast<uint64_t>(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"), _, _, _))
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);
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);
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);
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);
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);
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);
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_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);