From: Mykola Golub Date: Wed, 16 Jan 2019 11:58:04 +0000 (+0000) Subject: librbd: new attach child state machine X-Git-Tag: v14.1.0~56^2~10 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=c896c092fba9c6f858bee6cc3e1d4492ab32aa16;p=ceph.git librbd: new attach child state machine Signed-off-by: Mykola Golub --- diff --git a/src/librbd/CMakeLists.txt b/src/librbd/CMakeLists.txt index 549461c39b451..e7da59cf267f6 100644 --- a/src/librbd/CMakeLists.txt +++ b/src/librbd/CMakeLists.txt @@ -50,6 +50,7 @@ set(librbd_internal_srcs exclusive_lock/PostAcquireRequest.cc exclusive_lock/PreReleaseRequest.cc exclusive_lock/StandardPolicy.cc + image/AttachChildRequest.cc image/AttachParentRequest.cc image/CloneRequest.cc image/CloseRequest.cc diff --git a/src/librbd/image/AttachChildRequest.cc b/src/librbd/image/AttachChildRequest.cc new file mode 100644 index 0000000000000..9c95e9f7c4dc5 --- /dev/null +++ b/src/librbd/image/AttachChildRequest.cc @@ -0,0 +1,263 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "librbd/image/AttachChildRequest.h" +#include "common/dout.h" +#include "common/errno.h" +#include "cls/rbd/cls_rbd_client.h" +#include "librbd/ImageCtx.h" +#include "librbd/Utils.h" +#include "librbd/image/RefreshRequest.h" + +#define dout_subsys ceph_subsys_rbd +#undef dout_prefix +#define dout_prefix *_dout << "librbd::image::AttachChildRequest: " << this \ + << " " << __func__ << ": " + +namespace librbd { +namespace image { + +using util::create_context_callback; +using util::create_rados_callback; + +template +AttachChildRequest::AttachChildRequest( + I *image_ctx, I *parent_image_ctx, const librados::snap_t &parent_snap_id, + I *old_parent_image_ctx, const librados::snap_t &old_parent_snap_id, + uint32_t clone_format, Context* on_finish) + : m_image_ctx(image_ctx), m_parent_image_ctx(parent_image_ctx), + m_parent_snap_id(parent_snap_id), + m_old_parent_image_ctx(old_parent_image_ctx), + m_old_parent_snap_id(old_parent_snap_id), m_clone_format(clone_format), + m_on_finish(on_finish), m_cct(m_image_ctx->cct) { +} + +template +void AttachChildRequest::send() { + if (m_clone_format == 1) { + v1_add_child(); + } else { + v2_set_op_feature(); + } +} + +template +void AttachChildRequest::v1_add_child() { + ldout(m_cct, 15) << dendl; + + librados::ObjectWriteOperation op; + cls_client::add_child(&op, {m_parent_image_ctx->md_ctx.get_id(), + m_parent_image_ctx->md_ctx.get_namespace(), + m_parent_image_ctx->id, + m_parent_snap_id}, m_image_ctx->id); + + using klass = AttachChildRequest; + librados::AioCompletion *comp = + create_rados_callback(this); + int r = m_image_ctx->md_ctx.aio_operate(RBD_CHILDREN, comp, &op); + ceph_assert(r == 0); + comp->release(); +} + +template +void AttachChildRequest::handle_v1_add_child(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + if (r < 0) { + if (r == -EEXIST && m_old_parent_image_ctx != nullptr) { + ldout(m_cct, 5) << "child already exists" << dendl; + } else { + lderr(m_cct) << "couldn't add child: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + } + + v1_refresh(); +} + +template +void AttachChildRequest::v1_refresh() { + ldout(m_cct, 15) << dendl; + + using klass = AttachChildRequest; + RefreshRequest *req = RefreshRequest::create( + *m_image_ctx, false, false, + create_context_callback(this)); + req->send(); +} + +template +void AttachChildRequest::handle_v1_refresh(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + bool snap_protected = false; + if (r == 0) { + m_parent_image_ctx->snap_lock.get_read(); + r = m_parent_image_ctx->is_snap_protected(m_parent_snap_id, + &snap_protected); + m_parent_image_ctx->snap_lock.put_read(); + } + + if (r < 0 || !snap_protected) { + lderr(m_cct) << "validate protected failed" << dendl; + finish(-EINVAL); + return; + } + + v1_remove_child_from_old_parent(); +} + +template +void AttachChildRequest::v1_remove_child_from_old_parent() { + if (m_old_parent_image_ctx == nullptr) { + finish(0); + return; + } + + ldout(m_cct, 15) << dendl; + + librados::ObjectWriteOperation op; + cls_client::remove_child(&op, {m_old_parent_image_ctx->md_ctx.get_id(), + m_old_parent_image_ctx->md_ctx.get_namespace(), + m_old_parent_image_ctx->id, + m_old_parent_snap_id}, m_image_ctx->id); + + using klass = AttachChildRequest; + librados::AioCompletion *comp = create_rados_callback< + klass, &klass::handle_v1_remove_child_from_old_parent>(this); + int r = m_image_ctx->md_ctx.aio_operate(RBD_CHILDREN, comp, &op); + ceph_assert(r == 0); + comp->release(); +} + +template +void AttachChildRequest::handle_v1_remove_child_from_old_parent(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + if (r < 0 && r != -ENOENT) { + lderr(m_cct) << "couldn't remove child: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + finish(0); +} + +template +void AttachChildRequest::v2_set_op_feature() { + ldout(m_cct, 15) << dendl; + + librados::ObjectWriteOperation op; + cls_client::op_features_set(&op, RBD_OPERATION_FEATURE_CLONE_CHILD, + RBD_OPERATION_FEATURE_CLONE_CHILD); + + using klass = AttachChildRequest; + auto aio_comp = create_rados_callback< + klass, &klass::handle_v2_set_op_feature>(this); + int r = m_image_ctx->md_ctx.aio_operate(m_image_ctx->header_oid, aio_comp, + &op); + ceph_assert(r == 0); + aio_comp->release(); +} + +template +void AttachChildRequest::handle_v2_set_op_feature(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + if (r < 0) { + lderr(m_cct) << "failed to enable clone v2: " << cpp_strerror(r) << dendl; + finish(r); + return; + } + + v2_child_attach(); +} + +template +void AttachChildRequest::v2_child_attach() { + ldout(m_cct, 15) << dendl; + + librados::ObjectWriteOperation op; + cls_client::child_attach(&op, m_parent_snap_id, + {m_image_ctx->md_ctx.get_id(), + m_image_ctx->md_ctx.get_namespace(), + m_image_ctx->id}); + + using klass = AttachChildRequest; + auto aio_comp = create_rados_callback< + klass, &klass::handle_v2_child_attach>(this); + int r = m_parent_image_ctx->md_ctx.aio_operate(m_parent_image_ctx->header_oid, + aio_comp, &op); + ceph_assert(r == 0); + aio_comp->release(); +} + +template +void AttachChildRequest::handle_v2_child_attach(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + if (r < 0) { + if (r == -EEXIST && m_old_parent_image_ctx != nullptr) { + ldout(m_cct, 5) << "child already exists" << dendl; + } else { + lderr(m_cct) << "failed to attach child image: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + } + + v2_child_detach_from_old_parent(); +} + +template +void AttachChildRequest::v2_child_detach_from_old_parent() { + if (m_old_parent_image_ctx == nullptr) { + finish(0); + return; + } + + ldout(m_cct, 15) << dendl; + + librados::ObjectWriteOperation op; + cls_client::child_detach(&op, m_old_parent_snap_id, + {m_image_ctx->md_ctx.get_id(), + m_image_ctx->md_ctx.get_namespace(), + m_image_ctx->id}); + + using klass = AttachChildRequest; + auto aio_comp = create_rados_callback< + klass, &klass::handle_v2_child_detach_from_old_parent>(this); + int r = m_old_parent_image_ctx->md_ctx.aio_operate( + m_old_parent_image_ctx->header_oid, aio_comp, &op); + ceph_assert(r == 0); + aio_comp->release(); +} + +template +void AttachChildRequest::handle_v2_child_detach_from_old_parent(int r) { + ldout(m_cct, 15) << "r=" << r << dendl; + + if (r < 0 && r != -ENOENT) { + lderr(m_cct) << "failed to detach child image: " << cpp_strerror(r) + << dendl; + finish(r); + return; + } + + finish(0); +} + +template +void AttachChildRequest::finish(int r) { + ldout(m_cct, 5) << "r=" << r << dendl; + + m_on_finish->complete(r); + delete this; +} + +} // namespace image +} // namespace librbd + +template class librbd::image::AttachChildRequest; diff --git a/src/librbd/image/AttachChildRequest.h b/src/librbd/image/AttachChildRequest.h new file mode 100644 index 0000000000000..d1450e8d7d1e4 --- /dev/null +++ b/src/librbd/image/AttachChildRequest.h @@ -0,0 +1,105 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H +#define CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H + +#include "include/int_types.h" +#include "include/rados/librados.hpp" + +class CephContext; +class Context; + +namespace librbd { + +class ImageCtx; + +namespace image { + +template +class AttachChildRequest { +public: + static AttachChildRequest* create(ImageCtxT *image_ctx, + ImageCtxT *parent_image_ctx, + const librados::snap_t &parent_snap_id, + ImageCtxT *old_parent_image_ctx, + const librados::snap_t &old_parent_snap_id, + uint32_t clone_format, + Context* on_finish) { + return new AttachChildRequest(image_ctx, parent_image_ctx, parent_snap_id, + old_parent_image_ctx, old_parent_snap_id, + clone_format, on_finish); + } + + AttachChildRequest(ImageCtxT *image_ctx, + ImageCtxT *parent_image_ctx, + const librados::snap_t &parent_snap_id, + ImageCtxT *old_parent_image_ctx, + const librados::snap_t &old_parent_snap_id, + uint32_t clone_format, + Context* on_finish); + + void send(); + +private: + /** + * @verbatim + * + * + * (clone v1) | (clone v2) + * /----------------/ \---------------\ + * | | + * v v + * V1 ADD CHILD V2 SET CLONE + * | | + * v v + * V1 VALIDATE PROTECTED V2 ATTACH CHILD + * | | + * | v + * V1 REMOVE CHILD FROM OLD PARENT V2 DETACH CHILD FROM OLD PARENT + * | | + * \----------------\ /---------------/ + * | + * v + * + * + * @endverbatim + */ + + ImageCtxT *m_image_ctx; + ImageCtxT *m_parent_image_ctx; + librados::snap_t m_parent_snap_id; + ImageCtxT *m_old_parent_image_ctx; + librados::snap_t m_old_parent_snap_id; + uint32_t m_clone_format; + Context* m_on_finish; + + CephContext *m_cct; + + void v1_add_child(); + void handle_v1_add_child(int r); + + void v1_refresh(); + void handle_v1_refresh(int r); + + void v1_remove_child_from_old_parent(); + void handle_v1_remove_child_from_old_parent(int r); + + void v2_set_op_feature(); + void handle_v2_set_op_feature(int r); + + void v2_child_attach(); + void handle_v2_child_attach(int r); + + void v2_child_detach_from_old_parent(); + void handle_v2_child_detach_from_old_parent(int r); + + void finish(int r); +}; + +} // namespace image +} // namespace librbd + +extern template class librbd::image::AttachChildRequest; + +#endif // CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H diff --git a/src/test/librbd/CMakeLists.txt b/src/test/librbd/CMakeLists.txt index 002b0d2c0a9bb..53570db526342 100644 --- a/src/test/librbd/CMakeLists.txt +++ b/src/test/librbd/CMakeLists.txt @@ -58,6 +58,7 @@ set(unittest_librbd_srcs exclusive_lock/test_mock_PreAcquireRequest.cc exclusive_lock/test_mock_PostAcquireRequest.cc exclusive_lock/test_mock_PreReleaseRequest.cc + image/test_mock_AttachChildRequest.cc image/test_mock_AttachParentRequest.cc image/test_mock_CloneRequest.cc image/test_mock_DetachChildRequest.cc diff --git a/src/test/librbd/image/test_mock_AttachChildRequest.cc b/src/test/librbd/image/test_mock_AttachChildRequest.cc new file mode 100644 index 0000000000000..14f9a73407382 --- /dev/null +++ b/src/test/librbd/image/test_mock_AttachChildRequest.cc @@ -0,0 +1,272 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "test/librbd/test_mock_fixture.h" +#include "test/librbd/test_support.h" +#include "test/librbd/mock/MockImageCtx.h" +#include "test/librbd/mock/MockContextWQ.h" +#include "test/librados_test_stub/MockTestMemIoCtxImpl.h" +#include "librbd/ImageState.h" +#include "librbd/Operations.h" +#include "librbd/image/AttachChildRequest.h" +#include "librbd/image/RefreshRequest.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace librbd { +namespace { + +struct MockTestImageCtx : public MockImageCtx { + MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { + } +}; + +} // anonymous namespace + +namespace image { + +template <> +struct RefreshRequest { + Context* on_finish = nullptr; + static RefreshRequest* s_instance; + static RefreshRequest* create(MockTestImageCtx &image_ctx, + bool acquiring_lock, bool skip_open_parent, + Context *on_finish) { + ceph_assert(s_instance != nullptr); + s_instance->on_finish = on_finish; + return s_instance; + } + + MOCK_METHOD0(send, void()); + + RefreshRequest() { + s_instance = this; + } +}; + +RefreshRequest* RefreshRequest::s_instance = nullptr; + +} // namespace image + +} // namespace librbd + +// template definitions +#include "librbd/image/AttachChildRequest.cc" + +namespace librbd { +namespace image { + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; +using ::testing::WithArg; + +class TestMockImageAttachChildRequest : public TestMockFixture { +public: + typedef AttachChildRequest MockAttachChildRequest; + typedef RefreshRequest MockRefreshRequest; + + void SetUp() override { + TestMockFixture::SetUp(); + + ASSERT_EQ(0, open_image(m_image_name, &image_ctx)); + ASSERT_EQ(0, image_ctx->operations->snap_create( + cls::rbd::UserSnapshotNamespace{}, "snap")); + if (is_feature_enabled(RBD_FEATURE_LAYERING)) { + ASSERT_EQ(0, image_ctx->operations->snap_protect( + cls::rbd::UserSnapshotNamespace{}, "snap")); + + uint64_t snap_id = image_ctx->snap_ids[ + {cls::rbd::UserSnapshotNamespace{}, "snap"}]; + ASSERT_NE(CEPH_NOSNAP, snap_id); + + C_SaferCond ctx; + image_ctx->state->snap_set(snap_id, &ctx); + ASSERT_EQ(0, ctx.wait()); + } + } + + void expect_add_child(MockImageCtx &mock_image_ctx, int r) { + EXPECT_CALL(get_mock_io_ctx(mock_image_ctx.md_ctx), + exec(RBD_CHILDREN, _, StrEq("rbd"), StrEq("add_child"), _, _, _)) + .WillOnce(Return(r)); + } + + void expect_refresh(MockRefreshRequest& mock_refresh_request, int r) { + EXPECT_CALL(mock_refresh_request, send()) + .WillOnce(Invoke([this, &mock_refresh_request, r]() { + image_ctx->op_work_queue->queue(mock_refresh_request.on_finish, r); + })); + } + + void expect_is_snap_protected(MockImageCtx &mock_image_ctx, bool is_protected, + int r) { + EXPECT_CALL(mock_image_ctx, is_snap_protected(_, _)) + .WillOnce(WithArg<1>(Invoke([is_protected, r](bool* is_prot) { + *is_prot = is_protected; + return r; + }))); + } + + void expect_op_features_set(MockImageCtx &mock_image_ctx, 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(mock_image_ctx.md_ctx), + exec(util::header_name(mock_image_ctx.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)); + } + + librbd::ImageCtx *image_ctx; +}; + +TEST_F(TestMockImageAttachChildRequest, SuccessV1) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, 0); + + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_refresh_request, 0); + expect_is_snap_protected(mock_image_ctx, true, 0); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, SuccessV2) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_op_features_set(mock_image_ctx, 0); + expect_child_attach(mock_image_ctx, 0); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 2, + &ctx); + req->send(); + ASSERT_EQ(0, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, AddChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, RefreshError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, 0); + + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_refresh_request, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, ValidateProtectedFailed) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_add_child(mock_image_ctx, 0); + + MockRefreshRequest mock_refresh_request; + expect_refresh(mock_refresh_request, 0); + expect_is_snap_protected(mock_image_ctx, false, 0); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 1, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, SetCloneError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_op_features_set(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 2, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +TEST_F(TestMockImageAttachChildRequest, AttachChildError) { + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + + MockTestImageCtx mock_image_ctx(*image_ctx); + + InSequence seq; + + expect_op_features_set(mock_image_ctx, 0); + expect_child_attach(mock_image_ctx, -EINVAL); + + C_SaferCond ctx; + auto req = MockAttachChildRequest::create(&mock_image_ctx, &mock_image_ctx, + image_ctx->snap_id, nullptr, 0, 2, + &ctx); + req->send(); + ASSERT_EQ(-EINVAL, ctx.wait()); +} + +} // namespace image +} // namespace librbd