exclusive_lock/PostAcquireRequest.cc
exclusive_lock/PreReleaseRequest.cc
exclusive_lock/StandardPolicy.cc
+ image/AttachChildRequest.cc
image/AttachParentRequest.cc
image/CloneRequest.cc
image/CloseRequest.cc
--- /dev/null
+// -*- 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 <typename I>
+AttachChildRequest<I>::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 <typename I>
+void AttachChildRequest<I>::send() {
+ if (m_clone_format == 1) {
+ v1_add_child();
+ } else {
+ v2_set_op_feature();
+ }
+}
+
+template <typename I>
+void AttachChildRequest<I>::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<I>;
+ librados::AioCompletion *comp =
+ create_rados_callback<klass, &klass::handle_v1_add_child>(this);
+ int r = m_image_ctx->md_ctx.aio_operate(RBD_CHILDREN, comp, &op);
+ ceph_assert(r == 0);
+ comp->release();
+}
+
+template <typename I>
+void AttachChildRequest<I>::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 <typename I>
+void AttachChildRequest<I>::v1_refresh() {
+ ldout(m_cct, 15) << dendl;
+
+ using klass = AttachChildRequest<I>;
+ RefreshRequest<I> *req = RefreshRequest<I>::create(
+ *m_image_ctx, false, false,
+ create_context_callback<klass, &klass::handle_v1_refresh>(this));
+ req->send();
+}
+
+template <typename I>
+void AttachChildRequest<I>::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 <typename I>
+void AttachChildRequest<I>::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<I>;
+ 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 <typename I>
+void AttachChildRequest<I>::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 <typename I>
+void AttachChildRequest<I>::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<I>;
+ 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 <typename I>
+void AttachChildRequest<I>::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 <typename I>
+void AttachChildRequest<I>::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<I>;
+ 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 <typename I>
+void AttachChildRequest<I>::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 <typename I>
+void AttachChildRequest<I>::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<I>;
+ 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 <typename I>
+void AttachChildRequest<I>::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 <typename I>
+void AttachChildRequest<I>::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<librbd::ImageCtx>;
--- /dev/null
+// -*- 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 <typename ImageCtxT = ImageCtx>
+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
+ *
+ * <start>
+ * (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
+ * <finish>
+ *
+ * @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<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_IMAGE_ATTACH_CHILD_REQUEST_H
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
--- /dev/null
+// -*- 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<MockTestImageCtx> {
+ 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<MockTestImageCtx>* RefreshRequest<MockTestImageCtx>::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<MockTestImageCtx> MockAttachChildRequest;
+ typedef RefreshRequest<MockTestImageCtx> 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<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(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